contextpro-mcp 0.0.0-stage → 1.4.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 (466) hide show
  1. package/CHANGELOG.md +416 -0
  2. package/INSTALL.md +128 -0
  3. package/INSTRUCTIONS.md +33 -0
  4. package/LICENSE +21 -0
  5. package/NOTICE +12 -0
  6. package/README.md +164 -2
  7. package/SECURITY.md +27 -0
  8. package/agent-guide.md +323 -0
  9. package/agent-instructions.md +1 -0
  10. package/build/core/agent-setup.d.ts +5 -0
  11. package/build/core/agent-setup.d.ts.map +1 -0
  12. package/build/core/agent-setup.js +94 -0
  13. package/build/core/agent-setup.js.map +1 -0
  14. package/build/core/artifacts.d.ts +64 -0
  15. package/build/core/artifacts.d.ts.map +1 -0
  16. package/build/core/artifacts.js +370 -0
  17. package/build/core/artifacts.js.map +1 -0
  18. package/build/core/bounded-source.d.ts +15 -0
  19. package/build/core/bounded-source.d.ts.map +1 -0
  20. package/build/core/bounded-source.js +78 -0
  21. package/build/core/bounded-source.js.map +1 -0
  22. package/build/core/changed-since.d.ts +20 -0
  23. package/build/core/changed-since.d.ts.map +1 -0
  24. package/build/core/changed-since.js +43 -0
  25. package/build/core/changed-since.js.map +1 -0
  26. package/build/core/chat-model.d.ts +8 -0
  27. package/build/core/chat-model.d.ts.map +1 -0
  28. package/build/core/chat-model.js +135 -0
  29. package/build/core/chat-model.js.map +1 -0
  30. package/build/core/clustering.d.ts +6 -0
  31. package/build/core/clustering.d.ts.map +1 -0
  32. package/build/core/clustering.js +201 -0
  33. package/build/core/clustering.js.map +1 -0
  34. package/build/core/context-metadata.d.ts +50 -0
  35. package/build/core/context-metadata.d.ts.map +1 -0
  36. package/build/core/context-metadata.js +118 -0
  37. package/build/core/context-metadata.js.map +1 -0
  38. package/build/core/data-dir.d.ts +5 -0
  39. package/build/core/data-dir.d.ts.map +1 -0
  40. package/build/core/data-dir.js +53 -0
  41. package/build/core/data-dir.js.map +1 -0
  42. package/build/core/db-native.d.ts +28 -0
  43. package/build/core/db-native.d.ts.map +1 -0
  44. package/build/core/db-native.js +120 -0
  45. package/build/core/db-native.js.map +1 -0
  46. package/build/core/db.d.ts +294 -0
  47. package/build/core/db.d.ts.map +1 -0
  48. package/build/core/db.js +1047 -0
  49. package/build/core/db.js.map +1 -0
  50. package/build/core/doctor.d.ts +13 -0
  51. package/build/core/doctor.d.ts.map +1 -0
  52. package/build/core/doctor.js +55 -0
  53. package/build/core/doctor.js.map +1 -0
  54. package/build/core/efficiency.d.ts +16 -0
  55. package/build/core/efficiency.d.ts.map +1 -0
  56. package/build/core/efficiency.js +170 -0
  57. package/build/core/efficiency.js.map +1 -0
  58. package/build/core/embedding-tracker.d.ts +25 -0
  59. package/build/core/embedding-tracker.d.ts.map +1 -0
  60. package/build/core/embedding-tracker.js +228 -0
  61. package/build/core/embedding-tracker.js.map +1 -0
  62. package/build/core/embeddings.d.ts +90 -0
  63. package/build/core/embeddings.d.ts.map +1 -0
  64. package/build/core/embeddings.js +693 -0
  65. package/build/core/embeddings.js.map +1 -0
  66. package/build/core/evidence-state.d.ts +42 -0
  67. package/build/core/evidence-state.d.ts.map +1 -0
  68. package/build/core/evidence-state.js +153 -0
  69. package/build/core/evidence-state.js.map +1 -0
  70. package/build/core/extractor-stamp.d.ts +10 -0
  71. package/build/core/extractor-stamp.d.ts.map +1 -0
  72. package/build/core/extractor-stamp.js +51 -0
  73. package/build/core/extractor-stamp.js.map +1 -0
  74. package/build/core/feature-brief-cache.d.ts +37 -0
  75. package/build/core/feature-brief-cache.d.ts.map +1 -0
  76. package/build/core/feature-brief-cache.js +100 -0
  77. package/build/core/feature-brief-cache.js.map +1 -0
  78. package/build/core/file-fingerprint.d.ts +15 -0
  79. package/build/core/file-fingerprint.d.ts.map +1 -0
  80. package/build/core/file-fingerprint.js +24 -0
  81. package/build/core/file-fingerprint.js.map +1 -0
  82. package/build/core/graph-analytics.d.ts +46 -0
  83. package/build/core/graph-analytics.d.ts.map +1 -0
  84. package/build/core/graph-analytics.js +371 -0
  85. package/build/core/graph-analytics.js.map +1 -0
  86. package/build/core/hub.d.ts +23 -0
  87. package/build/core/hub.d.ts.map +1 -0
  88. package/build/core/hub.js +123 -0
  89. package/build/core/hub.js.map +1 -0
  90. package/build/core/ignore-dirs.d.ts +7 -0
  91. package/build/core/ignore-dirs.d.ts.map +1 -0
  92. package/build/core/ignore-dirs.js +84 -0
  93. package/build/core/ignore-dirs.js.map +1 -0
  94. package/build/core/index-freshness.d.ts +15 -0
  95. package/build/core/index-freshness.d.ts.map +1 -0
  96. package/build/core/index-freshness.js +96 -0
  97. package/build/core/index-freshness.js.map +1 -0
  98. package/build/core/index-reconcile.d.ts +28 -0
  99. package/build/core/index-reconcile.d.ts.map +1 -0
  100. package/build/core/index-reconcile.js +129 -0
  101. package/build/core/index-reconcile.js.map +1 -0
  102. package/build/core/local-daemon.d.ts +5 -0
  103. package/build/core/local-daemon.d.ts.map +1 -0
  104. package/build/core/local-daemon.js +282 -0
  105. package/build/core/local-daemon.js.map +1 -0
  106. package/build/core/memory-graph.d.ts +68 -0
  107. package/build/core/memory-graph.d.ts.map +1 -0
  108. package/build/core/memory-graph.js +152 -0
  109. package/build/core/memory-graph.js.map +1 -0
  110. package/build/core/native-repair.d.ts +12 -0
  111. package/build/core/native-repair.d.ts.map +1 -0
  112. package/build/core/native-repair.js +167 -0
  113. package/build/core/native-repair.js.map +1 -0
  114. package/build/core/native-runtime.d.ts +12 -0
  115. package/build/core/native-runtime.d.ts.map +1 -0
  116. package/build/core/native-runtime.js +38 -0
  117. package/build/core/native-runtime.js.map +1 -0
  118. package/build/core/pagerank.d.ts +29 -0
  119. package/build/core/pagerank.d.ts.map +1 -0
  120. package/build/core/pagerank.js +90 -0
  121. package/build/core/pagerank.js.map +1 -0
  122. package/build/core/parser-process.d.ts +21 -0
  123. package/build/core/parser-process.d.ts.map +1 -0
  124. package/build/core/parser-process.js +103 -0
  125. package/build/core/parser-process.js.map +1 -0
  126. package/build/core/parser-worker.d.ts +2 -0
  127. package/build/core/parser-worker.d.ts.map +1 -0
  128. package/build/core/parser-worker.js +17 -0
  129. package/build/core/parser-worker.js.map +1 -0
  130. package/build/core/parser.d.ts +66 -0
  131. package/build/core/parser.d.ts.map +1 -0
  132. package/build/core/parser.js +750 -0
  133. package/build/core/parser.js.map +1 -0
  134. package/build/core/path-guard.d.ts +6 -0
  135. package/build/core/path-guard.d.ts.map +1 -0
  136. package/build/core/path-guard.js +55 -0
  137. package/build/core/path-guard.js.map +1 -0
  138. package/build/core/process-lifecycle.d.ts +30 -0
  139. package/build/core/process-lifecycle.d.ts.map +1 -0
  140. package/build/core/process-lifecycle.js +129 -0
  141. package/build/core/process-lifecycle.js.map +1 -0
  142. package/build/core/project-config.d.ts +83 -0
  143. package/build/core/project-config.d.ts.map +1 -0
  144. package/build/core/project-config.js +302 -0
  145. package/build/core/project-config.js.map +1 -0
  146. package/build/core/project-graph.d.ts +69 -0
  147. package/build/core/project-graph.d.ts.map +1 -0
  148. package/build/core/project-graph.js +874 -0
  149. package/build/core/project-graph.js.map +1 -0
  150. package/build/core/provider-config.d.ts +15 -0
  151. package/build/core/provider-config.d.ts.map +1 -0
  152. package/build/core/provider-config.js +67 -0
  153. package/build/core/provider-config.js.map +1 -0
  154. package/build/core/provider-usage.d.ts +21 -0
  155. package/build/core/provider-usage.d.ts.map +1 -0
  156. package/build/core/provider-usage.js +52 -0
  157. package/build/core/provider-usage.js.map +1 -0
  158. package/build/core/python-language-server.d.ts +23 -0
  159. package/build/core/python-language-server.d.ts.map +1 -0
  160. package/build/core/python-language-server.js +217 -0
  161. package/build/core/python-language-server.js.map +1 -0
  162. package/build/core/python-runtime-identity.d.ts +3 -0
  163. package/build/core/python-runtime-identity.d.ts.map +1 -0
  164. package/build/core/python-runtime-identity.js +53 -0
  165. package/build/core/python-runtime-identity.js.map +1 -0
  166. package/build/core/reconcile-scheduler.d.ts +20 -0
  167. package/build/core/reconcile-scheduler.d.ts.map +1 -0
  168. package/build/core/reconcile-scheduler.js +58 -0
  169. package/build/core/reconcile-scheduler.js.map +1 -0
  170. package/build/core/request-context.d.ts +7 -0
  171. package/build/core/request-context.d.ts.map +1 -0
  172. package/build/core/request-context.js +17 -0
  173. package/build/core/request-context.js.map +1 -0
  174. package/build/core/request-root.d.ts +19 -0
  175. package/build/core/request-root.d.ts.map +1 -0
  176. package/build/core/request-root.js +26 -0
  177. package/build/core/request-root.js.map +1 -0
  178. package/build/core/root-guard.d.ts +8 -0
  179. package/build/core/root-guard.d.ts.map +1 -0
  180. package/build/core/root-guard.js +53 -0
  181. package/build/core/root-guard.js.map +1 -0
  182. package/build/core/roots-detect.d.ts +17 -0
  183. package/build/core/roots-detect.d.ts.map +1 -0
  184. package/build/core/roots-detect.js +63 -0
  185. package/build/core/roots-detect.js.map +1 -0
  186. package/build/core/runtime-session.d.ts +5 -0
  187. package/build/core/runtime-session.d.ts.map +1 -0
  188. package/build/core/runtime-session.js +10 -0
  189. package/build/core/runtime-session.js.map +1 -0
  190. package/build/core/scip-index.d.ts +71 -0
  191. package/build/core/scip-index.d.ts.map +1 -0
  192. package/build/core/scip-index.js +343 -0
  193. package/build/core/scip-index.js.map +1 -0
  194. package/build/core/semantic-diff.d.ts +32 -0
  195. package/build/core/semantic-diff.d.ts.map +1 -0
  196. package/build/core/semantic-diff.js +259 -0
  197. package/build/core/semantic-diff.js.map +1 -0
  198. package/build/core/source-context.d.ts +3 -0
  199. package/build/core/source-context.d.ts.map +1 -0
  200. package/build/core/source-context.js +86 -0
  201. package/build/core/source-context.js.map +1 -0
  202. package/build/core/source-mask.d.ts +8 -0
  203. package/build/core/source-mask.d.ts.map +1 -0
  204. package/build/core/source-mask.js +156 -0
  205. package/build/core/source-mask.js.map +1 -0
  206. package/build/core/source-mutation.d.ts +16 -0
  207. package/build/core/source-mutation.d.ts.map +1 -0
  208. package/build/core/source-mutation.js +193 -0
  209. package/build/core/source-mutation.js.map +1 -0
  210. package/build/core/sql-schema.d.ts +59 -0
  211. package/build/core/sql-schema.d.ts.map +1 -0
  212. package/build/core/sql-schema.js +218 -0
  213. package/build/core/sql-schema.js.map +1 -0
  214. package/build/core/startup-health.d.ts +40 -0
  215. package/build/core/startup-health.d.ts.map +1 -0
  216. package/build/core/startup-health.js +132 -0
  217. package/build/core/startup-health.js.map +1 -0
  218. package/build/core/team-ledger.d.ts +60 -0
  219. package/build/core/team-ledger.d.ts.map +1 -0
  220. package/build/core/team-ledger.js +171 -0
  221. package/build/core/team-ledger.js.map +1 -0
  222. package/build/core/test-impact.d.ts +23 -0
  223. package/build/core/test-impact.d.ts.map +1 -0
  224. package/build/core/test-impact.js +131 -0
  225. package/build/core/test-impact.js.map +1 -0
  226. package/build/core/tool-metrics.d.ts +29 -0
  227. package/build/core/tool-metrics.d.ts.map +1 -0
  228. package/build/core/tool-metrics.js +74 -0
  229. package/build/core/tool-metrics.js.map +1 -0
  230. package/build/core/tool-profile.d.ts +12 -0
  231. package/build/core/tool-profile.d.ts.map +1 -0
  232. package/build/core/tool-profile.js +102 -0
  233. package/build/core/tool-profile.js.map +1 -0
  234. package/build/core/tool-trace.d.ts +8 -0
  235. package/build/core/tool-trace.d.ts.map +1 -0
  236. package/build/core/tool-trace.js +70 -0
  237. package/build/core/tool-trace.js.map +1 -0
  238. package/build/core/tree-sitter-runtime.d.ts +20 -0
  239. package/build/core/tree-sitter-runtime.d.ts.map +1 -0
  240. package/build/core/tree-sitter-runtime.js +324 -0
  241. package/build/core/tree-sitter-runtime.js.map +1 -0
  242. package/build/core/tree-sitter.d.ts +6 -0
  243. package/build/core/tree-sitter.d.ts.map +1 -0
  244. package/build/core/tree-sitter.js +24 -0
  245. package/build/core/tree-sitter.js.map +1 -0
  246. package/build/core/typescript-reference-process.d.ts +30 -0
  247. package/build/core/typescript-reference-process.d.ts.map +1 -0
  248. package/build/core/typescript-reference-process.js +124 -0
  249. package/build/core/typescript-reference-process.js.map +1 -0
  250. package/build/core/typescript-reference-worker.d.ts +2 -0
  251. package/build/core/typescript-reference-worker.d.ts.map +1 -0
  252. package/build/core/typescript-reference-worker.js +14 -0
  253. package/build/core/typescript-reference-worker.js.map +1 -0
  254. package/build/core/typescript-references.d.ts +71 -0
  255. package/build/core/typescript-references.d.ts.map +1 -0
  256. package/build/core/typescript-references.js +392 -0
  257. package/build/core/typescript-references.js.map +1 -0
  258. package/build/core/typescript-skeleton.d.ts +5 -0
  259. package/build/core/typescript-skeleton.d.ts.map +1 -0
  260. package/build/core/typescript-skeleton.js +49 -0
  261. package/build/core/typescript-skeleton.js.map +1 -0
  262. package/build/core/walker.d.ts +16 -0
  263. package/build/core/walker.d.ts.map +1 -0
  264. package/build/core/walker.js +152 -0
  265. package/build/core/walker.js.map +1 -0
  266. package/build/core/workspace-router.d.ts +28 -0
  267. package/build/core/workspace-router.d.ts.map +1 -0
  268. package/build/core/workspace-router.js +93 -0
  269. package/build/core/workspace-router.js.map +1 -0
  270. package/build/core/worktree-gc.d.ts +16 -0
  271. package/build/core/worktree-gc.d.ts.map +1 -0
  272. package/build/core/worktree-gc.js +69 -0
  273. package/build/core/worktree-gc.js.map +1 -0
  274. package/build/core/worktree-overlay.d.ts +22 -0
  275. package/build/core/worktree-overlay.d.ts.map +1 -0
  276. package/build/core/worktree-overlay.js +84 -0
  277. package/build/core/worktree-overlay.js.map +1 -0
  278. package/build/core/worktree-root-guard.d.ts +8 -0
  279. package/build/core/worktree-root-guard.d.ts.map +1 -0
  280. package/build/core/worktree-root-guard.js +82 -0
  281. package/build/core/worktree-root-guard.js.map +1 -0
  282. package/build/core/worktree-seed.d.ts +14 -0
  283. package/build/core/worktree-seed.d.ts.map +1 -0
  284. package/build/core/worktree-seed.js +98 -0
  285. package/build/core/worktree-seed.js.map +1 -0
  286. package/build/git/shadow.d.ts +40 -0
  287. package/build/git/shadow.d.ts.map +1 -0
  288. package/build/git/shadow.js +298 -0
  289. package/build/git/shadow.js.map +1 -0
  290. package/build/index.d.ts +3 -0
  291. package/build/index.d.ts.map +1 -0
  292. package/build/index.js +599 -0
  293. package/build/index.js.map +1 -0
  294. package/build/tools/agentic-context.d.ts +86 -0
  295. package/build/tools/agentic-context.d.ts.map +1 -0
  296. package/build/tools/agentic-context.js +1546 -0
  297. package/build/tools/agentic-context.js.map +1 -0
  298. package/build/tools/blast-radius.d.ts +24 -0
  299. package/build/tools/blast-radius.d.ts.map +1 -0
  300. package/build/tools/blast-radius.js +231 -0
  301. package/build/tools/blast-radius.js.map +1 -0
  302. package/build/tools/changed-since.d.ts +7 -0
  303. package/build/tools/changed-since.d.ts.map +1 -0
  304. package/build/tools/changed-since.js +68 -0
  305. package/build/tools/changed-since.js.map +1 -0
  306. package/build/tools/compiler-actions.d.ts +26 -0
  307. package/build/tools/compiler-actions.d.ts.map +1 -0
  308. package/build/tools/compiler-actions.js +121 -0
  309. package/build/tools/compiler-actions.js.map +1 -0
  310. package/build/tools/context-tree.d.ts +9 -0
  311. package/build/tools/context-tree.d.ts.map +1 -0
  312. package/build/tools/context-tree.js +131 -0
  313. package/build/tools/context-tree.js.map +1 -0
  314. package/build/tools/context.d.ts +37 -0
  315. package/build/tools/context.d.ts.map +1 -0
  316. package/build/tools/context.js +800 -0
  317. package/build/tools/context.js.map +1 -0
  318. package/build/tools/diff.d.ts +9 -0
  319. package/build/tools/diff.d.ts.map +1 -0
  320. package/build/tools/diff.js +176 -0
  321. package/build/tools/diff.js.map +1 -0
  322. package/build/tools/directory-prior.d.ts +16 -0
  323. package/build/tools/directory-prior.d.ts.map +1 -0
  324. package/build/tools/directory-prior.js +121 -0
  325. package/build/tools/directory-prior.js.map +1 -0
  326. package/build/tools/discovery-documents.d.ts +11 -0
  327. package/build/tools/discovery-documents.d.ts.map +1 -0
  328. package/build/tools/discovery-documents.js +71 -0
  329. package/build/tools/discovery-documents.js.map +1 -0
  330. package/build/tools/discovery-hints.d.ts +25 -0
  331. package/build/tools/discovery-hints.d.ts.map +1 -0
  332. package/build/tools/discovery-hints.js +329 -0
  333. package/build/tools/discovery-hints.js.map +1 -0
  334. package/build/tools/efficiency-report.d.ts +13 -0
  335. package/build/tools/efficiency-report.d.ts.map +1 -0
  336. package/build/tools/efficiency-report.js +78 -0
  337. package/build/tools/efficiency-report.js.map +1 -0
  338. package/build/tools/evidence-retrieval.d.ts +33 -0
  339. package/build/tools/evidence-retrieval.d.ts.map +1 -0
  340. package/build/tools/evidence-retrieval.js +200 -0
  341. package/build/tools/evidence-retrieval.js.map +1 -0
  342. package/build/tools/exact-references.d.ts +10 -0
  343. package/build/tools/exact-references.d.ts.map +1 -0
  344. package/build/tools/exact-references.js +349 -0
  345. package/build/tools/exact-references.js.map +1 -0
  346. package/build/tools/explain.d.ts +14 -0
  347. package/build/tools/explain.d.ts.map +1 -0
  348. package/build/tools/explain.js +370 -0
  349. package/build/tools/explain.js.map +1 -0
  350. package/build/tools/feature-brief.d.ts +7 -0
  351. package/build/tools/feature-brief.d.ts.map +1 -0
  352. package/build/tools/feature-brief.js +89 -0
  353. package/build/tools/feature-brief.js.map +1 -0
  354. package/build/tools/feature-hub.d.ts +15 -0
  355. package/build/tools/feature-hub.d.ts.map +1 -0
  356. package/build/tools/feature-hub.js +404 -0
  357. package/build/tools/feature-hub.js.map +1 -0
  358. package/build/tools/file-skeleton.d.ts +7 -0
  359. package/build/tools/file-skeleton.d.ts.map +1 -0
  360. package/build/tools/file-skeleton.js +190 -0
  361. package/build/tools/file-skeleton.js.map +1 -0
  362. package/build/tools/grep.d.ts +25 -0
  363. package/build/tools/grep.d.ts.map +1 -0
  364. package/build/tools/grep.js +341 -0
  365. package/build/tools/grep.js.map +1 -0
  366. package/build/tools/impact-delta.d.ts +7 -0
  367. package/build/tools/impact-delta.d.ts.map +1 -0
  368. package/build/tools/impact-delta.js +102 -0
  369. package/build/tools/impact-delta.js.map +1 -0
  370. package/build/tools/memory-tools.d.ts +69 -0
  371. package/build/tools/memory-tools.d.ts.map +1 -0
  372. package/build/tools/memory-tools.js +366 -0
  373. package/build/tools/memory-tools.js.map +1 -0
  374. package/build/tools/planning-helpers.d.ts +26 -0
  375. package/build/tools/planning-helpers.d.ts.map +1 -0
  376. package/build/tools/planning-helpers.js +269 -0
  377. package/build/tools/planning-helpers.js.map +1 -0
  378. package/build/tools/project-graph.d.ts +19 -0
  379. package/build/tools/project-graph.d.ts.map +1 -0
  380. package/build/tools/project-graph.js +259 -0
  381. package/build/tools/project-graph.js.map +1 -0
  382. package/build/tools/propose-commit.d.ts +9 -0
  383. package/build/tools/propose-commit.d.ts.map +1 -0
  384. package/build/tools/propose-commit.js +156 -0
  385. package/build/tools/propose-commit.js.map +1 -0
  386. package/build/tools/register-tools.d.ts +18 -0
  387. package/build/tools/register-tools.d.ts.map +1 -0
  388. package/build/tools/register-tools.js +1131 -0
  389. package/build/tools/register-tools.js.map +1 -0
  390. package/build/tools/reindex.d.ts +76 -0
  391. package/build/tools/reindex.d.ts.map +1 -0
  392. package/build/tools/reindex.js +871 -0
  393. package/build/tools/reindex.js.map +1 -0
  394. package/build/tools/related.d.ts +6 -0
  395. package/build/tools/related.d.ts.map +1 -0
  396. package/build/tools/related.js +143 -0
  397. package/build/tools/related.js.map +1 -0
  398. package/build/tools/repo-map.d.ts +59 -0
  399. package/build/tools/repo-map.d.ts.map +1 -0
  400. package/build/tools/repo-map.js +270 -0
  401. package/build/tools/repo-map.js.map +1 -0
  402. package/build/tools/resolve-symbol.d.ts +41 -0
  403. package/build/tools/resolve-symbol.d.ts.map +1 -0
  404. package/build/tools/resolve-symbol.js +250 -0
  405. package/build/tools/resolve-symbol.js.map +1 -0
  406. package/build/tools/retrieval-helpers.d.ts +35 -0
  407. package/build/tools/retrieval-helpers.d.ts.map +1 -0
  408. package/build/tools/retrieval-helpers.js +302 -0
  409. package/build/tools/retrieval-helpers.js.map +1 -0
  410. package/build/tools/scip-index.d.ts +12 -0
  411. package/build/tools/scip-index.d.ts.map +1 -0
  412. package/build/tools/scip-index.js +66 -0
  413. package/build/tools/scip-index.js.map +1 -0
  414. package/build/tools/scoped-discovery.d.ts +7 -0
  415. package/build/tools/scoped-discovery.d.ts.map +1 -0
  416. package/build/tools/scoped-discovery.js +89 -0
  417. package/build/tools/scoped-discovery.js.map +1 -0
  418. package/build/tools/search.d.ts +62 -0
  419. package/build/tools/search.d.ts.map +1 -0
  420. package/build/tools/search.js +419 -0
  421. package/build/tools/search.js.map +1 -0
  422. package/build/tools/semantic-diff.d.ts +7 -0
  423. package/build/tools/semantic-diff.d.ts.map +1 -0
  424. package/build/tools/semantic-diff.js +45 -0
  425. package/build/tools/semantic-diff.js.map +1 -0
  426. package/build/tools/semantic-identifiers.d.ts +51 -0
  427. package/build/tools/semantic-identifiers.d.ts.map +1 -0
  428. package/build/tools/semantic-identifiers.js +522 -0
  429. package/build/tools/semantic-identifiers.js.map +1 -0
  430. package/build/tools/semantic-navigate.d.ts +18 -0
  431. package/build/tools/semantic-navigate.d.ts.map +1 -0
  432. package/build/tools/semantic-navigate.js +413 -0
  433. package/build/tools/semantic-navigate.js.map +1 -0
  434. package/build/tools/semantic-search.d.ts +30 -0
  435. package/build/tools/semantic-search.d.ts.map +1 -0
  436. package/build/tools/semantic-search.js +466 -0
  437. package/build/tools/semantic-search.js.map +1 -0
  438. package/build/tools/sql-schema.d.ts +23 -0
  439. package/build/tools/sql-schema.d.ts.map +1 -0
  440. package/build/tools/sql-schema.js +155 -0
  441. package/build/tools/sql-schema.js.map +1 -0
  442. package/build/tools/static-analysis.d.ts +7 -0
  443. package/build/tools/static-analysis.d.ts.map +1 -0
  444. package/build/tools/static-analysis.js +331 -0
  445. package/build/tools/static-analysis.js.map +1 -0
  446. package/build/tools/status.d.ts +74 -0
  447. package/build/tools/status.d.ts.map +1 -0
  448. package/build/tools/status.js +227 -0
  449. package/build/tools/status.js.map +1 -0
  450. package/build/tools/symbolic-edit.d.ts +57 -0
  451. package/build/tools/symbolic-edit.d.ts.map +1 -0
  452. package/build/tools/symbolic-edit.js +500 -0
  453. package/build/tools/symbolic-edit.js.map +1 -0
  454. package/build/tools/test-impact.d.ts +7 -0
  455. package/build/tools/test-impact.d.ts.map +1 -0
  456. package/build/tools/test-impact.js +34 -0
  457. package/build/tools/test-impact.js.map +1 -0
  458. package/build/tools/test-map.d.ts +8 -0
  459. package/build/tools/test-map.d.ts.map +1 -0
  460. package/build/tools/test-map.js +75 -0
  461. package/build/tools/test-map.js.map +1 -0
  462. package/docs/README.md +26 -0
  463. package/docs/architecture.md +35 -0
  464. package/docs/configuration.md +178 -0
  465. package/docs/maintenance.md +77 -0
  466. package/package.json +80 -4
@@ -0,0 +1,800 @@
1
+ // Locate and read evidence in one round trip. All excerpts come from one disk
2
+ // snapshot per file. Query packets can persist regenerable evidence artifacts;
3
+ // exact targets remain stateless and never request model enrichment.
4
+ import { readFile, stat } from "node:fs/promises";
5
+ import { relative, dirname } from "node:path";
6
+ import { createHash } from "node:crypto";
7
+ import ts from "typescript";
8
+ import { analyzeContent, flattenSymbols } from "../core/parser.js";
9
+ import { maskSourceText } from "../core/source-mask.js";
10
+ import { resolveProjectPath } from "../core/path-guard.js";
11
+ import { searchTerms, hasCompatibleEmbeddingModel, getEmbedProvider, getEmbedModel } from "../core/embeddings.js";
12
+ import { getDB } from "../core/db.js";
13
+ import { walkDirectory } from "../core/walker.js";
14
+ import { readBoundedRanges } from "../core/bounded-source.js";
15
+ import { extractIdentifierHints, unifiedSearchData } from "./search.js";
16
+ import { mentionedFiles, documentTopicFiles, isUiOrientationQuery, isOrientationQuery, orientationFiles, isLockfile, wantsLockfile, queryLiterals, discoveryPathWeight, positiveDiscoveryQuery } from "./discovery-hints.js";
17
+ import { scopedSourceCandidates } from "./scoped-discovery.js";
18
+ import { discoverDocuments, wantsDocuments } from "./discovery-documents.js";
19
+ import { connectedRetrievalEnabled, retrieveEvidence } from "./evidence-retrieval.js";
20
+ import { finishSourceContext } from "../core/source-context.js";
21
+ import { lookupFeatureBrief } from "../core/feature-brief-cache.js";
22
+ import { chatModelIdentity } from "../core/chat-model.js";
23
+ import { getProvider, getEmbedModelName } from "../core/provider-config.js";
24
+ import { getProjectRiskEvidence, isTestEvidence } from "./retrieval-helpers.js";
25
+ const bytes = (value) => Buffer.byteLength(value, "utf8");
26
+ const render = (part) => `${part.file}:${part.start}-${part.end}\n${part.text}`;
27
+ function lexicalFallbackNotice(db) {
28
+ // Use local metadata, without a status walk or an embedding request.
29
+ const stale = db.isEmbeddingsStale();
30
+ const detail = (value) => {
31
+ let result = "";
32
+ for (const character of value.replace(/\s+/g, " ")) {
33
+ if (bytes(result + character) > 100)
34
+ break;
35
+ result += character;
36
+ }
37
+ return result;
38
+ };
39
+ const error = db.getMeta("last_embed_error");
40
+ const cause = stale.stale ? `semantic vectors are stale (${detail(stale.reason || "unknown cause")})`
41
+ : db.getEmbeddingStats().fileSearch > 0 && (!db.getMeta("embed_provider") || !db.getMeta("embed_model")) ? "stored vectors have no complete provider/model identity"
42
+ : db.getMeta("embed_model") && !hasCompatibleEmbeddingModel(db) ? `stored model ${detail(`${db.getMeta("embed_provider")}/${db.getMeta("embed_model")}`)} differs from this worker's ${detail(`${getEmbedProvider()}/${getEmbedModel()}`)}`
43
+ : db.getEmbeddingStats().fileSearch > 0 ? undefined
44
+ : error ? `last embedding build failed: ${detail(error)}`
45
+ : db.getMeta("last_embed_skipped") === "true" ? "corpus embeddings were skipped by a structural-only refresh"
46
+ : "no file vectors are stored";
47
+ return cause ? `Discovery used lexical fallback: ${cause}. Configure the intended embedding model; run reindex with this root and no_embed:false (unset CONTEXTPRO_REINDEX_NO_EMBED).`
48
+ : "Discovery used lexical fallback or weak semantic evidence for this query; vectors exist, but the query signal was weak or unavailable. Check provider health or refine the query; a weak match alone does not require rebuilding the index.";
49
+ }
50
+ /** Keep uncovered tails/interiors instead of rejecting every partial overlap. */
51
+ function uncoveredRanges(part, selected) {
52
+ const ranges = [];
53
+ let start = part.start;
54
+ for (const covered of selected.filter(item => item.file === part.file).sort((a, b) => a.start - b.start)) {
55
+ if (covered.end < start)
56
+ continue;
57
+ if (covered.start > part.end)
58
+ break;
59
+ if (covered.start > start)
60
+ ranges.push([start, covered.start - 1]);
61
+ start = Math.max(start, covered.end + 1);
62
+ }
63
+ if (start <= part.end)
64
+ ranges.push([start, part.end]);
65
+ const lines = part.text.split("\n");
66
+ return ranges.map(([start, end]) => ({ ...part, start, end, text: lines.slice(start - part.start, end - part.start + 1).join("\n") }));
67
+ }
68
+ export async function getContext(options) {
69
+ if (options.previousContextId && !/^source_context-[a-f0-9]{14}$/.test(options.previousContextId))
70
+ throw new Error("Invalid previous_context_id; use a source_context ID from a context response.");
71
+ if (options.previousContextId && options.targets?.length)
72
+ throw new Error("previous_context_id is for query discovery only; exact targets are stateless.");
73
+ const packet = await collectSourceEvidence(options);
74
+ return options.targets?.length ? packet.text : finishSourceContext(options, packet);
75
+ }
76
+ export async function collectSourceEvidence(options) {
77
+ const query = options.query?.trim() ?? "";
78
+ if (options.targets?.length && options.intent)
79
+ throw new Error("intent is for query discovery only.");
80
+ const adaptive = !options.targets?.length && (options.intent !== undefined || process.env.CONTEXTPRO_ADAPTIVE_CONTEXT === "true");
81
+ const intent = options.intent && options.intent !== "auto" ? options.intent
82
+ : isOrientationQuery(query) ? "overview" : /\b(?:fix|debug|failure|error|broken)\b/i.test(query) ? "debug"
83
+ : /\b(?:review|impact|regression)\b/i.test(query) ? "review"
84
+ : /\b(?:change|implement|add|update|refactor|remove)\b/i.test(query) ? "change" : "lookup";
85
+ const changeContext = adaptive && ["change", "debug", "review"].includes(intent);
86
+ const literals = [...new Set([...queryLiterals(query), query.replace(/^(["'`])([\s\S]*)\1$/, "$2").normalize("NFC").toLowerCase()])];
87
+ if (!query && !options.targets?.length)
88
+ throw new Error("Provide query or targets.");
89
+ if (query.length > 2000 || (options.targets?.length ?? 0) > 6)
90
+ throw new Error("Use a query up to 2000 characters and at most six targets.");
91
+ const budget = options.maxTokens ?? 4000;
92
+ if (!Number.isInteger(budget) || budget < 512 || budget > 12000)
93
+ throw new Error("max_tokens must be from 512 to 12000 (estimated UTF-8 bytes/4).");
94
+ let targets = [...options.targets ?? []];
95
+ const notices = [];
96
+ let discoveryNotice;
97
+ const filePriors = new Map();
98
+ const namedSymbols = new Set();
99
+ const namedDefinitions = new Map();
100
+ const requestedFiles = new Set();
101
+ // Change intent implies code only when documentation is not explicitly requested.
102
+ const positiveQuery = positiveDiscoveryQuery(query);
103
+ const explicitDocuments = wantsDocuments(query) && (/\b(?:docs|documents?|documentation|readme|specifications?|specs?|plans?|instructions)\b|\.(?:md|mdx|rst|txt)\b/i.test(positiveQuery)
104
+ || /\b(?:read|find|open|show|locate|search|review|inspect)\s+(?:(?:the|relevant|existing|current)\s+)*(?:roadmap|handoff|readiness)\b/i.test(positiveQuery));
105
+ const mixedDocuments = (changeContext && !explicitDocuments) || (!options.targets?.length && /\b(?:code|implementation)\b/i.test(query));
106
+ const orientation = options.retrievalPreset !== "semantic_only" && (isOrientationQuery(query) || (adaptive && intent === "overview"));
107
+ // File mentions within an overview supplement the requested architecture;
108
+ // they must not switch discovery into a restricted whole-file read.
109
+ const readRequestedFiles = !orientation && (/\b(?:read|show|open|whole|entire|full)\b/i.test(positiveDiscoveryQuery(query))
110
+ || /^[\w@./-]+\.[A-Za-z][\w]*(?::\d+(?:-\d+)?)?$/.test(query));
111
+ const overviewFiles = new Set();
112
+ const documentFiles = new Set();
113
+ const topicTerms = new Map();
114
+ const scopedFiles = new Set();
115
+ const coverageFiles = new Set();
116
+ let connectedCandidates = [];
117
+ let rolesClassified = false;
118
+ const connected = options.connected ?? (changeContext || connectedRetrievalEnabled());
119
+ if (!targets.length) {
120
+ // Exact identifiers need no embedding round trip. Natural language keeps the
121
+ // hybrid retriever: no unvalidated lexical confidence gate discards recall.
122
+ const exact = /^[A-Za-z_$][\w$]*$/.test(query);
123
+ const packet = options.retrieved ?? (connected && (!exact || changeContext) ? await retrieveEvidence({ rootDir: options.rootDir, query, preset: options.retrievalPreset }) : undefined);
124
+ // Gather beyond the first few hits before packing; incidental callers can
125
+ // rank ahead of the implementation. The source/output budget stays fixed.
126
+ const found = packet?.search ?? await unifiedSearchData({ rootDir: options.rootDir, query,
127
+ mode: "file", preset: exact ? "keyword_only" : options.retrievalPreset ?? "balanced", topK: exact ? 4 : 12,
128
+ inlineCode: "off", includeDependencies: true, persistArtifact: false });
129
+ connectedCandidates = packet?.candidates ?? [];
130
+ rolesClassified = packet !== undefined;
131
+ if (packet?.relationNotice)
132
+ notices.push(packet.relationNotice);
133
+ if (packet?.relevanceNotice)
134
+ notices.push(packet.relevanceNotice);
135
+ if (packet?.limited)
136
+ notices.push("Candidate pool capped at 32 files; additional paths omitted.");
137
+ if (packet) {
138
+ const brief = await lookupFeatureBrief(options.rootDir, query);
139
+ if (brief) {
140
+ const interpretation = brief.brief.interpretations[0];
141
+ const detail = interpretation ? `Model interpretation: ${interpretation.text}` : brief.brief.facts[0];
142
+ notices.push(`Cached feature explanation: ${brief.artifact.id}${detail ? `; ${detail.slice(0, 260)}` : ""}. Read artifact for citations.`);
143
+ }
144
+ }
145
+ if (found.freshness.warning)
146
+ notices.push(found.freshness.warning);
147
+ const db = await getDB(options.rootDir);
148
+ if (!exact && options.retrievalPreset === "keyword_only")
149
+ discoveryNotice = "Discovery used the requested keyword_only preset; semantic vectors were not consulted.";
150
+ else if (!exact && found.trust.ambiguities.some(warning => warning.includes("keyword-only")))
151
+ discoveryNotice = lexicalFallbackNotice(db);
152
+ if (!["fresh", "refreshed"].includes(found.freshness.indexState))
153
+ notices.push(`Discovery index: ${found.freshness.indexState}; verify scope with native search if incomplete.`);
154
+ const primary = found.files?.results.map(item => ({ file: item.path })) ?? [];
155
+ primary.forEach((item, index) => filePriors.set(item.file, 1 / (1 + index * .5)));
156
+ targets = [...primary.slice(0, 2), ...found.dependencies?.slice(0, 2).map(item => ({ file: item.path, helper: true })) ?? [], ...primary.slice(2)];
157
+ if (packet) {
158
+ targets = packet.candidates.map(item => ({ file: item.path, helper: item.roles.includes("dependency") }));
159
+ const high = Math.max(...packet.candidates.map(item => item.score), .001);
160
+ packet.candidates.forEach(item => filePriors.set(item.path, .5 + item.score / high));
161
+ }
162
+ // A literal symbol is stronger evidence of the requested definition than
163
+ // prose similarity. Look up defining files even if ranking omitted them;
164
+ // ranges are still resolved against the current source below.
165
+ const hints = extractIdentifierHints(query);
166
+ const indexed = db.getAllFiles();
167
+ // Multi-key questions need evidence for each literal, even when one key's
168
+ // many callers fill the primary result window before another key's source.
169
+ const literalFiles = new Set();
170
+ const coverageTerms = [...new Set(searchTerms(query))];
171
+ for (const literal of options.retrievalPreset === "semantic_only" ? [] : exact ? [query] : queryLiterals(query)) {
172
+ const matches = db.searchFileText(literal, 40, true)
173
+ .sort((a, b) => discoveryPathWeight(query, b.path) - discoveryPathWeight(query, a.path) || a.score - b.score).slice(0, 8);
174
+ for (const match of matches) {
175
+ literalFiles.add(match.path);
176
+ filePriors.set(match.path, 1);
177
+ }
178
+ }
179
+ // Common literals can fill the OR lane with short incidental files. Reserve
180
+ // bounded candidates containing every normalized term without displacing
181
+ // explicit quoted literals from the front of the candidate lane.
182
+ if (options.retrievalPreset !== "semantic_only" && coverageTerms.length > 1 && coverageTerms.length <= 12) {
183
+ for (const match of db.searchFileText(coverageTerms.join(" "), 8, false, true)) {
184
+ if (discoveryPathWeight(query, match.path) < 1)
185
+ continue;
186
+ literalFiles.add(match.path);
187
+ coverageFiles.add(match.path);
188
+ filePriors.set(match.path, 1);
189
+ }
190
+ }
191
+ targets = [...literalFiles].map(file => ({ file })).concat(targets.filter(target => !literalFiles.has(target.file)));
192
+ const files = new Map(indexed.map(file => [file.id, file.path]));
193
+ for (const hint of options.retrievalPreset === "semantic_only" ? [] : hints.slice(0, 16)) {
194
+ const matches = db.searchSymbolsByName(hint).filter(symbol => symbol.name === hint);
195
+ const hasImplementation = matches.some(symbol => {
196
+ const file = files.get(symbol.file_id);
197
+ return file && !isTestEvidence(file) && discoveryPathWeight(query, file, true) >= 1;
198
+ });
199
+ for (const symbol of matches) {
200
+ const file = files.get(symbol.file_id);
201
+ if (file && discoveryPathWeight(query, file, true) >= 1) {
202
+ if (hasImplementation && isTestEvidence(file) && discoveryPathWeight(query, file) < 1)
203
+ continue;
204
+ const names = namedDefinitions.get(file) ?? new Set();
205
+ names.add(symbol.name);
206
+ namedDefinitions.set(file, names);
207
+ namedSymbols.add(symbol.name);
208
+ }
209
+ }
210
+ }
211
+ const definitions = [...namedDefinitions.keys()].sort((a, b) => Number(query.includes(b)) - Number(query.includes(a)) || a.localeCompare(b));
212
+ if (definitions.length > 6 || hints.length > 16)
213
+ notices.push("Some named definitions omitted; narrow the query or use exact file/symbol targets.");
214
+ targets = [...definitions.slice(0, 6).map(file => ({ file })), ...targets.filter(target => !definitions.slice(0, 6).includes(target.file))];
215
+ const documents = options.retrievalPreset !== "semantic_only" && (wantsDocuments(query) || (adaptive && (!exact || definitions.length === 0)))
216
+ ? await discoverDocuments(options.rootDir, query, { requireRelevance: adaptive && !wantsDocuments(query) }) : undefined;
217
+ // Strict multi-topic candidates can include controls without saying "code";
218
+ // discovering them does not itself authorize capping documentation.
219
+ if (documents && !orientation && options.retrievalPreset !== "semantic_only") {
220
+ const topics = documentTopicFiles(db, query);
221
+ for (const { file, terms } of topics) {
222
+ topicTerms.set(file, terms);
223
+ filePriors.set(file, Math.max(filePriors.get(file) ?? 0, 1));
224
+ }
225
+ targets = topics.map(({ file }) => ({ file })).concat(targets.filter(target => !topicTerms.has(target.file)));
226
+ }
227
+ if (!exact && !orientation && options.retrievalPreset !== "semantic_only") {
228
+ const scoped = scopedSourceCandidates(db, query, hints);
229
+ for (const { file, terms } of scoped) {
230
+ if (!topicTerms.has(file))
231
+ scopedFiles.add(file);
232
+ topicTerms.set(file, [...new Set([...topicTerms.get(file) ?? [], ...terms])]);
233
+ filePriors.set(file, Math.max(filePriors.get(file) ?? 0, 2));
234
+ }
235
+ targets = scoped.map(({ file }) => ({ file })).concat(targets.filter(target => !scoped.some(item => item.file === target.file)));
236
+ if (scoped.length)
237
+ notices.push("Scoped candidates use filename and literal-text hints, not resolved references; verify bindings with exact_references.");
238
+ }
239
+ const mentioned = [...new Set([...mentionedFiles(options.rootDir, query, indexed), ...documents?.mentioned ?? []])];
240
+ for (const file of documents?.files ?? []) {
241
+ documentFiles.add(file);
242
+ filePriors.set(file, 2 * discoveryPathWeight(query, file));
243
+ }
244
+ if (documentFiles.size)
245
+ notices.push(`Document discovery uses filenames and bounded heading/intro previews${documents?.limited ? "; additional documents omitted" : ""}; verify scope with exact reads.`);
246
+ if (mentioned.length > 6)
247
+ notices.push("More than six files named; additional files omitted. Use batches of at most six exact targets.");
248
+ for (const file of mentioned.slice(0, 6)) {
249
+ requestedFiles.add(file);
250
+ filePriors.set(file, 2);
251
+ }
252
+ if (orientation) {
253
+ for (const file of orientationFiles(db, indexed, query)) {
254
+ overviewFiles.add(file);
255
+ filePriors.set(file, 2);
256
+ }
257
+ // Documentation is not necessarily part of the structural source index.
258
+ // Use the ignore-aware walker so ignored files and symlinks stay excluded.
259
+ const readme = (await walkDirectory({ rootDir: options.rootDir, depthLimit: 1 }))
260
+ .filter(entry => !entry.isDirectory && entry.depth === 0 && /^readme\.(?:md|txt|rst)$/i.test(entry.relativePath))
261
+ .sort((a, b) => a.relativePath.localeCompare(b.relativePath))[0];
262
+ if (readme && (!isUiOrientationQuery(query) || documents))
263
+ overviewFiles.add(readme.relativePath);
264
+ notices.push("Orientation includes structural entry points and import hubs; excerpts are not exhaustive.");
265
+ }
266
+ const priority = new Set([...requestedFiles, ...documentFiles, ...overviewFiles]);
267
+ targets = [...priority].map(file => ({ file })).concat(targets.filter(target => !priority.has(target.file)));
268
+ if (readRequestedFiles && requestedFiles.size)
269
+ targets = targets.filter(target => requestedFiles.has(target.file));
270
+ targets = targets.filter(target => discoveryPathWeight(query, target.file) > 0);
271
+ if (!wantsLockfile(query) && targets.some(target => !isLockfile(target.file))) {
272
+ targets = targets.filter(target => !isLockfile(target.file) || requestedFiles.has(target.file));
273
+ }
274
+ if (changeContext) {
275
+ const configurations = (await walkDirectory({ rootDir: options.rootDir })).filter(entry => !entry.isDirectory
276
+ && /(?:^|\/)(?:package\.json|(?:ts|js|pyright)config[^/]*\.json|pyproject\.toml|[^/]+\.config\.[^/]+)$/.test(entry.relativePath)
277
+ && targets.some(target => dirname(entry.relativePath) === "." || target.file.startsWith(dirname(entry.relativePath) + "/")))
278
+ .sort((a, b) => b.depth - a.depth || a.relativePath.localeCompare(b.relativePath));
279
+ targets.push(...configurations.slice(0, 8).map(entry => ({ file: entry.relativePath, helper: true })));
280
+ if (configurations.length > 8)
281
+ notices.push("Additional configuration candidates omitted at the eight-file bound.");
282
+ }
283
+ if (packet) {
284
+ targets = [...new Map(targets.map(target => [target.file, target])).values()];
285
+ if (targets.length > 32) {
286
+ targets = targets.slice(0, 32);
287
+ notices.push("Additional discovery targets omitted at the 32-file bound.");
288
+ }
289
+ }
290
+ }
291
+ const terms = new Set(searchTerms(query));
292
+ if (adaptive && !orientation && /\b(?:routes?|endpoints?|handlers?)\b/i.test(positiveDiscoveryQuery(query))) {
293
+ // A named endpoint topic is stronger than an incidental prose match in a
294
+ // client or workflow. This stays a soft lexical prior: callers, contracts
295
+ // and tests still compete for the same bounded packet.
296
+ const generic = new Set("api app apps src source web native server route routes endpoint endpoints handler handlers code file files implementation locate find show".split(" "));
297
+ for (const target of targets) {
298
+ if (!/(?:^|\/)(?:api|routes?|endpoints?|handlers?)(?:\/|[.-])/i.test(target.file))
299
+ continue;
300
+ const topics = new Set(searchTerms(target.file).filter(term => terms.has(term) && !generic.has(term)));
301
+ if (topics.size)
302
+ filePriors.set(target.file, (filePriors.get(target.file) ?? .5) * (1 + Math.min(2, topics.size)));
303
+ }
304
+ }
305
+ const candidates = [];
306
+ const omitted = [];
307
+ const snapshots = new Map();
308
+ const boundedSnapshots = new Map();
309
+ const analyses = new Map();
310
+ const discoveryParts = [];
311
+ const definitions = [];
312
+ const literalParts = [];
313
+ const identifierParts = [];
314
+ const priorityParts = [];
315
+ const documentPassages = [];
316
+ const scopedParts = [];
317
+ const smallDependencies = [];
318
+ const seen = new Set();
319
+ const limit = budget * 4;
320
+ const risks = changeContext ? await getProjectRiskEvidence(options.rootDir, query) : [];
321
+ let riskNotice;
322
+ if (changeContext) {
323
+ riskNotice = "Known risks: unavailable in bounded current task memory; not proof of absence.";
324
+ if (risks.length) {
325
+ riskNotice = "Known risks (textual task notes; source evidence current):";
326
+ const riskBudget = Math.min(480, Math.floor(limit * .15));
327
+ riskEntries: for (const note of risks)
328
+ for (const risk of note.risks) {
329
+ const entry = `\n${note.id}: ${risk}`;
330
+ if (bytes(riskNotice + entry) + 30 <= riskBudget)
331
+ riskNotice += entry;
332
+ else {
333
+ riskNotice += "\nOmitted: more risk text.";
334
+ break riskEntries;
335
+ }
336
+ }
337
+ }
338
+ }
339
+ // Explicit targets are admitted whole. Discovery may select labelled interior
340
+ // ranges, always copied verbatim from the snapshot rather than synthesized.
341
+ let available = limit - 768 - (options.targets?.length ? 0 : 384) - (adaptive ? 480 : 0) - (riskNotice ? bytes(riskNotice) + 2 : 0);
342
+ for (const target of targets) {
343
+ const fullPath = resolveProjectPath(options.rootDir, target.file, "context target");
344
+ const file = relative(options.rootDir, fullPath).split("\\").join("/");
345
+ if (bytes(file) > 240 || (target.symbol?.length ?? 0) > 200)
346
+ throw new Error("Target path or symbol is too long; use a shorter repository-relative target.");
347
+ for (const line of [target.start, target.end])
348
+ if (line !== undefined && (!Number.isInteger(line) || line < 1))
349
+ throw new Error("Line numbers must be positive integers.");
350
+ if (target.symbol && (target.start !== undefined || target.end !== undefined))
351
+ throw new Error("Choose symbol or line range for a target.");
352
+ if (target.end !== undefined && target.end < (target.start ?? 1))
353
+ throw new Error("end must be at least start.");
354
+ let source = snapshots.get(file);
355
+ try {
356
+ if (source === undefined) {
357
+ const stats = await stat(fullPath);
358
+ if (!stats.isFile()) {
359
+ omitted.push(`${file}: not a regular file`);
360
+ continue;
361
+ }
362
+ if (stats.size > 2_000_000) {
363
+ if (!options.targets?.length || target.symbol || target.end === undefined) {
364
+ omitted.push(`${file}: exceeds 2 MB read limit; use explicit start/end targets`);
365
+ continue;
366
+ }
367
+ let ranges = boundedSnapshots.get(file);
368
+ if (!ranges) {
369
+ const requested = options.targets.filter(item => !item.symbol && item.end !== undefined
370
+ && Number.isInteger(item.start ?? 1) && (item.start ?? 1) > 0
371
+ && Number.isInteger(item.end) && item.end >= (item.start ?? 1)
372
+ && resolveProjectPath(options.rootDir, item.file, "context target") === fullPath);
373
+ ranges = await readBoundedRanges(fullPath, requested.map(item => ({ start: item.start ?? 1, end: item.end })), limit);
374
+ boundedSnapshots.set(file, ranges);
375
+ }
376
+ const result = ranges.get(`${target.start ?? 1}:${target.end}`);
377
+ if ("omitted" in result) {
378
+ omitted.push(`${file}:${/^\d/.test(result.omitted) ? "" : " "}${result.omitted}`);
379
+ continue;
380
+ }
381
+ const excerpt = { file, ...result };
382
+ if (candidates.some(item => item.file === file && item.start <= excerpt.start && item.end >= excerpt.end))
383
+ continue;
384
+ const cost = bytes(render(excerpt)) + 2;
385
+ if (cost > available) {
386
+ omitted.push(`${file}:${excerpt.start}-${excerpt.end}: over budget; request narrower targets or raise max_tokens`);
387
+ continue;
388
+ }
389
+ candidates.push(excerpt);
390
+ available -= cost;
391
+ continue;
392
+ }
393
+ source = await readFile(fullPath, "utf8");
394
+ if (bytes(source) > 2_000_000) {
395
+ omitted.push(`${file}: exceeds 2 MB read limit`);
396
+ continue;
397
+ }
398
+ if (source.includes("\0")) {
399
+ omitted.push(`${file}: binary file`);
400
+ continue;
401
+ }
402
+ snapshots.set(file, source);
403
+ }
404
+ }
405
+ catch {
406
+ omitted.push(`${file}: unreadable`);
407
+ continue;
408
+ }
409
+ const lines = source.split("\n");
410
+ if (!options.targets?.length && /\.(?:md|mdx|rst|txt)$/i.test(file)
411
+ && discoveryPathWeight(query, file, false, lines.filter(line => /^#{1,6}\s/.test(line)).join("\n")) === 0)
412
+ continue;
413
+ let start = target.start ?? 1, end = Math.min(target.end ?? lines.length, lines.length);
414
+ const analyze = async () => {
415
+ let value = analyses.get(file);
416
+ if (!value) {
417
+ value = await analyzeContent(fullPath, source);
418
+ analyses.set(file, value);
419
+ }
420
+ return value;
421
+ };
422
+ const analysis = target.symbol ? await analyze() : undefined;
423
+ if (target.symbol) {
424
+ const matches = flattenSymbols(analysis.symbols).filter(item => item.name === target.symbol);
425
+ if (matches.length !== 1) {
426
+ omitted.push(`${file}: ${target.symbol} ${matches.length ? "ambiguous; use line range" : "not found"}`);
427
+ continue;
428
+ }
429
+ start = matches[0].line;
430
+ end = matches[0].endLine;
431
+ }
432
+ if (start > lines.length) {
433
+ omitted.push(`${file}:${start}: beyond end of file`);
434
+ continue;
435
+ }
436
+ const key = `${file}:${start}-${end}`;
437
+ if (seen.has(key))
438
+ continue;
439
+ seen.add(key);
440
+ const excerpt = { file, start, end, text: lines.slice(start - 1, end).join("\n") };
441
+ const discovery = !options.targets?.length;
442
+ if (adaptive && documentFiles.has(file)) {
443
+ // Operational notes often use long paragraphs or table rows. Rank the
444
+ // actual matching line before generic heading previews and large windows.
445
+ const hits = lines.map((text, index) => ({ part: { file, start: index + 1, end: index + 1, text },
446
+ score: [...new Set(searchTerms(text))].filter(term => terms.has(term)).length }))
447
+ .filter(item => item.score >= Math.min(2, terms.size) && bytes(render(item.part)) <= Math.min(1600, available))
448
+ .sort((a, b) => b.score - a.score || a.part.start - b.part.start);
449
+ documentPassages.push(...hits.slice(0, 2).map(item => item.part));
450
+ }
451
+ if (target.symbol && !target.helper && end - start < 10 && bytes(excerpt.text) < 600) {
452
+ const executable = maskSourceText(excerpt.text, file);
453
+ const calls = new Set([...executable.matchAll(/(?<![.\w$])([A-Za-z_$][\w$]*)\s*\(/g)].map(match => match[1]));
454
+ const local = analysis.symbols.filter(symbol => symbol.name !== target.symbol && symbol.kind === "function" && calls.has(symbol.name));
455
+ for (const helper of local.slice(0, 2)) {
456
+ const name = helper.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
457
+ const shadowed = new RegExp(`(?:\\b(?:const|let|var|function)\\s+${name}\\b|[,(]\\s*${name}\\s*[:=,)])`).test(executable);
458
+ if (shadowed || local.filter(item => item.name === helper.name).length !== 1)
459
+ continue;
460
+ if (!targets.some(item => item.file === file && item.symbol === helper.name))
461
+ targets.push({ file, symbol: helper.name, helper: true });
462
+ const note = "Includes possible same-file helpers for short forwarding functions; verify call bindings from source.";
463
+ if (!notices.includes(note))
464
+ notices.push(note);
465
+ }
466
+ }
467
+ // Fully covered overlapping targets do not consume the budget twice.
468
+ if (candidates.some(item => item.file === file && item.start <= start && item.end >= end))
469
+ continue;
470
+ if (!discovery && bytes(render(excerpt)) + 2 <= available) {
471
+ candidates.push(excerpt);
472
+ available -= bytes(render(excerpt)) + 2;
473
+ continue;
474
+ }
475
+ if (target.symbol || target.start !== undefined || target.end !== undefined || !query) {
476
+ omitted.push(`${key}: over budget; request narrower targets or raise max_tokens`);
477
+ continue;
478
+ }
479
+ if ((readRequestedFiles || lines.length <= 40) && requestedFiles.has(file) && bytes(render(excerpt)) + 2 <= available) {
480
+ priorityParts.push(excerpt);
481
+ }
482
+ else if (overviewFiles.has(file) || documentFiles.has(file)) {
483
+ // Bound each preview in bytes, not lines: one Markdown table row can be
484
+ // larger than an entire packet. Leave room for the other requested roles.
485
+ const share = Math.max(128, Math.min(1200, Math.floor(available * .4 / Math.max(1, overviewFiles.size + documentFiles.size))));
486
+ // A long path or localized first line can exceed a small divided share.
487
+ // Admit one line up to 512 bytes including its citation; global packing
488
+ // still enforces the response budget and oversized rows remain bounded.
489
+ const firstLineCost = bytes(render({ file, start: 1, end: 1, text: lines[0] })) + 2;
490
+ const cap = Math.max(share, Math.min(512, available, firstLineCost));
491
+ const preview = { file, start: 1, end: 0, text: "" };
492
+ for (let line = 0; line < Math.min(25, lines.length); line++) {
493
+ const next = { ...preview, end: line + 1, text: lines.slice(0, line + 1).join("\n") };
494
+ if (bytes(render(next)) + 2 > cap)
495
+ break;
496
+ Object.assign(preview, next);
497
+ }
498
+ if (preview.end)
499
+ priorityParts.push(preview);
500
+ }
501
+ const topic = topicTerms.get(file);
502
+ if (topic) {
503
+ const cap = Math.min(800, Math.max(128, Math.floor(limit * .25 / topicTerms.size)));
504
+ const windows = lines.flatMap((line, index) => {
505
+ // Anchor to an actual hit so a tied window cannot put the matching
506
+ // declaration at its tail and discard the following checks.
507
+ if (!searchTerms(line).some(term => topic.includes(term)))
508
+ return [];
509
+ const from = Math.max(0, index - 2);
510
+ let to = from, text = "";
511
+ while (to < Math.min(lines.length, index + 9)) {
512
+ const next = lines.slice(from, to + 1).join("\n");
513
+ if (bytes(`${file}:${from + 1}-${to + 1}\n${next}`) + 2 > cap)
514
+ break;
515
+ text = next;
516
+ to++;
517
+ }
518
+ const words = searchTerms(text);
519
+ // Break coverage ties using bounded term frequency: a body using a
520
+ // topic repeatedly is stronger evidence than its first setup mention.
521
+ const score = topic.reduce((score, term) => {
522
+ const count = words.filter(word => word === term).length;
523
+ return score + (count ? 1 + Math.min(2, count - 1) * .1 : 0);
524
+ }, 0);
525
+ return [{ part: { file, start: from + 1, end: to, text }, score }];
526
+ }).filter(item => item.part.end >= item.part.start && item.score > 0);
527
+ windows.sort((a, b) => b.score - a.score || a.part.start - b.part.start);
528
+ if (windows[0])
529
+ (scopedFiles.has(file) ? scopedParts : priorityParts).push(windows[0].part);
530
+ }
531
+ // File ranking can now find literal text anywhere in full-source FTS.
532
+ // Keep the actual matching lines, not just other terms from the same file.
533
+ for (const literal of literals.filter(Boolean)) {
534
+ const matches = [];
535
+ for (let line = 0; line < lines.length; line++) {
536
+ if (!lines[line].normalize("NFC").toLowerCase().includes(literal))
537
+ continue;
538
+ const from = Math.max(0, line - 2), to = Math.min(lines.length, line + 4);
539
+ const part = { file, start: from + 1, end: to, text: lines.slice(from, to).join("\n") };
540
+ const words = new Set(searchTerms(part.text));
541
+ matches.push({ part, score: [...terms].filter(term => words.has(term)).length });
542
+ }
543
+ matches.sort((a, b) => b.score - a.score || a.part.start - b.part.start);
544
+ const parts = /^[A-Za-z_$][\w$]*$/.test(literal) ? identifierParts : literalParts;
545
+ parts.push(...matches.slice(0, 3).map(match => match.part));
546
+ if (matches.length > 3)
547
+ omitted.push(`${file}: additional literal matches omitted`);
548
+ }
549
+ const syntax = /\.[cm]?[jt]sx?$/i.test(file) ? ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true) : undefined;
550
+ const localNames = new Set(namedDefinitions.get(file));
551
+ // A type inferred from a local runtime schema needs that schema as evidence.
552
+ for (const statement of syntax?.statements ?? [])
553
+ if (ts.isTypeAliasDeclaration(statement) && localNames.has(statement.name.text)) {
554
+ const visit = (node) => {
555
+ if (ts.isTypeQueryNode(node) && ts.isIdentifier(node.exprName))
556
+ localNames.add(node.exprName.text);
557
+ ts.forEachChild(node, visit);
558
+ };
559
+ visit(statement.type);
560
+ }
561
+ if (localNames.size) {
562
+ for (const symbol of flattenSymbols((await analyze()).symbols)) {
563
+ if (!localNames.has(symbol.name))
564
+ continue;
565
+ const definition = { file, start: symbol.line, end: symbol.endLine, text: lines.slice(symbol.line - 1, symbol.endLine).join("\n") };
566
+ // Keep complete bodies when possible. Large bodies retain their entry
567
+ // point and can contribute further query-relevant interior windows.
568
+ if (bytes(render(definition)) + 2 > available) {
569
+ definition.end = Math.min(symbol.endLine, symbol.line + 18);
570
+ definition.text = lines.slice(symbol.line - 1, definition.end).join("\n");
571
+ }
572
+ definitions.push(definition);
573
+ }
574
+ }
575
+ // Gather all files before packing so a low-value header/dependency cannot
576
+ // consume the budget ahead of a decisive range in another file.
577
+ // Compiler statements keep locals inside anonymous test/router callbacks
578
+ // from masquerading as complete top-level declarations.
579
+ const spans = syntax ? syntax.statements.filter(s => !ts.isImportDeclaration(s)).map(s => ({
580
+ line: syntax.getLineAndCharacterOfPosition(s.getStart(syntax)).line + 1,
581
+ endLine: syntax.getLineAndCharacterOfPosition(s.end).line + 1,
582
+ })) : (await analyze()).symbols.filter(s => s.line >= 1 && s.endLine <= lines.length);
583
+ const add = (from, to) => discoveryParts.push({ file, start: from, end: to, text: lines.slice(from - 1, to).join("\n") });
584
+ // A connected helper may not repeat any prose query terms. Its symbol or
585
+ // call anchors supply real source evidence without treating a file header
586
+ // as the implementation. Other windows still compete on query coverage.
587
+ const connectedCandidate = connectedCandidates.find(item => item.path === file);
588
+ for (const line of connectedCandidate?.anchors ?? [])
589
+ add(Math.max(1, line - 2), Math.min(lines.length, line + 16));
590
+ if (connectedCandidate && (connectedCandidate.roles.some(role => role !== "implementation") || (connectedCandidate.semanticScore ?? 0) > 40)) {
591
+ for (const span of spans.slice(0, 4))
592
+ add(span.line, Math.min(span.endLine, span.line + 20));
593
+ }
594
+ if (lines.length <= 40 && bytes(render(excerpt)) <= available) {
595
+ add(1, lines.length);
596
+ if (target.helper)
597
+ smallDependencies.push(excerpt);
598
+ }
599
+ else {
600
+ for (const symbol of spans)
601
+ if (symbol.endLine - symbol.line < 60)
602
+ add(symbol.line, symbol.endLine);
603
+ // Anchor windows to query hits instead of arbitrary page boundaries.
604
+ // Extra trailing context retains checks following a relevant comment.
605
+ let spanIndex = 0, previousStart = -1;
606
+ for (let line = 1; line <= lines.length; line++) {
607
+ while (spanIndex < spans.length && spans[spanIndex].endLine < line)
608
+ spanIndex++;
609
+ const whole = spans[spanIndex];
610
+ if (whole && whole.line <= line && whole.endLine - whole.line < 60)
611
+ continue;
612
+ if (!searchTerms(lines[line - 1]).some(term => terms.has(term)))
613
+ continue;
614
+ const start = Math.max(1, line - 6), end = Math.min(lines.length, line + 18);
615
+ if (start !== previousStart)
616
+ add(start, end);
617
+ previousStart = start;
618
+ }
619
+ }
620
+ omitted.push(`${file}: partial; unreturned lines omitted`);
621
+ }
622
+ const sourceFirst = namedSymbols.size > 0 && (mixedDocuments || !wantsDocuments(query));
623
+ const documentLimit = mixedDocuments || sourceFirst || isUiOrientationQuery(query) ? Math.floor(limit * .3) : limit;
624
+ // Keep quoted/compound keys ahead of generic literals, then prefer windows
625
+ // covering the rest of the question over the first incidental occurrence.
626
+ const literalRank = (part) => literals.findIndex(literal => part.text.normalize("NFC").toLowerCase().includes(literal));
627
+ const coverage = (part) => [...new Set(searchTerms(part.text))].filter(term => terms.has(term)).length;
628
+ documentPassages.sort((a, b) => coverage(b) * discoveryPathWeight(query, b.file) - coverage(a) * discoveryPathWeight(query, a.file) || Number(requestedFiles.has(b.file)) - Number(requestedFiles.has(a.file)) || a.file.localeCompare(b.file));
629
+ literalParts.sort((a, b) => literalRank(a) - literalRank(b) || coverage(b) - coverage(a));
630
+ identifierParts.sort((a, b) => Number(coverageFiles.has(b.file)) - Number(coverageFiles.has(a.file)) || coverage(b) - coverage(a));
631
+ let documentBytes = 0;
632
+ // Topic/search/graph candidates need the same allowance as heading previews.
633
+ const isDocument = (part) => (documentFiles.has(part.file) || /\.(?:md|mdx|rst|txt)$/i.test(part.file))
634
+ && !requestedFiles.has(part.file);
635
+ const deferDocuments = changeContext && !wantsDocuments(query);
636
+ const deferredDocuments = [];
637
+ // Preserve the existing exact-name priority before adding scoped usage windows.
638
+ const priority = sourceFirst ? [...definitions, ...documentPassages, ...priorityParts] : [...documentPassages, ...priorityParts, ...definitions];
639
+ for (const definition of [...priority, ...literalParts, ...identifierParts, ...scopedParts]) {
640
+ // Keep incidental journal prose behind implementation even without a named
641
+ // symbol. Explicit documentation questions and file reads retain priority.
642
+ if (deferDocuments && isDocument(definition)) {
643
+ deferredDocuments.push(definition);
644
+ continue;
645
+ }
646
+ for (const part of uncoveredRanges(definition, candidates)) {
647
+ const size = bytes(render(part)) + 2;
648
+ if (size <= available && (!isDocument(part) || documentBytes + size <= documentLimit)) {
649
+ candidates.push(part);
650
+ available -= size;
651
+ if (isDocument(part))
652
+ documentBytes += size;
653
+ }
654
+ }
655
+ }
656
+ // IDF across windows downweights ubiquitous identifiers; marginal coverage
657
+ // reduces repetitive snippets while keeping some evidence for repeated terms.
658
+ const remaining = [...deferredDocuments, ...discoveryParts].map(part => ({ part, words: new Set(searchTerms(part.text)), size: bytes(render(part)) + 2 }));
659
+ // Deferred previews duplicate document windows; they must not change IDF and
660
+ // accidentally elevate tiny incidental source matches over implementations.
661
+ const weightingWindows = discoveryParts.map(part => new Set(searchTerms(part.text)));
662
+ const weights = new Map([...terms].map(term => [term, 1 + Math.log(1 + weightingWindows.length / (1 + weightingWindows.filter(words => words.has(term)).length))]));
663
+ const covered = new Set(candidates.flatMap(part => searchTerms(part.text)));
664
+ const selectedBytes = new Map();
665
+ const coveredRoles = new Set();
666
+ const roles = (part) => [...new Set([
667
+ ...connectedCandidates.find(item => item.path === part.file)?.roles ?? [],
668
+ ...(isTestEvidence(part.file) ? ["test"] : []),
669
+ ...(/(?:^|\/)(?:[^/]*config[^/]*|package\.json|pyproject\.toml)$|\.(?:ya?ml|toml)$/i.test(part.file) ? ["configuration"] : []),
670
+ ...(/(?:^|\/)(?:types|schemas?|contracts?)(?:[./-]|$)/i.test(part.file) ? ["contract"] : []),
671
+ ])];
672
+ for (const part of candidates)
673
+ for (const role of roles(part))
674
+ coveredRoles.add(role);
675
+ for (const part of candidates)
676
+ selectedBytes.set(part.file, (selectedBytes.get(part.file) ?? 0) + bytes(render(part)));
677
+ while (remaining.length && available > 0) {
678
+ let best = -1, bestScore = 0;
679
+ let bestParts = [], bestSize = 0;
680
+ for (let i = 0; i < remaining.length; i++) {
681
+ const { part } = remaining[i];
682
+ const parts = uncoveredRanges(part, candidates);
683
+ const size = parts.reduce((sum, item) => sum + bytes(render(item)) + 2, 0);
684
+ if (!parts.length || size > available || (isDocument(part) && documentBytes + size > documentLimit))
685
+ continue;
686
+ const unchanged = parts.length === 1 && parts[0].start === part.start && parts[0].end === part.end;
687
+ const words = unchanged ? remaining[i].words : new Set(parts.flatMap(item => searchTerms(item.text)));
688
+ let gain = [...terms].reduce((n, term) => n + (words.has(term) ? weights.get(term) * (covered.has(term) ? .25 : 1) : 0), 0);
689
+ const linkedRoles = roles(part).filter(role => role !== "implementation");
690
+ if (linkedRoles.some(role => !coveredRoles.has(role)))
691
+ gain += 5;
692
+ // Reserve an opportunity for a facet's implementation even when another
693
+ // file has already spent the query-term novelty bonus on incidental prose.
694
+ const candidate = connectedCandidates.find(item => item.path === part.file);
695
+ if (!selectedBytes.has(part.file) && candidate?.reason.split("; ").some(reason => reason.startsWith("facet:") && words.has(reason.slice(6))))
696
+ gain += 3;
697
+ if (!selectedBytes.has(part.file) && gain > 0 && candidate?.reason.split("; ").includes("path topics"))
698
+ gain += 3;
699
+ if (!selectedBytes.has(part.file) && candidate?.reason.split("; ").some(reason => reason.startsWith("graph: imported by ") && selectedBytes.has(reason.slice("graph: imported by ".length))))
700
+ gain += 3;
701
+ // Strong semantic-only implementations need a first excerpt even when
702
+ // the user's paraphrase has no literal overlap with the source.
703
+ if (!selectedBytes.has(part.file) && (candidate?.semanticScore ?? 0) > 40)
704
+ gain += 2;
705
+ // Retain control-flow predicates with their surrounding source; this is a
706
+ // packing hint, not a claim that a particular predicate is a constraint.
707
+ if (connectedCandidates.length && gain > 0 && parts.some(item => /\b(?:if|throw|assert|reject)\b/.test(item.text)))
708
+ gain *= 1.15;
709
+ // Keep the retriever's ordering as a prior; otherwise a rare incidental
710
+ // word in a lower-ranked UI/test file can displace implementation evidence.
711
+ // A large first-ranked file must not monopolize a cross-module question.
712
+ // This is a soft diversity prior; strong evidence can still reuse a file.
713
+ const repetition = 1 + (selectedBytes.get(part.file) ?? 0) / Math.max(512, limit / 4);
714
+ const score = gain * (filePriors.get(part.file) ?? .5) / (Math.sqrt(size) * repetition);
715
+ const sourcePriority = deferDocuments && best >= 0 && isDocument(remaining[best].part) !== isDocument(part);
716
+ if (score > 0 && (sourcePriority ? !isDocument(part) : score > bestScore)) {
717
+ best = i;
718
+ bestScore = score;
719
+ bestParts = parts;
720
+ bestSize = size;
721
+ }
722
+ }
723
+ if (best < 0)
724
+ break;
725
+ const next = remaining.splice(best, 1)[0];
726
+ candidates.push(...bestParts);
727
+ available -= bestSize;
728
+ if (isDocument(next.part))
729
+ documentBytes += bestSize;
730
+ selectedBytes.set(next.part.file, (selectedBytes.get(next.part.file) ?? 0) + bestSize);
731
+ for (const part of bestParts)
732
+ for (const term of searchTerms(part.text))
733
+ covered.add(term);
734
+ for (const role of roles(next.part))
735
+ coveredRoles.add(role);
736
+ }
737
+ for (const part of smallDependencies) {
738
+ const cost = bytes(render(part)) + 2;
739
+ if (cost <= available && (!isDocument(part) || documentBytes + cost <= documentLimit)
740
+ && !candidates.some(item => item.file === part.file)) {
741
+ candidates.push(part);
742
+ available -= cost;
743
+ if (isDocument(part))
744
+ documentBytes += cost;
745
+ }
746
+ }
747
+ // Coalesce overlapping/adjacent ranges using the original snapshot. This also
748
+ // makes a function included through multiple targets appear only once.
749
+ const merged = [];
750
+ for (const file of new Set(candidates.map(item => item.file))) {
751
+ for (const part of candidates.filter(item => item.file === file).sort((a, b) => a.start - b.start)) {
752
+ const last = merged.at(-1);
753
+ if (last?.file === file && part.start <= last.end + 1) {
754
+ const uncovered = part.text.split("\n").slice(Math.max(0, last.end - part.start + 1));
755
+ if (uncovered.length)
756
+ last.text += "\n" + uncovered.join("\n");
757
+ last.end = Math.max(last.end, part.end);
758
+ }
759
+ else
760
+ merged.push({ ...part });
761
+ }
762
+ }
763
+ const noticeText = notices.join("\n");
764
+ const partialOmissions = omitted.filter(value => !merged.some(part => value === `${part.file}: partial; unreturned lines omitted`
765
+ && part.start === 1 && part.end === snapshots.get(part.file).split("\n").length));
766
+ const info = [...notices, ...partialOmissions.map(value => `Omitted: ${value}`)].join("\n");
767
+ let footer = bytes(info) <= 650 ? info : [
768
+ bytes(noticeText) <= 400 ? noticeText : [
769
+ ...notices.filter(notice => notice.startsWith("Discovery used") || notice.startsWith("Discovery index:") || /stale|empty|refresh/i.test(notice)).slice(0, 2),
770
+ "Additional discovery diagnostics omitted; narrow query/targets for detail.",
771
+ ].join("\n"),
772
+ "Some targets or lines omitted. Narrow query/targets or increase max_tokens; verify scope with native search.",
773
+ ].filter(Boolean).join("\n");
774
+ // The header and footer share the existing diagnostic allowance. Moving a
775
+ // warning to the top must not evict source that fit the old packet budget.
776
+ const footerLimit = 650 - bytes(discoveryNotice ?? "") - 2;
777
+ if (bytes(footer) > footerLimit) {
778
+ const omission = "Some targets, lines or diagnostics omitted. Narrow query/targets or increase max_tokens; verify scope with native search.";
779
+ const kept = [];
780
+ for (const notice of notices) {
781
+ if (bytes([...kept, notice, omission].join("\n")) <= footerLimit)
782
+ kept.push(notice);
783
+ }
784
+ footer = [...kept, omission].join("\n");
785
+ }
786
+ if (connectedCandidates.length) {
787
+ const missing = ["implementation", "caller", "dependency", "test"].filter(role => !merged.some(part => roles(part).includes(role)));
788
+ if (missing.length)
789
+ notices.push(`Evidence not returned for: ${missing.join(", ")}; this is not proof of absence.`);
790
+ }
791
+ const summary = notices.at(-1)?.startsWith("Evidence not returned") ? notices.at(-1) : undefined;
792
+ const coverageNotice = adaptive ? `Retrieval: ${intent}; ${rolesClassified ? "hybrid + structural relations" : "exact/lexical + configured semantic"}. Relations are structural/textual, not compiler-resolved.\nEvidence: ${["implementation", "caller", "dependency", "contract", "configuration", "test"].map(role => `${role}=${merged.some(part => roles(part).includes(role)) ? "returned" : targets.some(target => roles(target).includes(role)) ? "omitted" : rolesClassified ? "unavailable" : "unclassified"}`).join(", ")}. Unclassified roles were not evaluated. Unavailable is not proof of absence.` : undefined;
793
+ const text = [discoveryNotice, "Source evidence (only the indicated ranges are included):", coverageNotice, ...merged.map(render), riskNotice, footer, summary,
794
+ !merged.length ? "No source fit or matched. Try exact file/line targets or native search." : ""].filter(Boolean).join("\n\n");
795
+ return { text, ranges: merged, hashes: Object.fromEntries([...snapshots].map(([file, source]) => [file, createHash("sha256").update(source).digest("hex")])),
796
+ notices: [discoveryNotice, coverageNotice, riskNotice, footer, summary].filter((value) => !!value), complete: true,
797
+ settings: JSON.stringify({ version: 57, adaptive, intent, connected, risks, preset: options.retrievalPreset ?? "balanced", rerank: process.env.CONTEXTPRO_CHAT_RERANK === "true",
798
+ embedding: `${getProvider()}:${getEmbedModelName()}`, chat: options.targets?.length ? "unused" : await chatModelIdentity(options.rootDir) }) };
799
+ }
800
+ //# sourceMappingURL=context.js.map