@colbymchenry/codegraph 0.8.0 → 0.9.1

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 (381) hide show
  1. package/README.md +23 -7
  2. package/npm-shim.js +43 -0
  3. package/package.json +13 -51
  4. package/LICENSE +0 -21
  5. package/dist/bin/codegraph.d.ts +0 -21
  6. package/dist/bin/codegraph.d.ts.map +0 -1
  7. package/dist/bin/codegraph.js +0 -1257
  8. package/dist/bin/codegraph.js.map +0 -1
  9. package/dist/bin/node-version-check.d.ts +0 -20
  10. package/dist/bin/node-version-check.d.ts.map +0 -1
  11. package/dist/bin/node-version-check.js +0 -42
  12. package/dist/bin/node-version-check.js.map +0 -1
  13. package/dist/bin/uninstall.d.ts +0 -14
  14. package/dist/bin/uninstall.d.ts.map +0 -1
  15. package/dist/bin/uninstall.js +0 -36
  16. package/dist/bin/uninstall.js.map +0 -1
  17. package/dist/config.d.ts +0 -51
  18. package/dist/config.d.ts.map +0 -1
  19. package/dist/config.js +0 -321
  20. package/dist/config.js.map +0 -1
  21. package/dist/context/formatter.d.ts +0 -30
  22. package/dist/context/formatter.d.ts.map +0 -1
  23. package/dist/context/formatter.js +0 -244
  24. package/dist/context/formatter.js.map +0 -1
  25. package/dist/context/index.d.ts +0 -97
  26. package/dist/context/index.d.ts.map +0 -1
  27. package/dist/context/index.js +0 -1050
  28. package/dist/context/index.js.map +0 -1
  29. package/dist/db/index.d.ts +0 -72
  30. package/dist/db/index.d.ts.map +0 -1
  31. package/dist/db/index.js +0 -200
  32. package/dist/db/index.js.map +0 -1
  33. package/dist/db/migrations.d.ts +0 -44
  34. package/dist/db/migrations.d.ts.map +0 -1
  35. package/dist/db/migrations.js +0 -131
  36. package/dist/db/migrations.js.map +0 -1
  37. package/dist/db/queries.d.ts +0 -253
  38. package/dist/db/queries.d.ts.map +0 -1
  39. package/dist/db/queries.js +0 -1207
  40. package/dist/db/queries.js.map +0 -1
  41. package/dist/db/schema.sql +0 -151
  42. package/dist/db/sqlite-adapter.d.ts +0 -52
  43. package/dist/db/sqlite-adapter.d.ts.map +0 -1
  44. package/dist/db/sqlite-adapter.js +0 -237
  45. package/dist/db/sqlite-adapter.js.map +0 -1
  46. package/dist/directory.d.ts +0 -57
  47. package/dist/directory.d.ts.map +0 -1
  48. package/dist/directory.js +0 -264
  49. package/dist/directory.js.map +0 -1
  50. package/dist/errors.d.ts +0 -136
  51. package/dist/errors.d.ts.map +0 -1
  52. package/dist/errors.js +0 -219
  53. package/dist/errors.js.map +0 -1
  54. package/dist/extraction/dfm-extractor.d.ts +0 -31
  55. package/dist/extraction/dfm-extractor.d.ts.map +0 -1
  56. package/dist/extraction/dfm-extractor.js +0 -151
  57. package/dist/extraction/dfm-extractor.js.map +0 -1
  58. package/dist/extraction/grammars.d.ts +0 -78
  59. package/dist/extraction/grammars.d.ts.map +0 -1
  60. package/dist/extraction/grammars.js +0 -322
  61. package/dist/extraction/grammars.js.map +0 -1
  62. package/dist/extraction/index.d.ts +0 -130
  63. package/dist/extraction/index.d.ts.map +0 -1
  64. package/dist/extraction/index.js +0 -1305
  65. package/dist/extraction/index.js.map +0 -1
  66. package/dist/extraction/languages/c-cpp.d.ts +0 -4
  67. package/dist/extraction/languages/c-cpp.d.ts.map +0 -1
  68. package/dist/extraction/languages/c-cpp.js +0 -126
  69. package/dist/extraction/languages/c-cpp.js.map +0 -1
  70. package/dist/extraction/languages/csharp.d.ts +0 -3
  71. package/dist/extraction/languages/csharp.d.ts.map +0 -1
  72. package/dist/extraction/languages/csharp.js +0 -72
  73. package/dist/extraction/languages/csharp.js.map +0 -1
  74. package/dist/extraction/languages/dart.d.ts +0 -3
  75. package/dist/extraction/languages/dart.d.ts.map +0 -1
  76. package/dist/extraction/languages/dart.js +0 -192
  77. package/dist/extraction/languages/dart.js.map +0 -1
  78. package/dist/extraction/languages/go.d.ts +0 -3
  79. package/dist/extraction/languages/go.d.ts.map +0 -1
  80. package/dist/extraction/languages/go.js +0 -58
  81. package/dist/extraction/languages/go.js.map +0 -1
  82. package/dist/extraction/languages/index.d.ts +0 -10
  83. package/dist/extraction/languages/index.d.ts.map +0 -1
  84. package/dist/extraction/languages/index.js +0 -45
  85. package/dist/extraction/languages/index.js.map +0 -1
  86. package/dist/extraction/languages/java.d.ts +0 -3
  87. package/dist/extraction/languages/java.d.ts.map +0 -1
  88. package/dist/extraction/languages/java.js +0 -64
  89. package/dist/extraction/languages/java.js.map +0 -1
  90. package/dist/extraction/languages/javascript.d.ts +0 -3
  91. package/dist/extraction/languages/javascript.d.ts.map +0 -1
  92. package/dist/extraction/languages/javascript.js +0 -90
  93. package/dist/extraction/languages/javascript.js.map +0 -1
  94. package/dist/extraction/languages/kotlin.d.ts +0 -3
  95. package/dist/extraction/languages/kotlin.d.ts.map +0 -1
  96. package/dist/extraction/languages/kotlin.js +0 -253
  97. package/dist/extraction/languages/kotlin.js.map +0 -1
  98. package/dist/extraction/languages/pascal.d.ts +0 -3
  99. package/dist/extraction/languages/pascal.d.ts.map +0 -1
  100. package/dist/extraction/languages/pascal.js +0 -66
  101. package/dist/extraction/languages/pascal.js.map +0 -1
  102. package/dist/extraction/languages/php.d.ts +0 -3
  103. package/dist/extraction/languages/php.d.ts.map +0 -1
  104. package/dist/extraction/languages/php.js +0 -107
  105. package/dist/extraction/languages/php.js.map +0 -1
  106. package/dist/extraction/languages/python.d.ts +0 -3
  107. package/dist/extraction/languages/python.d.ts.map +0 -1
  108. package/dist/extraction/languages/python.js +0 -56
  109. package/dist/extraction/languages/python.js.map +0 -1
  110. package/dist/extraction/languages/ruby.d.ts +0 -3
  111. package/dist/extraction/languages/ruby.d.ts.map +0 -1
  112. package/dist/extraction/languages/ruby.js +0 -114
  113. package/dist/extraction/languages/ruby.js.map +0 -1
  114. package/dist/extraction/languages/rust.d.ts +0 -3
  115. package/dist/extraction/languages/rust.d.ts.map +0 -1
  116. package/dist/extraction/languages/rust.js +0 -109
  117. package/dist/extraction/languages/rust.js.map +0 -1
  118. package/dist/extraction/languages/scala.d.ts +0 -3
  119. package/dist/extraction/languages/scala.d.ts.map +0 -1
  120. package/dist/extraction/languages/scala.js +0 -139
  121. package/dist/extraction/languages/scala.js.map +0 -1
  122. package/dist/extraction/languages/swift.d.ts +0 -3
  123. package/dist/extraction/languages/swift.d.ts.map +0 -1
  124. package/dist/extraction/languages/swift.js +0 -91
  125. package/dist/extraction/languages/swift.js.map +0 -1
  126. package/dist/extraction/languages/typescript.d.ts +0 -3
  127. package/dist/extraction/languages/typescript.d.ts.map +0 -1
  128. package/dist/extraction/languages/typescript.js +0 -129
  129. package/dist/extraction/languages/typescript.js.map +0 -1
  130. package/dist/extraction/liquid-extractor.d.ts +0 -52
  131. package/dist/extraction/liquid-extractor.d.ts.map +0 -1
  132. package/dist/extraction/liquid-extractor.js +0 -313
  133. package/dist/extraction/liquid-extractor.js.map +0 -1
  134. package/dist/extraction/parse-worker.d.ts +0 -8
  135. package/dist/extraction/parse-worker.d.ts.map +0 -1
  136. package/dist/extraction/parse-worker.js +0 -94
  137. package/dist/extraction/parse-worker.js.map +0 -1
  138. package/dist/extraction/svelte-extractor.d.ts +0 -56
  139. package/dist/extraction/svelte-extractor.d.ts.map +0 -1
  140. package/dist/extraction/svelte-extractor.js +0 -272
  141. package/dist/extraction/svelte-extractor.js.map +0 -1
  142. package/dist/extraction/tree-sitter-helpers.d.ts +0 -28
  143. package/dist/extraction/tree-sitter-helpers.d.ts.map +0 -1
  144. package/dist/extraction/tree-sitter-helpers.js +0 -103
  145. package/dist/extraction/tree-sitter-helpers.js.map +0 -1
  146. package/dist/extraction/tree-sitter-types.d.ts +0 -179
  147. package/dist/extraction/tree-sitter-types.d.ts.map +0 -1
  148. package/dist/extraction/tree-sitter-types.js +0 -10
  149. package/dist/extraction/tree-sitter-types.js.map +0 -1
  150. package/dist/extraction/tree-sitter.d.ts +0 -233
  151. package/dist/extraction/tree-sitter.d.ts.map +0 -1
  152. package/dist/extraction/tree-sitter.js +0 -2393
  153. package/dist/extraction/tree-sitter.js.map +0 -1
  154. package/dist/extraction/vue-extractor.d.ts +0 -36
  155. package/dist/extraction/vue-extractor.d.ts.map +0 -1
  156. package/dist/extraction/vue-extractor.js +0 -163
  157. package/dist/extraction/vue-extractor.js.map +0 -1
  158. package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  159. package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
  160. package/dist/graph/index.d.ts +0 -8
  161. package/dist/graph/index.d.ts.map +0 -1
  162. package/dist/graph/index.js +0 -13
  163. package/dist/graph/index.js.map +0 -1
  164. package/dist/graph/queries.d.ts +0 -106
  165. package/dist/graph/queries.d.ts.map +0 -1
  166. package/dist/graph/queries.js +0 -366
  167. package/dist/graph/queries.js.map +0 -1
  168. package/dist/graph/traversal.d.ts +0 -127
  169. package/dist/graph/traversal.d.ts.map +0 -1
  170. package/dist/graph/traversal.js +0 -493
  171. package/dist/graph/traversal.js.map +0 -1
  172. package/dist/index.d.ts +0 -447
  173. package/dist/index.d.ts.map +0 -1
  174. package/dist/index.js +0 -825
  175. package/dist/index.js.map +0 -1
  176. package/dist/installer/claude-md-template.d.ts +0 -14
  177. package/dist/installer/claude-md-template.d.ts.map +0 -1
  178. package/dist/installer/claude-md-template.js +0 -21
  179. package/dist/installer/claude-md-template.js.map +0 -1
  180. package/dist/installer/config-writer.d.ts +0 -29
  181. package/dist/installer/config-writer.d.ts.map +0 -1
  182. package/dist/installer/config-writer.js +0 -111
  183. package/dist/installer/config-writer.js.map +0 -1
  184. package/dist/installer/index.d.ts +0 -65
  185. package/dist/installer/index.d.ts.map +0 -1
  186. package/dist/installer/index.js +0 -406
  187. package/dist/installer/index.js.map +0 -1
  188. package/dist/installer/instructions-template.d.ts +0 -28
  189. package/dist/installer/instructions-template.d.ts.map +0 -1
  190. package/dist/installer/instructions-template.js +0 -64
  191. package/dist/installer/instructions-template.js.map +0 -1
  192. package/dist/installer/targets/claude.d.ts +0 -31
  193. package/dist/installer/targets/claude.d.ts.map +0 -1
  194. package/dist/installer/targets/claude.js +0 -308
  195. package/dist/installer/targets/claude.js.map +0 -1
  196. package/dist/installer/targets/codex.d.ts +0 -18
  197. package/dist/installer/targets/codex.d.ts.map +0 -1
  198. package/dist/installer/targets/codex.js +0 -185
  199. package/dist/installer/targets/codex.js.map +0 -1
  200. package/dist/installer/targets/cursor.d.ts +0 -35
  201. package/dist/installer/targets/cursor.d.ts.map +0 -1
  202. package/dist/installer/targets/cursor.js +0 -229
  203. package/dist/installer/targets/cursor.js.map +0 -1
  204. package/dist/installer/targets/opencode.d.ts +0 -30
  205. package/dist/installer/targets/opencode.d.ts.map +0 -1
  206. package/dist/installer/targets/opencode.js +0 -235
  207. package/dist/installer/targets/opencode.js.map +0 -1
  208. package/dist/installer/targets/registry.d.ts +0 -35
  209. package/dist/installer/targets/registry.d.ts.map +0 -1
  210. package/dist/installer/targets/registry.js +0 -83
  211. package/dist/installer/targets/registry.js.map +0 -1
  212. package/dist/installer/targets/shared.d.ts +0 -77
  213. package/dist/installer/targets/shared.d.ts.map +0 -1
  214. package/dist/installer/targets/shared.js +0 -246
  215. package/dist/installer/targets/shared.js.map +0 -1
  216. package/dist/installer/targets/toml.d.ts +0 -52
  217. package/dist/installer/targets/toml.d.ts.map +0 -1
  218. package/dist/installer/targets/toml.js +0 -147
  219. package/dist/installer/targets/toml.js.map +0 -1
  220. package/dist/installer/targets/types.d.ts +0 -116
  221. package/dist/installer/targets/types.d.ts.map +0 -1
  222. package/dist/installer/targets/types.js +0 -16
  223. package/dist/installer/targets/types.js.map +0 -1
  224. package/dist/mcp/index.d.ts +0 -94
  225. package/dist/mcp/index.d.ts.map +0 -1
  226. package/dist/mcp/index.js +0 -453
  227. package/dist/mcp/index.js.map +0 -1
  228. package/dist/mcp/server-instructions.d.ts +0 -19
  229. package/dist/mcp/server-instructions.d.ts.map +0 -1
  230. package/dist/mcp/server-instructions.js +0 -71
  231. package/dist/mcp/server-instructions.js.map +0 -1
  232. package/dist/mcp/tools.d.ts +0 -257
  233. package/dist/mcp/tools.d.ts.map +0 -1
  234. package/dist/mcp/tools.js +0 -1633
  235. package/dist/mcp/tools.js.map +0 -1
  236. package/dist/mcp/transport.d.ts +0 -106
  237. package/dist/mcp/transport.d.ts.map +0 -1
  238. package/dist/mcp/transport.js +0 -233
  239. package/dist/mcp/transport.js.map +0 -1
  240. package/dist/resolution/frameworks/cargo-workspace.d.ts +0 -18
  241. package/dist/resolution/frameworks/cargo-workspace.d.ts.map +0 -1
  242. package/dist/resolution/frameworks/cargo-workspace.js +0 -225
  243. package/dist/resolution/frameworks/cargo-workspace.js.map +0 -1
  244. package/dist/resolution/frameworks/csharp.d.ts +0 -8
  245. package/dist/resolution/frameworks/csharp.d.ts.map +0 -1
  246. package/dist/resolution/frameworks/csharp.js +0 -213
  247. package/dist/resolution/frameworks/csharp.js.map +0 -1
  248. package/dist/resolution/frameworks/express.d.ts +0 -8
  249. package/dist/resolution/frameworks/express.d.ts.map +0 -1
  250. package/dist/resolution/frameworks/express.js +0 -225
  251. package/dist/resolution/frameworks/express.js.map +0 -1
  252. package/dist/resolution/frameworks/go.d.ts +0 -8
  253. package/dist/resolution/frameworks/go.d.ts.map +0 -1
  254. package/dist/resolution/frameworks/go.js +0 -158
  255. package/dist/resolution/frameworks/go.js.map +0 -1
  256. package/dist/resolution/frameworks/index.d.ts +0 -42
  257. package/dist/resolution/frameworks/index.d.ts.map +0 -1
  258. package/dist/resolution/frameworks/index.js +0 -133
  259. package/dist/resolution/frameworks/index.js.map +0 -1
  260. package/dist/resolution/frameworks/java.d.ts +0 -8
  261. package/dist/resolution/frameworks/java.d.ts.map +0 -1
  262. package/dist/resolution/frameworks/java.js +0 -177
  263. package/dist/resolution/frameworks/java.js.map +0 -1
  264. package/dist/resolution/frameworks/laravel.d.ts +0 -13
  265. package/dist/resolution/frameworks/laravel.d.ts.map +0 -1
  266. package/dist/resolution/frameworks/laravel.js +0 -248
  267. package/dist/resolution/frameworks/laravel.js.map +0 -1
  268. package/dist/resolution/frameworks/nestjs.d.ts +0 -26
  269. package/dist/resolution/frameworks/nestjs.d.ts.map +0 -1
  270. package/dist/resolution/frameworks/nestjs.js +0 -374
  271. package/dist/resolution/frameworks/nestjs.js.map +0 -1
  272. package/dist/resolution/frameworks/python.d.ts +0 -10
  273. package/dist/resolution/frameworks/python.d.ts.map +0 -1
  274. package/dist/resolution/frameworks/python.js +0 -278
  275. package/dist/resolution/frameworks/python.js.map +0 -1
  276. package/dist/resolution/frameworks/react.d.ts +0 -8
  277. package/dist/resolution/frameworks/react.d.ts.map +0 -1
  278. package/dist/resolution/frameworks/react.js +0 -272
  279. package/dist/resolution/frameworks/react.js.map +0 -1
  280. package/dist/resolution/frameworks/ruby.d.ts +0 -8
  281. package/dist/resolution/frameworks/ruby.d.ts.map +0 -1
  282. package/dist/resolution/frameworks/ruby.js +0 -198
  283. package/dist/resolution/frameworks/ruby.js.map +0 -1
  284. package/dist/resolution/frameworks/rust.d.ts +0 -8
  285. package/dist/resolution/frameworks/rust.d.ts.map +0 -1
  286. package/dist/resolution/frameworks/rust.js +0 -207
  287. package/dist/resolution/frameworks/rust.js.map +0 -1
  288. package/dist/resolution/frameworks/svelte.d.ts +0 -9
  289. package/dist/resolution/frameworks/svelte.d.ts.map +0 -1
  290. package/dist/resolution/frameworks/svelte.js +0 -249
  291. package/dist/resolution/frameworks/svelte.js.map +0 -1
  292. package/dist/resolution/frameworks/swift.d.ts +0 -10
  293. package/dist/resolution/frameworks/swift.d.ts.map +0 -1
  294. package/dist/resolution/frameworks/swift.js +0 -376
  295. package/dist/resolution/frameworks/swift.js.map +0 -1
  296. package/dist/resolution/frameworks/vue.d.ts +0 -9
  297. package/dist/resolution/frameworks/vue.d.ts.map +0 -1
  298. package/dist/resolution/frameworks/vue.js +0 -306
  299. package/dist/resolution/frameworks/vue.js.map +0 -1
  300. package/dist/resolution/import-resolver.d.ts +0 -40
  301. package/dist/resolution/import-resolver.d.ts.map +0 -1
  302. package/dist/resolution/import-resolver.js +0 -663
  303. package/dist/resolution/import-resolver.js.map +0 -1
  304. package/dist/resolution/index.d.ts +0 -106
  305. package/dist/resolution/index.d.ts.map +0 -1
  306. package/dist/resolution/index.js +0 -709
  307. package/dist/resolution/index.js.map +0 -1
  308. package/dist/resolution/name-matcher.d.ts +0 -32
  309. package/dist/resolution/name-matcher.d.ts.map +0 -1
  310. package/dist/resolution/name-matcher.js +0 -384
  311. package/dist/resolution/name-matcher.js.map +0 -1
  312. package/dist/resolution/path-aliases.d.ts +0 -68
  313. package/dist/resolution/path-aliases.d.ts.map +0 -1
  314. package/dist/resolution/path-aliases.js +0 -238
  315. package/dist/resolution/path-aliases.js.map +0 -1
  316. package/dist/resolution/strip-comments.d.ts +0 -27
  317. package/dist/resolution/strip-comments.d.ts.map +0 -1
  318. package/dist/resolution/strip-comments.js +0 -441
  319. package/dist/resolution/strip-comments.js.map +0 -1
  320. package/dist/resolution/types.d.ts +0 -172
  321. package/dist/resolution/types.d.ts.map +0 -1
  322. package/dist/resolution/types.js +0 -8
  323. package/dist/resolution/types.js.map +0 -1
  324. package/dist/search/query-parser.d.ts +0 -57
  325. package/dist/search/query-parser.d.ts.map +0 -1
  326. package/dist/search/query-parser.js +0 -177
  327. package/dist/search/query-parser.js.map +0 -1
  328. package/dist/search/query-utils.d.ts +0 -53
  329. package/dist/search/query-utils.d.ts.map +0 -1
  330. package/dist/search/query-utils.js +0 -350
  331. package/dist/search/query-utils.js.map +0 -1
  332. package/dist/sync/git-hooks.d.ts +0 -45
  333. package/dist/sync/git-hooks.d.ts.map +0 -1
  334. package/dist/sync/git-hooks.js +0 -223
  335. package/dist/sync/git-hooks.js.map +0 -1
  336. package/dist/sync/index.d.ts +0 -17
  337. package/dist/sync/index.d.ts.map +0 -1
  338. package/dist/sync/index.js +0 -28
  339. package/dist/sync/index.js.map +0 -1
  340. package/dist/sync/watch-policy.d.ts +0 -48
  341. package/dist/sync/watch-policy.d.ts.map +0 -1
  342. package/dist/sync/watch-policy.js +0 -124
  343. package/dist/sync/watch-policy.js.map +0 -1
  344. package/dist/sync/watcher.d.ts +0 -81
  345. package/dist/sync/watcher.d.ts.map +0 -1
  346. package/dist/sync/watcher.js +0 -194
  347. package/dist/sync/watcher.js.map +0 -1
  348. package/dist/types.d.ts +0 -423
  349. package/dist/types.d.ts.map +0 -1
  350. package/dist/types.js +0 -256
  351. package/dist/types.js.map +0 -1
  352. package/dist/ui/glyphs.d.ts +0 -42
  353. package/dist/ui/glyphs.d.ts.map +0 -1
  354. package/dist/ui/glyphs.js +0 -78
  355. package/dist/ui/glyphs.js.map +0 -1
  356. package/dist/ui/shimmer-progress.d.ts +0 -11
  357. package/dist/ui/shimmer-progress.d.ts.map +0 -1
  358. package/dist/ui/shimmer-progress.js +0 -90
  359. package/dist/ui/shimmer-progress.js.map +0 -1
  360. package/dist/ui/shimmer-worker.d.ts +0 -2
  361. package/dist/ui/shimmer-worker.d.ts.map +0 -1
  362. package/dist/ui/shimmer-worker.js +0 -118
  363. package/dist/ui/shimmer-worker.js.map +0 -1
  364. package/dist/ui/types.d.ts +0 -17
  365. package/dist/ui/types.d.ts.map +0 -1
  366. package/dist/ui/types.js +0 -3
  367. package/dist/ui/types.js.map +0 -1
  368. package/dist/utils.d.ts +0 -205
  369. package/dist/utils.d.ts.map +0 -1
  370. package/dist/utils.js +0 -549
  371. package/dist/utils.js.map +0 -1
  372. package/scripts/agent-eval/audit.sh +0 -68
  373. package/scripts/agent-eval/itrun.sh +0 -107
  374. package/scripts/agent-eval/parse-run.mjs +0 -45
  375. package/scripts/agent-eval/parse-session.mjs +0 -93
  376. package/scripts/agent-eval/run-agent.sh +0 -34
  377. package/scripts/agent-eval/run-all.sh +0 -67
  378. package/scripts/extract-release-notes.mjs +0 -130
  379. package/scripts/local-install.sh +0 -41
  380. package/scripts/patch-tree-sitter-dart.js +0 -112
  381. package/scripts/release.sh +0 -68
package/dist/mcp/tools.js DELETED
@@ -1,1633 +0,0 @@
1
- "use strict";
2
- /**
3
- * MCP Tool Definitions
4
- *
5
- * Defines the tools exposed by the CodeGraph MCP server.
6
- */
7
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
- if (k2 === undefined) k2 = k;
9
- var desc = Object.getOwnPropertyDescriptor(m, k);
10
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
- desc = { enumerable: true, get: function() { return m[k]; } };
12
- }
13
- Object.defineProperty(o, k2, desc);
14
- }) : (function(o, m, k, k2) {
15
- if (k2 === undefined) k2 = k;
16
- o[k2] = m[k];
17
- }));
18
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
- Object.defineProperty(o, "default", { enumerable: true, value: v });
20
- }) : function(o, v) {
21
- o["default"] = v;
22
- });
23
- var __importStar = (this && this.__importStar) || (function () {
24
- var ownKeys = function(o) {
25
- ownKeys = Object.getOwnPropertyNames || function (o) {
26
- var ar = [];
27
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
- return ar;
29
- };
30
- return ownKeys(o);
31
- };
32
- return function (mod) {
33
- if (mod && mod.__esModule) return mod;
34
- var result = {};
35
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
- __setModuleDefault(result, mod);
37
- return result;
38
- };
39
- })();
40
- Object.defineProperty(exports, "__esModule", { value: true });
41
- exports.ToolHandler = exports.tools = void 0;
42
- exports.getExploreBudget = getExploreBudget;
43
- exports.getExploreOutputBudget = getExploreOutputBudget;
44
- const index_1 = __importStar(require("../index"));
45
- const crypto_1 = require("crypto");
46
- const fs_1 = require("fs");
47
- const utils_1 = require("../utils");
48
- const os_1 = require("os");
49
- const path_1 = require("path");
50
- const db_1 = require("../db");
51
- /** Maximum output length to prevent context bloat (characters) */
52
- const MAX_OUTPUT_LENGTH = 15000;
53
- /**
54
- * Rust path roots that have no file-system equivalent — `crate` is the
55
- * current crate, `super` is the parent module, `self` is the current
56
- * module. Used by `matchesSymbol` to strip these before file-path
57
- * matching so `crate::configurator::stage_apply::run` resolves the
58
- * same as `configurator::stage_apply::run`.
59
- */
60
- const RUST_PATH_PREFIXES = new Set(['crate', 'super', 'self']);
61
- /**
62
- * Node kinds that contain other symbols. For these, `codegraph_node` with
63
- * `includeCode=true` returns a structural outline (member names + signatures
64
- * + line numbers) instead of the full body, which for a large class is a
65
- * multi-thousand-character wall of source that bloats the agent's context.
66
- */
67
- const CONTAINER_NODE_KINDS = new Set([
68
- 'class', 'struct', 'interface', 'trait', 'protocol', 'enum', 'namespace', 'module',
69
- ]);
70
- /** Last `::` / `.` / `/`-separated segment of a qualified symbol. */
71
- function lastQualifierPart(symbol) {
72
- const parts = symbol.split(/::|[./]/).filter((p) => p.length > 0);
73
- return parts[parts.length - 1] ?? symbol;
74
- }
75
- /**
76
- * Calculate the recommended number of codegraph_explore calls based on project size.
77
- * Larger codebases need more exploration calls to cover their surface area,
78
- * but smaller ones should use fewer to avoid unnecessary overhead.
79
- */
80
- function getExploreBudget(fileCount) {
81
- if (fileCount < 500)
82
- return 1;
83
- if (fileCount < 5000)
84
- return 2;
85
- if (fileCount < 15000)
86
- return 3;
87
- if (fileCount < 25000)
88
- return 4;
89
- return 5;
90
- }
91
- function getExploreOutputBudget(fileCount) {
92
- if (fileCount < 500) {
93
- return {
94
- maxOutputChars: 18000,
95
- defaultMaxFiles: 5,
96
- maxCharsPerFile: 3800,
97
- gapThreshold: 8,
98
- maxSymbolsInFileHeader: 6,
99
- maxEdgesPerRelationshipKind: 6,
100
- includeRelationships: true,
101
- includeAdditionalFiles: false,
102
- includeCompletenessSignal: false,
103
- includeBudgetNote: false,
104
- };
105
- }
106
- if (fileCount < 5000) {
107
- return {
108
- maxOutputChars: 13000,
109
- defaultMaxFiles: 6,
110
- maxCharsPerFile: 2500,
111
- gapThreshold: 10,
112
- maxSymbolsInFileHeader: 8,
113
- maxEdgesPerRelationshipKind: 8,
114
- includeRelationships: true,
115
- includeAdditionalFiles: true,
116
- includeCompletenessSignal: true,
117
- includeBudgetNote: true,
118
- };
119
- }
120
- if (fileCount < 15000) {
121
- return {
122
- maxOutputChars: 35000,
123
- defaultMaxFiles: 12,
124
- maxCharsPerFile: 7000,
125
- gapThreshold: 15,
126
- maxSymbolsInFileHeader: 15,
127
- maxEdgesPerRelationshipKind: 15,
128
- includeRelationships: true,
129
- includeAdditionalFiles: true,
130
- includeCompletenessSignal: true,
131
- includeBudgetNote: true,
132
- };
133
- }
134
- return {
135
- maxOutputChars: 38000,
136
- defaultMaxFiles: 14,
137
- maxCharsPerFile: 7000,
138
- gapThreshold: 15,
139
- maxSymbolsInFileHeader: 15,
140
- maxEdgesPerRelationshipKind: 15,
141
- includeRelationships: true,
142
- includeAdditionalFiles: true,
143
- includeCompletenessSignal: true,
144
- includeBudgetNote: true,
145
- };
146
- }
147
- /**
148
- * Whether `codegraph_explore` should prefix source lines with their line
149
- * numbers (cat -n style: `<num>\t<code>`).
150
- *
151
- * Line numbers let the agent cite `file:line` straight from the explore
152
- * payload instead of re-Reading the file just to find a line number — the
153
- * dominant residual cost on precise-tracing questions (#185 follow-up).
154
- *
155
- * Defaults ON. Set `CODEGRAPH_EXPLORE_LINENUMS=0` to disable (used by the
156
- * A/B harness to measure the payload-cost vs. read-savings tradeoff).
157
- */
158
- function exploreLineNumbersEnabled() {
159
- return process.env.CODEGRAPH_EXPLORE_LINENUMS !== '0';
160
- }
161
- /**
162
- * Prefix each line of a source slice with its 1-based line number, matching
163
- * the Read tool's `cat -n` convention (number + tab) so the agent treats it
164
- * the same way it treats Read output.
165
- *
166
- * @param slice contiguous source text (already extracted from the file)
167
- * @param firstLineNumber the 1-based line number of the slice's first line
168
- */
169
- function numberSourceLines(slice, firstLineNumber) {
170
- const out = [];
171
- const split = slice.split('\n');
172
- for (let i = 0; i < split.length; i++) {
173
- out.push(`${firstLineNumber + i}\t${split[i]}`);
174
- }
175
- return out.join('\n');
176
- }
177
- /**
178
- * Mark a Claude session as having consulted MCP tools.
179
- * This enables Grep/Glob/Bash commands that would otherwise be blocked.
180
- */
181
- function markSessionConsulted(sessionId) {
182
- try {
183
- const hash = (0, crypto_1.createHash)('md5').update(sessionId).digest('hex').slice(0, 16);
184
- const markerPath = (0, path_1.join)((0, os_1.tmpdir)(), `codegraph-consulted-${hash}`);
185
- (0, fs_1.writeFileSync)(markerPath, new Date().toISOString(), 'utf8');
186
- }
187
- catch {
188
- // Silently fail - don't break MCP on marker write failure
189
- }
190
- }
191
- /**
192
- * Common projectPath property for cross-project queries
193
- */
194
- const projectPathProperty = {
195
- type: 'string',
196
- description: 'Path to a different project with .codegraph/ initialized. If omitted, uses current project. Use this to query other codebases.',
197
- };
198
- /**
199
- * All CodeGraph MCP tools
200
- *
201
- * Designed for minimal context usage - use codegraph_context as the primary tool,
202
- * and only use other tools for targeted follow-up queries.
203
- *
204
- * All tools support cross-project queries via the optional `projectPath` parameter.
205
- */
206
- exports.tools = [
207
- {
208
- name: 'codegraph_search',
209
- description: 'Quick symbol search by name. Returns locations only (no code). Use codegraph_context instead for comprehensive task context.',
210
- inputSchema: {
211
- type: 'object',
212
- properties: {
213
- query: {
214
- type: 'string',
215
- description: 'Symbol name or partial name (e.g., "auth", "signIn", "UserService")',
216
- },
217
- kind: {
218
- type: 'string',
219
- description: 'Filter by node kind',
220
- enum: ['function', 'method', 'class', 'interface', 'type', 'variable', 'route', 'component'],
221
- },
222
- limit: {
223
- type: 'number',
224
- description: 'Maximum results (default: 10)',
225
- default: 10,
226
- },
227
- projectPath: projectPathProperty,
228
- },
229
- required: ['query'],
230
- },
231
- },
232
- {
233
- name: 'codegraph_context',
234
- description: 'PRIMARY TOOL — call this FIRST for any "how does X work", architecture, feature, or bug-context question. Composes search + node + callers + callees and returns entry points, related symbols, and key code in ONE call — usually enough to answer with no further search/Read/Grep. Prefer this over chaining codegraph_search + codegraph_node, and over codegraph_explore. NOTE: provides CODE context, not product requirements; for new features still clarify UX/edge cases with the user.',
235
- inputSchema: {
236
- type: 'object',
237
- properties: {
238
- task: {
239
- type: 'string',
240
- description: 'Description of the task, bug, or feature to build context for',
241
- },
242
- maxNodes: {
243
- type: 'number',
244
- description: 'Maximum symbols to include (default: 20)',
245
- default: 20,
246
- },
247
- includeCode: {
248
- type: 'boolean',
249
- description: 'Include code snippets for key symbols (default: true)',
250
- default: true,
251
- },
252
- projectPath: projectPathProperty,
253
- },
254
- required: ['task'],
255
- },
256
- },
257
- {
258
- name: 'codegraph_callers',
259
- description: 'Find all functions/methods that call a specific symbol. Useful for understanding usage patterns and impact of changes.',
260
- inputSchema: {
261
- type: 'object',
262
- properties: {
263
- symbol: {
264
- type: 'string',
265
- description: 'Name of the function, method, or class to find callers for',
266
- },
267
- limit: {
268
- type: 'number',
269
- description: 'Maximum number of callers to return (default: 20)',
270
- default: 20,
271
- },
272
- projectPath: projectPathProperty,
273
- },
274
- required: ['symbol'],
275
- },
276
- },
277
- {
278
- name: 'codegraph_callees',
279
- description: 'Find all functions/methods that a specific symbol calls. Useful for understanding dependencies and code flow.',
280
- inputSchema: {
281
- type: 'object',
282
- properties: {
283
- symbol: {
284
- type: 'string',
285
- description: 'Name of the function, method, or class to find callees for',
286
- },
287
- limit: {
288
- type: 'number',
289
- description: 'Maximum number of callees to return (default: 20)',
290
- default: 20,
291
- },
292
- projectPath: projectPathProperty,
293
- },
294
- required: ['symbol'],
295
- },
296
- },
297
- {
298
- name: 'codegraph_impact',
299
- description: 'Analyze the impact radius of changing a symbol. Shows what code could be affected by modifications.',
300
- inputSchema: {
301
- type: 'object',
302
- properties: {
303
- symbol: {
304
- type: 'string',
305
- description: 'Name of the symbol to analyze impact for',
306
- },
307
- depth: {
308
- type: 'number',
309
- description: 'How many levels of dependencies to traverse (default: 2)',
310
- default: 2,
311
- },
312
- projectPath: projectPathProperty,
313
- },
314
- required: ['symbol'],
315
- },
316
- },
317
- {
318
- name: 'codegraph_node',
319
- description: 'Get detailed info about ONE symbol (location, signature, docstring). Pass includeCode=true for source: a function/method returns its body; a class/interface/struct/enum returns a compact member OUTLINE (fields + method signatures + line numbers), not every method body — Read or codegraph_node a specific member for its body. Keep includeCode=false to minimize context. For SEVERAL related symbols, make ONE codegraph_explore (or codegraph_context) call instead of many node calls — repeated node calls each re-read the whole context and cost far more.',
320
- inputSchema: {
321
- type: 'object',
322
- properties: {
323
- symbol: {
324
- type: 'string',
325
- description: 'Name of the symbol to get details for',
326
- },
327
- includeCode: {
328
- type: 'boolean',
329
- description: 'Include full source code (default: false to minimize context)',
330
- default: false,
331
- },
332
- projectPath: projectPathProperty,
333
- },
334
- required: ['symbol'],
335
- },
336
- },
337
- {
338
- name: 'codegraph_explore',
339
- description: 'Returns source for SEVERAL related symbols grouped by file, plus a relationship map, in ONE capped call. This is the efficient way to inspect many related symbols at once — strongly prefer it over a series of codegraph_node or Read calls (each separate call re-reads the whole context, so 8 node calls cost far more than 1 explore). Use it after codegraph_context when you need to see the actual source of several symbols. Query with specific symbol/file/code terms, NOT natural-language sentences — run codegraph_search first to find names. Bad: "how are agent prompts loaded and passed to the CLI". Good: "renderStaticScene drawElementOnCanvas ShapeCache renderElement.ts".',
340
- inputSchema: {
341
- type: 'object',
342
- properties: {
343
- query: {
344
- type: 'string',
345
- description: 'Symbol names, file names, or short code terms to explore (e.g., "AuthService loginUser session-manager", "GraphTraverser BFS impact traversal.ts"). Use codegraph_search first to find relevant names.',
346
- },
347
- maxFiles: {
348
- type: 'number',
349
- description: 'Maximum number of files to include source code from (default: 12)',
350
- default: 12,
351
- },
352
- projectPath: projectPathProperty,
353
- },
354
- required: ['query'],
355
- },
356
- },
357
- {
358
- name: 'codegraph_status',
359
- description: 'Get the status of the CodeGraph index, including statistics about indexed files, nodes, and edges.',
360
- inputSchema: {
361
- type: 'object',
362
- properties: {
363
- projectPath: projectPathProperty,
364
- },
365
- },
366
- },
367
- {
368
- name: 'codegraph_files',
369
- description: 'REQUIRED for file/folder exploration. Get the project file structure from the CodeGraph index. Returns a tree view of all indexed files with metadata (language, symbol count). Much faster than Glob/filesystem scanning. Use this FIRST when exploring project structure, finding files, or understanding codebase organization.',
370
- inputSchema: {
371
- type: 'object',
372
- properties: {
373
- path: {
374
- type: 'string',
375
- description: 'Filter to files under this directory path (e.g., "src/components"). Returns all files if not specified.',
376
- },
377
- pattern: {
378
- type: 'string',
379
- description: 'Filter files matching this glob pattern (e.g., "*.tsx", "**/*.test.ts")',
380
- },
381
- format: {
382
- type: 'string',
383
- description: 'Output format: "tree" (hierarchical, default), "flat" (simple list), "grouped" (by language)',
384
- enum: ['tree', 'flat', 'grouped'],
385
- default: 'tree',
386
- },
387
- includeMetadata: {
388
- type: 'boolean',
389
- description: 'Include file metadata like language and symbol count (default: true)',
390
- default: true,
391
- },
392
- maxDepth: {
393
- type: 'number',
394
- description: 'Maximum directory depth to show (default: unlimited)',
395
- },
396
- projectPath: projectPathProperty,
397
- },
398
- },
399
- },
400
- ];
401
- /**
402
- * Tool handler that executes tools against a CodeGraph instance
403
- *
404
- * Supports cross-project queries via the projectPath parameter.
405
- * Other projects are opened on-demand and cached for performance.
406
- */
407
- class ToolHandler {
408
- cg;
409
- // Cache of opened CodeGraph instances for cross-project queries
410
- projectCache = new Map();
411
- // The directory the server last searched for a default project. Surfaced in
412
- // the "not initialized" error so users can see why detection missed.
413
- defaultProjectHint = null;
414
- constructor(cg) {
415
- this.cg = cg;
416
- }
417
- /**
418
- * Update the default CodeGraph instance (e.g. after lazy initialization)
419
- */
420
- setDefaultCodeGraph(cg) {
421
- this.cg = cg;
422
- }
423
- /**
424
- * Record the directory the server tried to resolve the default project from.
425
- * Used only to make the "no default project" error actionable.
426
- */
427
- setDefaultProjectHint(searchedPath) {
428
- this.defaultProjectHint = searchedPath;
429
- }
430
- /**
431
- * Whether a default CodeGraph instance is available
432
- */
433
- hasDefaultCodeGraph() {
434
- return this.cg !== null;
435
- }
436
- /**
437
- * Get tool definitions with dynamic descriptions based on project size.
438
- * The codegraph_explore tool description includes a budget recommendation
439
- * scaled to the number of indexed files.
440
- */
441
- getTools() {
442
- if (!this.cg)
443
- return exports.tools;
444
- try {
445
- const stats = this.cg.getStats();
446
- const budget = getExploreBudget(stats.fileCount);
447
- return exports.tools.map(tool => {
448
- if (tool.name === 'codegraph_explore') {
449
- return {
450
- ...tool,
451
- description: `${tool.description} Budget: make at most ${budget} calls for this project (${stats.fileCount.toLocaleString()} files indexed).`,
452
- };
453
- }
454
- return tool;
455
- });
456
- }
457
- catch {
458
- return exports.tools;
459
- }
460
- }
461
- /**
462
- * Get CodeGraph instance for a project
463
- *
464
- * If projectPath is provided, opens that project's CodeGraph (cached).
465
- * Otherwise returns the default CodeGraph instance.
466
- *
467
- * Walks up parent directories to find the nearest .codegraph/ folder,
468
- * similar to how git finds .git/ directories.
469
- */
470
- getCodeGraph(projectPath) {
471
- if (!projectPath) {
472
- if (!this.cg) {
473
- const searched = this.defaultProjectHint ?? process.cwd();
474
- throw new Error('No CodeGraph project is loaded for this session.\n' +
475
- `Searched for a .codegraph/ directory starting from: ${searched}\n` +
476
- 'The index is likely fine — this is a working-directory detection issue: ' +
477
- "the MCP client launched the server outside your project and didn't report the " +
478
- 'workspace root. Fix it either way:\n' +
479
- ' • Pass projectPath to the tool call, e.g. projectPath: "/absolute/path/to/your/project"\n' +
480
- ' • Or add --path to the server\'s MCP config args: ["serve", "--mcp", "--path", "/absolute/path/to/your/project"]');
481
- }
482
- return this.cg;
483
- }
484
- // Check cache first (using original path as key)
485
- if (this.projectCache.has(projectPath)) {
486
- return this.projectCache.get(projectPath);
487
- }
488
- // Walk up parent directories to find nearest .codegraph/
489
- const resolvedRoot = (0, index_1.findNearestCodeGraphRoot)(projectPath);
490
- if (!resolvedRoot) {
491
- throw new Error(`CodeGraph not initialized in ${projectPath}. Run 'codegraph init' in that project first.`);
492
- }
493
- // Check if we already have this resolved root cached (different path, same project)
494
- if (this.projectCache.has(resolvedRoot)) {
495
- const cg = this.projectCache.get(resolvedRoot);
496
- // Cache under original path too for faster future lookups
497
- this.projectCache.set(projectPath, cg);
498
- return cg;
499
- }
500
- // Open and cache under both paths
501
- const cg = index_1.default.openSync(resolvedRoot);
502
- this.projectCache.set(resolvedRoot, cg);
503
- if (projectPath !== resolvedRoot) {
504
- this.projectCache.set(projectPath, cg);
505
- }
506
- return cg;
507
- }
508
- /**
509
- * Close all cached project connections
510
- */
511
- closeAll() {
512
- for (const cg of this.projectCache.values()) {
513
- cg.close();
514
- }
515
- this.projectCache.clear();
516
- }
517
- /**
518
- * Validate that a value is a non-empty string
519
- */
520
- validateString(value, name) {
521
- if (typeof value !== 'string' || value.length === 0) {
522
- return this.errorResult(`${name} must be a non-empty string`);
523
- }
524
- return value;
525
- }
526
- /**
527
- * Execute a tool by name
528
- */
529
- async execute(toolName, args) {
530
- try {
531
- switch (toolName) {
532
- case 'codegraph_search':
533
- return await this.handleSearch(args);
534
- case 'codegraph_context':
535
- return await this.handleContext(args);
536
- case 'codegraph_callers':
537
- return await this.handleCallers(args);
538
- case 'codegraph_callees':
539
- return await this.handleCallees(args);
540
- case 'codegraph_impact':
541
- return await this.handleImpact(args);
542
- case 'codegraph_explore':
543
- return await this.handleExplore(args);
544
- case 'codegraph_node':
545
- return await this.handleNode(args);
546
- case 'codegraph_status':
547
- return await this.handleStatus(args);
548
- case 'codegraph_files':
549
- return await this.handleFiles(args);
550
- default:
551
- return this.errorResult(`Unknown tool: ${toolName}`);
552
- }
553
- }
554
- catch (err) {
555
- return this.errorResult(`Tool execution failed: ${err instanceof Error ? err.message : String(err)}`);
556
- }
557
- }
558
- /**
559
- * Handle codegraph_search
560
- */
561
- async handleSearch(args) {
562
- const query = this.validateString(args.query, 'query');
563
- if (typeof query !== 'string')
564
- return query;
565
- const cg = this.getCodeGraph(args.projectPath);
566
- const kind = args.kind;
567
- const rawLimit = Number(args.limit) || 10;
568
- const limit = (0, utils_1.clamp)(rawLimit, 1, 100);
569
- const results = cg.searchNodes(query, {
570
- limit,
571
- kinds: kind ? [kind] : undefined,
572
- });
573
- if (results.length === 0) {
574
- return this.textResult(`No results found for "${query}"`);
575
- }
576
- const formatted = this.formatSearchResults(results);
577
- return this.textResult(this.truncateOutput(formatted));
578
- }
579
- /**
580
- * Handle codegraph_context
581
- */
582
- async handleContext(args) {
583
- const task = this.validateString(args.task, 'task');
584
- if (typeof task !== 'string')
585
- return task;
586
- // Mark session as consulted (enables Grep/Glob/Bash)
587
- const sessionId = process.env.CLAUDE_SESSION_ID;
588
- if (sessionId) {
589
- markSessionConsulted(sessionId);
590
- }
591
- const cg = this.getCodeGraph(args.projectPath);
592
- const maxNodes = args.maxNodes || 20;
593
- const includeCode = args.includeCode !== false;
594
- const context = await cg.buildContext(task, {
595
- maxNodes,
596
- includeCode,
597
- format: 'markdown',
598
- });
599
- // Detect if this looks like a feature request (vs bug fix or exploration)
600
- const isFeatureQuery = this.looksLikeFeatureRequest(task);
601
- const reminder = isFeatureQuery
602
- ? '\n\n⚠️ **Ask user:** UX preferences, edge cases, acceptance criteria'
603
- : '';
604
- // buildContext returns string when format is 'markdown'
605
- if (typeof context === 'string') {
606
- return this.textResult(context + reminder);
607
- }
608
- // If it returns TaskContext, format it
609
- return this.textResult(this.formatTaskContext(context) + reminder);
610
- }
611
- /**
612
- * Heuristic to detect if a query looks like a feature request
613
- */
614
- looksLikeFeatureRequest(task) {
615
- const featureKeywords = [
616
- 'add', 'create', 'implement', 'build', 'enable', 'allow',
617
- 'new feature', 'support for', 'ability to', 'want to',
618
- 'should be able', 'need to add', 'swap', 'edit', 'modify'
619
- ];
620
- const bugKeywords = [
621
- 'fix', 'bug', 'error', 'broken', 'crash', 'issue', 'problem',
622
- 'not working', 'fails', 'undefined', 'null'
623
- ];
624
- const explorationKeywords = [
625
- 'how does', 'where is', 'what is', 'find', 'show me',
626
- 'explain', 'understand', 'explore'
627
- ];
628
- const lowerTask = task.toLowerCase();
629
- // If it's clearly a bug or exploration, not a feature
630
- if (bugKeywords.some(k => lowerTask.includes(k)))
631
- return false;
632
- if (explorationKeywords.some(k => lowerTask.includes(k)))
633
- return false;
634
- // If it matches feature keywords, it's likely a feature request
635
- return featureKeywords.some(k => lowerTask.includes(k));
636
- }
637
- /**
638
- * Handle codegraph_callers
639
- */
640
- async handleCallers(args) {
641
- const symbol = this.validateString(args.symbol, 'symbol');
642
- if (typeof symbol !== 'string')
643
- return symbol;
644
- const cg = this.getCodeGraph(args.projectPath);
645
- const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
646
- const allMatches = this.findAllSymbols(cg, symbol);
647
- if (allMatches.nodes.length === 0) {
648
- return this.textResult(`Symbol "${symbol}" not found in the codebase`);
649
- }
650
- // Aggregate callers across all matching symbols
651
- const seen = new Set();
652
- const allCallers = [];
653
- for (const node of allMatches.nodes) {
654
- for (const c of cg.getCallers(node.id)) {
655
- if (!seen.has(c.node.id)) {
656
- seen.add(c.node.id);
657
- allCallers.push(c.node);
658
- }
659
- }
660
- }
661
- if (allCallers.length === 0) {
662
- return this.textResult(`No callers found for "${symbol}"${allMatches.note}`);
663
- }
664
- const formatted = this.formatNodeList(allCallers.slice(0, limit), `Callers of ${symbol}`) + allMatches.note;
665
- return this.textResult(this.truncateOutput(formatted));
666
- }
667
- /**
668
- * Handle codegraph_callees
669
- */
670
- async handleCallees(args) {
671
- const symbol = this.validateString(args.symbol, 'symbol');
672
- if (typeof symbol !== 'string')
673
- return symbol;
674
- const cg = this.getCodeGraph(args.projectPath);
675
- const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
676
- const allMatches = this.findAllSymbols(cg, symbol);
677
- if (allMatches.nodes.length === 0) {
678
- return this.textResult(`Symbol "${symbol}" not found in the codebase`);
679
- }
680
- // Aggregate callees across all matching symbols
681
- const seen = new Set();
682
- const allCallees = [];
683
- for (const node of allMatches.nodes) {
684
- for (const c of cg.getCallees(node.id)) {
685
- if (!seen.has(c.node.id)) {
686
- seen.add(c.node.id);
687
- allCallees.push(c.node);
688
- }
689
- }
690
- }
691
- if (allCallees.length === 0) {
692
- return this.textResult(`No callees found for "${symbol}"${allMatches.note}`);
693
- }
694
- const formatted = this.formatNodeList(allCallees.slice(0, limit), `Callees of ${symbol}`) + allMatches.note;
695
- return this.textResult(this.truncateOutput(formatted));
696
- }
697
- /**
698
- * Handle codegraph_impact
699
- */
700
- async handleImpact(args) {
701
- const symbol = this.validateString(args.symbol, 'symbol');
702
- if (typeof symbol !== 'string')
703
- return symbol;
704
- const cg = this.getCodeGraph(args.projectPath);
705
- const depth = (0, utils_1.clamp)(args.depth || 2, 1, 10);
706
- const allMatches = this.findAllSymbols(cg, symbol);
707
- if (allMatches.nodes.length === 0) {
708
- return this.textResult(`Symbol "${symbol}" not found in the codebase`);
709
- }
710
- // Aggregate impact across all matching symbols
711
- const mergedNodes = new Map();
712
- const mergedEdges = [];
713
- const seenEdges = new Set();
714
- for (const node of allMatches.nodes) {
715
- const impact = cg.getImpactRadius(node.id, depth);
716
- for (const [id, n] of impact.nodes) {
717
- mergedNodes.set(id, n);
718
- }
719
- for (const e of impact.edges) {
720
- const key = `${e.source}->${e.target}:${e.kind}`;
721
- if (!seenEdges.has(key)) {
722
- seenEdges.add(key);
723
- mergedEdges.push(e);
724
- }
725
- }
726
- }
727
- const mergedImpact = {
728
- nodes: mergedNodes,
729
- edges: mergedEdges,
730
- roots: allMatches.nodes.map(n => n.id),
731
- };
732
- const formatted = this.formatImpact(symbol, mergedImpact) + allMatches.note;
733
- return this.textResult(this.truncateOutput(formatted));
734
- }
735
- /**
736
- * Handle codegraph_explore — deep exploration in a single call
737
- *
738
- * Strategy: find relevant symbols via graph traversal, group by file,
739
- * then read contiguous file sections covering all symbols per file.
740
- * This replaces multiple codegraph_node + Read calls.
741
- *
742
- * Output size is adaptive to project file count via
743
- * `getExploreOutputBudget` — see #185 for why a fixed 35k cap was a
744
- * tax on small projects while earning its keep on large ones.
745
- */
746
- async handleExplore(args) {
747
- const query = this.validateString(args.query, 'query');
748
- if (typeof query !== 'string')
749
- return query;
750
- const cg = this.getCodeGraph(args.projectPath);
751
- const projectRoot = cg.getProjectRoot();
752
- // Resolve adaptive output budget from project size. Falls back to the
753
- // largest-tier defaults if stats aren't available, which preserves
754
- // pre-#185 behavior for callers that hit the rare stats failure.
755
- let budget;
756
- try {
757
- budget = getExploreOutputBudget(cg.getStats().fileCount);
758
- }
759
- catch {
760
- budget = getExploreOutputBudget(Infinity);
761
- }
762
- const maxFiles = (0, utils_1.clamp)(args.maxFiles || budget.defaultMaxFiles, 1, 20);
763
- // Step 1: Find relevant context with generous parameters.
764
- // Use a large maxNodes budget — explore has its own 35k char output limit
765
- // that prevents context bloat, so more nodes just means better coverage
766
- // across entry points (especially for large files like Svelte components).
767
- const subgraph = await cg.findRelevantContext(query, {
768
- searchLimit: 8,
769
- traversalDepth: 3,
770
- maxNodes: 200,
771
- minScore: 0.2,
772
- });
773
- if (subgraph.nodes.size === 0) {
774
- return this.textResult(`No relevant code found for "${query}"`);
775
- }
776
- // Step 2: Group nodes by file, score by relevance
777
- const fileGroups = new Map();
778
- const entryNodeIds = new Set(subgraph.roots);
779
- // Build a set of nodes directly connected to entry points (depth 1)
780
- const connectedToEntry = new Set();
781
- for (const edge of subgraph.edges) {
782
- if (entryNodeIds.has(edge.source))
783
- connectedToEntry.add(edge.target);
784
- if (entryNodeIds.has(edge.target))
785
- connectedToEntry.add(edge.source);
786
- }
787
- for (const node of subgraph.nodes.values()) {
788
- // Skip import/export nodes — they add noise without information
789
- if (node.kind === 'import' || node.kind === 'export')
790
- continue;
791
- const group = fileGroups.get(node.filePath) || { nodes: [], score: 0 };
792
- group.nodes.push(node);
793
- // Score: entry point nodes worth 10, directly connected worth 3, others worth 1
794
- if (entryNodeIds.has(node.id)) {
795
- group.score += 10;
796
- }
797
- else if (connectedToEntry.has(node.id)) {
798
- group.score += 3;
799
- }
800
- else {
801
- group.score += 1;
802
- }
803
- fileGroups.set(node.filePath, group);
804
- }
805
- // Only include files that have entry points or nodes directly connected to entry points
806
- const relevantFiles = [...fileGroups.entries()].filter(([, group]) => group.score >= 3);
807
- // Extract query terms for relevance checking
808
- const queryTerms = query.toLowerCase().split(/\s+/).filter(t => t.length >= 3);
809
- // Sort files: highest relevance first, deprioritize low-value files
810
- const sortedFiles = relevantFiles.sort((a, b) => {
811
- const aPath = a[0].toLowerCase();
812
- const bPath = b[0].toLowerCase();
813
- // Check if any node name or file path relates to query terms
814
- const hasQueryRelevance = (filePath, nodes) => {
815
- const fp = filePath.toLowerCase();
816
- if (queryTerms.some(t => fp.includes(t)))
817
- return true;
818
- return nodes.some(n => queryTerms.some(t => n.name.toLowerCase().includes(t)));
819
- };
820
- const aRelevant = hasQueryRelevance(aPath, a[1].nodes);
821
- const bRelevant = hasQueryRelevance(bPath, b[1].nodes);
822
- if (aRelevant !== bRelevant)
823
- return aRelevant ? -1 : 1;
824
- // Deprioritize test files, icon files, and i18n files
825
- const isLowValue = (p) => /\/(tests?|__tests?__|spec)\//i.test(p) ||
826
- /\bicons?\b/i.test(p) ||
827
- /\bi18n\b/i.test(p);
828
- const aLow = isLowValue(aPath);
829
- const bLow = isLowValue(bPath);
830
- if (aLow !== bLow)
831
- return aLow ? 1 : -1;
832
- if (a[1].score !== b[1].score)
833
- return b[1].score - a[1].score;
834
- return b[1].nodes.length - a[1].nodes.length;
835
- });
836
- // Step 3: Build relationship map
837
- const lines = [
838
- `## Exploration: ${query}`,
839
- '',
840
- `Found ${subgraph.nodes.size} symbols across ${fileGroups.size} files.`,
841
- '',
842
- ];
843
- // Relationship map — show how symbols connect
844
- const significantEdges = subgraph.edges.filter(e => e.kind !== 'contains' // skip contains — it's implied by file grouping
845
- );
846
- if (budget.includeRelationships && significantEdges.length > 0) {
847
- lines.push('### Relationships');
848
- lines.push('');
849
- // Group edges by kind for readability
850
- const byKind = new Map();
851
- for (const edge of significantEdges) {
852
- const sourceNode = subgraph.nodes.get(edge.source);
853
- const targetNode = subgraph.nodes.get(edge.target);
854
- if (!sourceNode || !targetNode)
855
- continue;
856
- const group = byKind.get(edge.kind) || [];
857
- group.push({ source: sourceNode.name, target: targetNode.name });
858
- byKind.set(edge.kind, group);
859
- }
860
- for (const [kind, edges] of byKind) {
861
- const cap = budget.maxEdgesPerRelationshipKind;
862
- const shown = edges.slice(0, cap);
863
- lines.push(`**${kind}:**`);
864
- for (const e of shown) {
865
- lines.push(`- ${e.source} → ${e.target}`);
866
- }
867
- if (edges.length > cap) {
868
- lines.push(`- ... and ${edges.length - cap} more`);
869
- }
870
- lines.push('');
871
- }
872
- }
873
- // Step 4: Read contiguous file sections
874
- lines.push('### Source Code');
875
- lines.push('');
876
- let totalChars = lines.join('\n').length;
877
- let filesIncluded = 0;
878
- let anyFileTrimmed = false;
879
- for (const [filePath, group] of sortedFiles) {
880
- if (filesIncluded >= maxFiles)
881
- break;
882
- if (totalChars > budget.maxOutputChars * 0.9)
883
- break;
884
- const absPath = (0, utils_1.validatePathWithinRoot)(projectRoot, filePath);
885
- if (!absPath || !(0, fs_1.existsSync)(absPath))
886
- continue;
887
- let fileContent;
888
- try {
889
- fileContent = (0, fs_1.readFileSync)(absPath, 'utf-8');
890
- }
891
- catch {
892
- continue;
893
- }
894
- const fileLines = fileContent.split('\n');
895
- const lang = group.nodes[0]?.language || '';
896
- // Cluster nearby symbols to avoid reading huge gaps between distant symbols.
897
- // Sort by start line, then merge overlapping/adjacent ranges (within the
898
- // adaptive gap threshold). Include both node ranges AND edge source
899
- // locations so template sections with component usages/calls are
900
- // covered (not just script block symbols).
901
- //
902
- // Each range carries an `importance` score so we can rank clusters
903
- // when the per-file budget forces us to drop some: entry-point nodes
904
- // are worth 10, directly-connected nodes 3, peripheral nodes 1, and
905
- // bare edge-source lines 2 (less than a connected node but more than
906
- // a peripheral one — they hint at a reference but aren't a definition).
907
- // Container kinds whose body can span most/all of a file. When such a
908
- // node covers most of the file we drop it from the ranges: keeping it
909
- // would merge every method inside it into one giant cluster spanning
910
- // the whole file, which then tail-trims down to just the container's
911
- // opening lines (its header/declarations) and buries the methods the
912
- // query actually asked about (#185 follow-up — Session.swift in
913
- // Alamofire is the canonical case: the `Session` class spans ~1,400
914
- // lines). We want the granular symbols inside, not the envelope.
915
- const ENVELOPE_KINDS = new Set(['file', 'module', 'class', 'struct', 'interface', 'enum', 'namespace', 'protocol', 'trait', 'component']);
916
- const ranges = group.nodes
917
- .filter(n => n.startLine > 0 && n.endLine > 0)
918
- // Drop whole-file envelope nodes (containers covering >50% of the file).
919
- .filter(n => !(ENVELOPE_KINDS.has(n.kind) && (n.endLine - n.startLine + 1) > fileLines.length * 0.5))
920
- .map(n => {
921
- let importance = 1;
922
- if (entryNodeIds.has(n.id))
923
- importance = 10;
924
- else if (connectedToEntry.has(n.id))
925
- importance = 3;
926
- return { start: n.startLine, end: n.endLine, name: n.name, kind: n.kind, importance };
927
- });
928
- // Add edge source locations in this file — captures template references
929
- // (component usages, event handlers) that aren't nodes themselves.
930
- // Query edges directly from the DB (not just the subgraph) because BFS
931
- // traversal may have pruned template reference targets due to node budget.
932
- const edgeLines = new Set(); // dedup by "line:name"
933
- for (const node of group.nodes) {
934
- const outgoing = cg.getOutgoingEdges(node.id);
935
- for (const edge of outgoing) {
936
- if (!edge.line || edge.line <= 0 || edge.kind === 'contains')
937
- continue;
938
- const key = `${edge.line}:${edge.target}`;
939
- if (edgeLines.has(key))
940
- continue;
941
- edgeLines.add(key);
942
- // Look up target name from subgraph first, fall back to edge kind
943
- const targetNode = subgraph.nodes.get(edge.target);
944
- const targetName = targetNode?.name ?? edge.kind;
945
- ranges.push({ start: edge.line, end: edge.line, name: targetName, kind: edge.kind, importance: 2 });
946
- }
947
- }
948
- ranges.sort((a, b) => a.start - b.start);
949
- if (ranges.length === 0)
950
- continue;
951
- const gapThreshold = budget.gapThreshold;
952
- const clusters = [];
953
- let current = {
954
- start: ranges[0].start,
955
- end: ranges[0].end,
956
- symbols: [`${ranges[0].name}(${ranges[0].kind})`],
957
- score: ranges[0].importance,
958
- maxImportance: ranges[0].importance,
959
- };
960
- for (let i = 1; i < ranges.length; i++) {
961
- const r = ranges[i];
962
- if (r.start <= current.end + gapThreshold) {
963
- current.end = Math.max(current.end, r.end);
964
- current.symbols.push(`${r.name}(${r.kind})`);
965
- current.score += r.importance;
966
- current.maxImportance = Math.max(current.maxImportance, r.importance);
967
- }
968
- else {
969
- clusters.push(current);
970
- current = {
971
- start: r.start,
972
- end: r.end,
973
- symbols: [`${r.name}(${r.kind})`],
974
- score: r.importance,
975
- maxImportance: r.importance,
976
- };
977
- }
978
- }
979
- clusters.push(current);
980
- // Build file section output from clusters, capped by per-file budget.
981
- // The pathological case (#185): a file like Session.swift where every
982
- // method is adjacent collapses into one cluster spanning the whole
983
- // file, and dumping that into the agent's context is most of the
984
- // token cost on small projects. We pick clusters in priority order
985
- // until the per-file char cap is hit. Truly enormous single clusters
986
- // get tail-trimmed with a marker.
987
- const contextPadding = 3;
988
- const withLineNumbers = exploreLineNumbersEnabled();
989
- const buildSection = (c) => {
990
- const startIdx = Math.max(0, c.start - 1 - contextPadding);
991
- const endIdx = Math.min(fileLines.length, c.end + contextPadding);
992
- const slice = fileLines.slice(startIdx, endIdx).join('\n');
993
- // startIdx is 0-based, so the slice's first line is line startIdx + 1.
994
- return withLineNumbers ? numberSourceLines(slice, startIdx + 1) : slice;
995
- };
996
- // Language-neutral separator (no `//` — not a comment in Python, Ruby,
997
- // etc.). With line numbers on, the line-number jump also signals the gap.
998
- const GAP_MARKER = '\n\n... (gap) ...\n\n';
999
- // Rank clusters for inclusion under the per-file cap. Entry-point
1000
- // clusters come first: a cluster containing a query entry point
1001
- // (importance 10) must outrank a dense block of mere declarations,
1002
- // otherwise on a large file like Session.swift the top-of-file class
1003
- // header + property list (many adjacent low-importance nodes, high
1004
- // density) wins the budget and buries the actual methods the query
1005
- // asked about (perform/didCreateURLRequest/task live deep in the
1006
- // file). Within the same importance tier, prefer density (score per
1007
- // line) so we still favor focused clusters over sprawling ones, then
1008
- // smaller span as a cheap-to-include tiebreak.
1009
- const rankedClusters = clusters
1010
- .map((c, i) => ({ idx: i, span: c.end - c.start + 1, c }))
1011
- .sort((a, b) => {
1012
- if (b.c.maxImportance !== a.c.maxImportance)
1013
- return b.c.maxImportance - a.c.maxImportance;
1014
- const densityA = a.c.score / a.span;
1015
- const densityB = b.c.score / b.span;
1016
- if (densityB !== densityA)
1017
- return densityB - densityA;
1018
- if (b.c.score !== a.c.score)
1019
- return b.c.score - a.c.score;
1020
- return a.span - b.span;
1021
- });
1022
- const chosenIndices = new Set();
1023
- let projectedChars = 0;
1024
- for (const rc of rankedClusters) {
1025
- const sectionLen = buildSection(rc.c).length + (chosenIndices.size > 0 ? GAP_MARKER.length : 0);
1026
- // Always take the top-ranked cluster, even if oversize, so we don't
1027
- // return an empty file section (agent would then re-Read the file,
1028
- // negating the savings).
1029
- if (chosenIndices.size === 0) {
1030
- chosenIndices.add(rc.idx);
1031
- projectedChars += sectionLen;
1032
- continue;
1033
- }
1034
- if (projectedChars + sectionLen > budget.maxCharsPerFile)
1035
- continue;
1036
- chosenIndices.add(rc.idx);
1037
- projectedChars += sectionLen;
1038
- }
1039
- // Emit chosen clusters in source order so the file reads top-to-bottom.
1040
- let fileSection = '';
1041
- const allSymbols = [];
1042
- let fileTrimmed = false;
1043
- for (let i = 0; i < clusters.length; i++) {
1044
- if (!chosenIndices.has(i))
1045
- continue;
1046
- const cluster = clusters[i];
1047
- const section = buildSection(cluster);
1048
- if (fileSection.length > 0)
1049
- fileSection += GAP_MARKER;
1050
- fileSection += section;
1051
- allSymbols.push(...cluster.symbols);
1052
- }
1053
- // If a single chosen cluster is still oversize (long monolithic
1054
- // function), tail-trim it. Better one trimmed view than nothing.
1055
- if (fileSection.length > budget.maxCharsPerFile) {
1056
- fileSection = fileSection.slice(0, budget.maxCharsPerFile) + '\n... (trimmed) ...';
1057
- fileTrimmed = true;
1058
- }
1059
- if (chosenIndices.size < clusters.length || fileTrimmed) {
1060
- anyFileTrimmed = true;
1061
- }
1062
- // Dedupe + cap the symbols list shown in the per-file header. Some
1063
- // files (Session.swift in Alamofire) produced 3.4KB symbol lists
1064
- // from cluster scoring + edge-source lines, dwarfing the per-file
1065
- // body cap. Show top names by frequency, with a "+N more" tail.
1066
- const symbolCounts = new Map();
1067
- for (const s of allSymbols) {
1068
- symbolCounts.set(s, (symbolCounts.get(s) ?? 0) + 1);
1069
- }
1070
- const sortedSymbols = [...symbolCounts.entries()]
1071
- .sort((a, b) => b[1] - a[1])
1072
- .map(([name]) => name);
1073
- const headerCap = budget.maxSymbolsInFileHeader;
1074
- const headerSymbols = sortedSymbols.slice(0, headerCap);
1075
- const omittedCount = sortedSymbols.length - headerSymbols.length;
1076
- const headerSuffix = omittedCount > 0
1077
- ? `${headerSymbols.join(', ')}, +${omittedCount} more`
1078
- : headerSymbols.join(', ');
1079
- const fileHeader = `#### ${filePath} — ${headerSuffix}`;
1080
- // Respect the total output cap on a file-by-file basis.
1081
- if (totalChars + fileSection.length + 200 > budget.maxOutputChars) {
1082
- const remaining = budget.maxOutputChars - totalChars - 200;
1083
- if (remaining < 500)
1084
- break;
1085
- const trimmed = fileSection.slice(0, remaining) + '\n... (trimmed) ...';
1086
- lines.push(fileHeader);
1087
- lines.push('');
1088
- lines.push('```' + lang);
1089
- lines.push(trimmed);
1090
- lines.push('```');
1091
- lines.push('');
1092
- totalChars += trimmed.length + 200;
1093
- filesIncluded++;
1094
- anyFileTrimmed = true;
1095
- break;
1096
- }
1097
- lines.push(fileHeader);
1098
- lines.push('');
1099
- lines.push('```' + lang);
1100
- lines.push(fileSection);
1101
- lines.push('```');
1102
- lines.push('');
1103
- totalChars += fileSection.length + 200;
1104
- filesIncluded++;
1105
- }
1106
- // Add remaining files as references (from both relevant and peripheral files).
1107
- // Small projects (per budget) skip this — the relevant story already fits
1108
- // in the source section, and a trailing pointer list is pure overhead.
1109
- if (budget.includeAdditionalFiles) {
1110
- const remainingRelevant = sortedFiles.slice(filesIncluded);
1111
- const peripheralFiles = [...fileGroups.entries()]
1112
- .filter(([, group]) => group.score < 3)
1113
- .sort((a, b) => b[1].score - a[1].score);
1114
- const remainingFiles = [...remainingRelevant, ...peripheralFiles];
1115
- if (remainingFiles.length > 0) {
1116
- lines.push('### Additional relevant files (not shown)');
1117
- lines.push('');
1118
- for (const [filePath, group] of remainingFiles.slice(0, 10)) {
1119
- const symbols = group.nodes.map(n => `${n.name}:${n.startLine}`).join(', ');
1120
- lines.push(`- ${filePath}: ${symbols}`);
1121
- }
1122
- if (remainingFiles.length > 10) {
1123
- lines.push(`- ... and ${remainingFiles.length - 10} more files`);
1124
- }
1125
- }
1126
- }
1127
- // Add completeness signal so agents know they don't need to re-read these files.
1128
- // On small projects the budget gates this off — but if we actually had to
1129
- // trim or drop clusters, surface a brief note so the agent knows it can
1130
- // still Read for more detail.
1131
- if (budget.includeCompletenessSignal) {
1132
- lines.push('');
1133
- lines.push('---');
1134
- lines.push(`> **Complete source code is included above for ${filesIncluded} files.** You do NOT need to re-read these files — the relevant sections are already shown in full. Only use Read/Grep for files listed under "Additional relevant files" if you need more detail.`);
1135
- }
1136
- else if (anyFileTrimmed) {
1137
- lines.push('');
1138
- lines.push(`> Some file sections were trimmed for size. Use \`codegraph_node\` or Read for the full source if needed.`);
1139
- }
1140
- // Add explore budget note based on project size
1141
- if (budget.includeBudgetNote) {
1142
- try {
1143
- const stats = cg.getStats();
1144
- const callBudget = getExploreBudget(stats.fileCount);
1145
- lines.push('');
1146
- lines.push(`> **Explore budget: ${callBudget} calls max for this project (${stats.fileCount.toLocaleString()} files indexed).** Stop exploring and synthesize your answer once you've used ${callBudget} calls — do NOT make additional explore calls beyond this budget.`);
1147
- }
1148
- catch {
1149
- // Stats unavailable — skip budget note
1150
- }
1151
- }
1152
- // Hard-cap to the adaptive budget. The per-file loop bounds the source
1153
- // sections, but the relationship map, additional-files list, and
1154
- // completeness/budget notes can still push the assembled output past
1155
- // maxOutputChars (observed 30k against a 28k tier cap). A fat explore
1156
- // payload persists in the agent's context and is re-read as cache-input
1157
- // on every subsequent turn, so the overrun is paid many times over.
1158
- const output = lines.join('\n');
1159
- if (output.length > budget.maxOutputChars) {
1160
- const cut = output.slice(0, budget.maxOutputChars);
1161
- const lastNewline = cut.lastIndexOf('\n');
1162
- const safe = lastNewline > budget.maxOutputChars * 0.8 ? cut.slice(0, lastNewline) : cut;
1163
- return this.textResult(safe + '\n\n... (explore output truncated to budget — use codegraph_node or Read for more)');
1164
- }
1165
- return this.textResult(output);
1166
- }
1167
- /**
1168
- * Handle codegraph_node
1169
- */
1170
- async handleNode(args) {
1171
- const symbol = this.validateString(args.symbol, 'symbol');
1172
- if (typeof symbol !== 'string')
1173
- return symbol;
1174
- const cg = this.getCodeGraph(args.projectPath);
1175
- // Default to false to minimize context usage
1176
- const includeCode = args.includeCode === true;
1177
- const match = this.findSymbol(cg, symbol);
1178
- if (!match) {
1179
- return this.textResult(`Symbol "${symbol}" not found in the codebase`);
1180
- }
1181
- let code = null;
1182
- let outline = null;
1183
- if (includeCode) {
1184
- // For container symbols (class/interface/struct/…), the full body is the
1185
- // sum of every method body — a wall of source (e.g. a 10k-char class)
1186
- // that bloats context and is rarely needed in full. Return a structural
1187
- // outline (members + signatures + line numbers) instead; the agent can
1188
- // Read or codegraph_node a specific method for its body. Leaf symbols
1189
- // (function/method/etc.) return their full body as before.
1190
- if (CONTAINER_NODE_KINDS.has(match.node.kind)) {
1191
- outline = this.buildContainerOutline(cg, match.node);
1192
- }
1193
- if (!outline) {
1194
- code = await cg.getCode(match.node.id);
1195
- }
1196
- }
1197
- const formatted = this.formatNodeDetails(match.node, code, outline) + match.note;
1198
- return this.textResult(this.truncateOutput(formatted));
1199
- }
1200
- /**
1201
- * Handle codegraph_status
1202
- */
1203
- async handleStatus(args) {
1204
- const cg = this.getCodeGraph(args.projectPath);
1205
- const stats = cg.getStats();
1206
- const lines = [
1207
- '## CodeGraph Status',
1208
- '',
1209
- `**Files indexed:** ${stats.fileCount}`,
1210
- `**Total nodes:** ${stats.nodeCount}`,
1211
- `**Total edges:** ${stats.edgeCount}`,
1212
- `**Database size:** ${(stats.dbSizeBytes / 1024 / 1024).toFixed(2)} MB`,
1213
- ];
1214
- // Surface the active SQLite backend. Without this, users on the
1215
- // silent WASM fallback (better-sqlite3 install failed) see "slow"
1216
- // indexing and DB-lock errors with no signal of why.
1217
- const backend = cg.getBackend();
1218
- if (backend === 'native') {
1219
- lines.push(`**Backend:** native (better-sqlite3)`);
1220
- }
1221
- else {
1222
- lines.push(`**Backend:** ⚠ wasm (better-sqlite3 unavailable) — ` +
1223
- `5-10x slower than native. Fix: ${db_1.WASM_FALLBACK_FIX_RECIPE}`);
1224
- }
1225
- lines.push('', '### Nodes by Kind:');
1226
- for (const [kind, count] of Object.entries(stats.nodesByKind)) {
1227
- if (count > 0) {
1228
- lines.push(`- ${kind}: ${count}`);
1229
- }
1230
- }
1231
- lines.push('', '### Languages:');
1232
- for (const [lang, count] of Object.entries(stats.filesByLanguage)) {
1233
- if (count > 0) {
1234
- lines.push(`- ${lang}: ${count}`);
1235
- }
1236
- }
1237
- return this.textResult(lines.join('\n'));
1238
- }
1239
- /**
1240
- * Handle codegraph_files - get project file structure from the index
1241
- */
1242
- async handleFiles(args) {
1243
- const cg = this.getCodeGraph(args.projectPath);
1244
- const pathFilter = args.path;
1245
- const pattern = args.pattern;
1246
- const format = args.format || 'tree';
1247
- const includeMetadata = args.includeMetadata !== false;
1248
- const maxDepth = args.maxDepth != null ? (0, utils_1.clamp)(args.maxDepth, 1, 20) : undefined;
1249
- // Get all files from the index
1250
- const allFiles = cg.getFiles();
1251
- if (allFiles.length === 0) {
1252
- return this.textResult('No files indexed. Run `codegraph index` first.');
1253
- }
1254
- // Filter by path prefix
1255
- let files = pathFilter
1256
- ? allFiles.filter(f => f.path.startsWith(pathFilter) || f.path.startsWith('./' + pathFilter))
1257
- : allFiles;
1258
- // Filter by glob pattern
1259
- if (pattern) {
1260
- const regex = this.globToRegex(pattern);
1261
- files = files.filter(f => regex.test(f.path));
1262
- }
1263
- if (files.length === 0) {
1264
- return this.textResult(`No files found matching the criteria.`);
1265
- }
1266
- // Format output
1267
- let output;
1268
- switch (format) {
1269
- case 'flat':
1270
- output = this.formatFilesFlat(files, includeMetadata);
1271
- break;
1272
- case 'grouped':
1273
- output = this.formatFilesGrouped(files, includeMetadata);
1274
- break;
1275
- case 'tree':
1276
- default:
1277
- output = this.formatFilesTree(files, includeMetadata, maxDepth);
1278
- break;
1279
- }
1280
- return this.textResult(this.truncateOutput(output));
1281
- }
1282
- /**
1283
- * Convert glob pattern to regex
1284
- */
1285
- globToRegex(pattern) {
1286
- const escaped = pattern
1287
- .replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex chars except * and ?
1288
- .replace(/\*\*/g, '{{GLOBSTAR}}') // Temp placeholder for **
1289
- .replace(/\*/g, '[^/]*') // * matches anything except /
1290
- .replace(/\?/g, '[^/]') // ? matches single char except /
1291
- .replace(/\{\{GLOBSTAR\}\}/g, '.*'); // ** matches anything including /
1292
- return new RegExp(escaped);
1293
- }
1294
- /**
1295
- * Format files as a flat list
1296
- */
1297
- formatFilesFlat(files, includeMetadata) {
1298
- const lines = [`## Files (${files.length})`, ''];
1299
- for (const file of files.sort((a, b) => a.path.localeCompare(b.path))) {
1300
- if (includeMetadata) {
1301
- lines.push(`- ${file.path} (${file.language}, ${file.nodeCount} symbols)`);
1302
- }
1303
- else {
1304
- lines.push(`- ${file.path}`);
1305
- }
1306
- }
1307
- return lines.join('\n');
1308
- }
1309
- /**
1310
- * Format files grouped by language
1311
- */
1312
- formatFilesGrouped(files, includeMetadata) {
1313
- const byLang = new Map();
1314
- for (const file of files) {
1315
- const existing = byLang.get(file.language) || [];
1316
- existing.push(file);
1317
- byLang.set(file.language, existing);
1318
- }
1319
- const lines = [`## Files by Language (${files.length} total)`, ''];
1320
- // Sort languages by file count (descending)
1321
- const sortedLangs = [...byLang.entries()].sort((a, b) => b[1].length - a[1].length);
1322
- for (const [lang, langFiles] of sortedLangs) {
1323
- lines.push(`### ${lang} (${langFiles.length})`);
1324
- for (const file of langFiles.sort((a, b) => a.path.localeCompare(b.path))) {
1325
- if (includeMetadata) {
1326
- lines.push(`- ${file.path} (${file.nodeCount} symbols)`);
1327
- }
1328
- else {
1329
- lines.push(`- ${file.path}`);
1330
- }
1331
- }
1332
- lines.push('');
1333
- }
1334
- return lines.join('\n');
1335
- }
1336
- /**
1337
- * Format files as a tree structure
1338
- */
1339
- formatFilesTree(files, includeMetadata, maxDepth) {
1340
- const root = { name: '', children: new Map() };
1341
- for (const file of files) {
1342
- const parts = file.path.split('/');
1343
- let current = root;
1344
- for (let i = 0; i < parts.length; i++) {
1345
- const part = parts[i];
1346
- if (!part)
1347
- continue;
1348
- if (!current.children.has(part)) {
1349
- current.children.set(part, { name: part, children: new Map() });
1350
- }
1351
- current = current.children.get(part);
1352
- // If this is the last part, it's a file
1353
- if (i === parts.length - 1) {
1354
- current.file = { language: file.language, nodeCount: file.nodeCount };
1355
- }
1356
- }
1357
- }
1358
- // Render tree
1359
- const lines = [`## Project Structure (${files.length} files)`, ''];
1360
- const renderNode = (node, prefix, isLast, depth) => {
1361
- if (maxDepth !== undefined && depth > maxDepth)
1362
- return;
1363
- const connector = isLast ? '└── ' : '├── ';
1364
- const childPrefix = isLast ? ' ' : '│ ';
1365
- if (node.name) {
1366
- let line = prefix + connector + node.name;
1367
- if (node.file && includeMetadata) {
1368
- line += ` (${node.file.language}, ${node.file.nodeCount} symbols)`;
1369
- }
1370
- lines.push(line);
1371
- }
1372
- const children = [...node.children.values()];
1373
- // Sort: directories first, then files, both alphabetically
1374
- children.sort((a, b) => {
1375
- const aIsDir = a.children.size > 0 && !a.file;
1376
- const bIsDir = b.children.size > 0 && !b.file;
1377
- if (aIsDir !== bIsDir)
1378
- return aIsDir ? -1 : 1;
1379
- return a.name.localeCompare(b.name);
1380
- });
1381
- for (let i = 0; i < children.length; i++) {
1382
- const child = children[i];
1383
- const nextPrefix = node.name ? prefix + childPrefix : prefix;
1384
- renderNode(child, nextPrefix, i === children.length - 1, depth + 1);
1385
- }
1386
- };
1387
- renderNode(root, '', true, 0);
1388
- return lines.join('\n');
1389
- }
1390
- // =========================================================================
1391
- // Symbol resolution helpers
1392
- // =========================================================================
1393
- /**
1394
- * Find a symbol by name, handling disambiguation when multiple matches exist.
1395
- * Returns the best match and a note about alternatives if any.
1396
- */
1397
- /**
1398
- * Check if a node matches a symbol query.
1399
- *
1400
- * Accepts simple names (`run`) and three flavors of qualifier:
1401
- * - dotted `Session.request` (TS/JS/Python)
1402
- * - colon-pair `stage_apply::run` (Rust, C++, Ruby)
1403
- * - slash `configurator/stage_apply` (path-ish)
1404
- *
1405
- * Multi-level qualifiers compose: `crate::configurator::stage_apply::run`
1406
- * works. Rust path prefixes (`crate`, `super`, `self`) are stripped so
1407
- * the canonical `crate::module::symbol` form resolves.
1408
- *
1409
- * Resolution order, last part must always equal `node.name`:
1410
- * 1. Suffix-match against `qualifiedName` (handles class-scoped methods
1411
- * where the extractor builds the qualified name from the AST stack)
1412
- * 2. File-path containment (handles file-derived modules in Rust/
1413
- * Python — `stage_apply::run` matches a `run` in `stage_apply.rs`)
1414
- */
1415
- matchesSymbol(node, symbol) {
1416
- // Simple name match
1417
- if (node.name === symbol)
1418
- return true;
1419
- // File basename match (e.g., "product-card" matches "product-card.liquid")
1420
- if (node.kind === 'file' && node.name.replace(/\.[^.]+$/, '') === symbol)
1421
- return true;
1422
- // Qualified-name lookups: split on any supported separator. `\w` keeps
1423
- // identifier chars (incl. `_`) intact; everything else is treated as
1424
- // a separator we tolerate.
1425
- if (!/[.\/]|::/.test(symbol))
1426
- return false;
1427
- const parts = symbol.split(/::|[./]/).filter((p) => p.length > 0);
1428
- if (parts.length < 2)
1429
- return false;
1430
- const lastPart = parts[parts.length - 1];
1431
- if (node.name !== lastPart)
1432
- return false;
1433
- // Stage 1: qualified-name suffix match. The extractor joins the
1434
- // semantic hierarchy with `::`, so `Session.request` and
1435
- // `Session::request` both become `Session::request` here.
1436
- const colonSuffix = parts.join('::');
1437
- if (node.qualifiedName.includes(colonSuffix))
1438
- return true;
1439
- // Stage 2: file-path containment. Rust modules and Python packages
1440
- // are not in `qualifiedName` — they're encoded in the file path. So
1441
- // `stage_apply::run` matches a `run` in any file whose path
1442
- // contains a `stage_apply` segment (with or without an extension).
1443
- //
1444
- // Filter out Rust path prefixes that have no file-system equivalent.
1445
- const containerHints = parts.slice(0, -1).filter((p) => !RUST_PATH_PREFIXES.has(p));
1446
- if (containerHints.length === 0)
1447
- return false;
1448
- const segments = node.filePath.split('/').filter((s) => s.length > 0);
1449
- return containerHints.every((hint) => segments.some((seg) => seg === hint || seg.replace(/\.[^.]+$/, '') === hint));
1450
- }
1451
- findSymbol(cg, symbol) {
1452
- // Use higher limit for qualified lookups (e.g., "Session.request",
1453
- // "stage_apply::run") since the target may rank lower in FTS when
1454
- // there are many partial matches across the qualifier parts.
1455
- const isQualified = /[.\/]|::/.test(symbol);
1456
- const limit = isQualified ? 50 : 10;
1457
- let results = cg.searchNodes(symbol, { limit });
1458
- // FTS strips colons as a special char, so `stage_apply::run` searches
1459
- // for the literal `stage_applyrun` and finds nothing. Re-search by
1460
- // the bare last part and let `matchesSymbol` filter by qualifier.
1461
- if (isQualified && results.length === 0) {
1462
- const tail = lastQualifierPart(symbol);
1463
- if (tail && tail !== symbol)
1464
- results = cg.searchNodes(tail, { limit });
1465
- }
1466
- if (results.length === 0 || !results[0]) {
1467
- return null;
1468
- }
1469
- const exactMatches = results.filter(r => this.matchesSymbol(r.node, symbol));
1470
- if (exactMatches.length === 1) {
1471
- return { node: exactMatches[0].node, note: '' };
1472
- }
1473
- if (exactMatches.length > 1) {
1474
- // Multiple exact matches - pick first, note the others
1475
- const picked = exactMatches[0].node;
1476
- const others = exactMatches.slice(1).map(r => `${r.node.name} (${r.node.kind}) at ${r.node.filePath}:${r.node.startLine}`);
1477
- const note = `\n\n> **Note:** ${exactMatches.length} symbols named "${symbol}". Showing results for \`${picked.filePath}:${picked.startLine}\`. Others: ${others.join(', ')}`;
1478
- return { node: picked, note };
1479
- }
1480
- // No exact match. For qualified lookups, don't silently fall back
1481
- // to a fuzzy result — the user typed a specific qualifier, and
1482
- // resolving `stage_apply::nonexistent_fn` to the unrelated
1483
- // `stage_apply.rs` file would be actively misleading (#173).
1484
- if (isQualified)
1485
- return null;
1486
- return { node: results[0].node, note: '' };
1487
- }
1488
- /**
1489
- * Find ALL symbols matching a name. Used by callers/callees/impact to aggregate
1490
- * results across all matching symbols (e.g., multiple classes with an `execute` method).
1491
- */
1492
- findAllSymbols(cg, symbol) {
1493
- let results = cg.searchNodes(symbol, { limit: 50 });
1494
- // Mirror the fallback in `findSymbol` for qualified queries — FTS
1495
- // strips colons, so a module-qualified lookup needs a second pass
1496
- // by the bare last part.
1497
- if (results.length === 0 && /[.\/]|::/.test(symbol)) {
1498
- const tail = lastQualifierPart(symbol);
1499
- if (tail && tail !== symbol)
1500
- results = cg.searchNodes(tail, { limit: 50 });
1501
- }
1502
- if (results.length === 0) {
1503
- return { nodes: [], note: '' };
1504
- }
1505
- const exactMatches = results.filter(r => this.matchesSymbol(r.node, symbol));
1506
- if (exactMatches.length <= 1) {
1507
- const node = exactMatches[0]?.node ?? results[0].node;
1508
- return { nodes: [node], note: '' };
1509
- }
1510
- const locations = exactMatches.map(r => `${r.node.kind} at ${r.node.filePath}:${r.node.startLine}`);
1511
- const note = `\n\n> **Note:** Aggregated results across ${exactMatches.length} symbols named "${symbol}": ${locations.join(', ')}`;
1512
- return { nodes: exactMatches.map(r => r.node), note };
1513
- }
1514
- /**
1515
- * Truncate output if it exceeds the maximum length
1516
- */
1517
- truncateOutput(text) {
1518
- if (text.length <= MAX_OUTPUT_LENGTH)
1519
- return text;
1520
- const truncated = text.slice(0, MAX_OUTPUT_LENGTH);
1521
- const lastNewline = truncated.lastIndexOf('\n');
1522
- const cutPoint = lastNewline > MAX_OUTPUT_LENGTH * 0.8 ? lastNewline : MAX_OUTPUT_LENGTH;
1523
- return truncated.slice(0, cutPoint) + '\n\n... (output truncated)';
1524
- }
1525
- // =========================================================================
1526
- // Formatting helpers (compact by default to reduce context usage)
1527
- // =========================================================================
1528
- formatSearchResults(results) {
1529
- const lines = [`## Search Results (${results.length} found)`, ''];
1530
- for (const result of results) {
1531
- const { node } = result;
1532
- const location = node.startLine ? `:${node.startLine}` : '';
1533
- // Compact format: one line per result with key info
1534
- lines.push(`### ${node.name} (${node.kind})`);
1535
- lines.push(`${node.filePath}${location}`);
1536
- if (node.signature)
1537
- lines.push(`\`${node.signature}\``);
1538
- lines.push('');
1539
- }
1540
- return lines.join('\n');
1541
- }
1542
- formatNodeList(nodes, title) {
1543
- const lines = [`## ${title} (${nodes.length} found)`, ''];
1544
- for (const node of nodes) {
1545
- const location = node.startLine ? `:${node.startLine}` : '';
1546
- // Compact: just name, kind, location
1547
- lines.push(`- ${node.name} (${node.kind}) - ${node.filePath}${location}`);
1548
- }
1549
- return lines.join('\n');
1550
- }
1551
- formatImpact(symbol, impact) {
1552
- const nodeCount = impact.nodes.size;
1553
- // Compact format: just list affected symbols grouped by file
1554
- const lines = [
1555
- `## Impact: "${symbol}" affects ${nodeCount} symbols`,
1556
- '',
1557
- ];
1558
- // Group by file
1559
- const byFile = new Map();
1560
- for (const node of impact.nodes.values()) {
1561
- const existing = byFile.get(node.filePath) || [];
1562
- existing.push(node);
1563
- byFile.set(node.filePath, existing);
1564
- }
1565
- for (const [file, nodes] of byFile) {
1566
- lines.push(`**${file}:**`);
1567
- // Compact: inline list
1568
- const nodeList = nodes.map(n => `${n.name}:${n.startLine}`).join(', ');
1569
- lines.push(nodeList);
1570
- lines.push('');
1571
- }
1572
- return lines.join('\n');
1573
- }
1574
- /**
1575
- * Build a compact structural outline of a container symbol from its
1576
- * indexed children (methods, fields, properties, …) — name, kind,
1577
- * line number, and signature — so the agent gets the shape of a class
1578
- * without the full source of every method. Returns '' when the container
1579
- * has no indexed children, so the caller can fall back to full source.
1580
- */
1581
- buildContainerOutline(cg, node) {
1582
- const children = cg.getChildren(node.id)
1583
- .filter(c => c.kind !== 'import' && c.kind !== 'export')
1584
- .sort((a, b) => (a.startLine ?? 0) - (b.startLine ?? 0));
1585
- if (children.length === 0)
1586
- return '';
1587
- const lines = [`**Members (${children.length}):**`, ''];
1588
- for (const c of children) {
1589
- const loc = c.startLine ? `:${c.startLine}` : '';
1590
- const sig = c.signature ? ` — \`${c.signature}\`` : '';
1591
- lines.push(`- ${c.name} (${c.kind})${loc}${sig}`);
1592
- }
1593
- return lines.join('\n');
1594
- }
1595
- formatNodeDetails(node, code, outline) {
1596
- const location = node.startLine ? `:${node.startLine}` : '';
1597
- const lines = [
1598
- `## ${node.name} (${node.kind})`,
1599
- '',
1600
- `**Location:** ${node.filePath}${location}`,
1601
- ];
1602
- if (node.signature) {
1603
- lines.push(`**Signature:** \`${node.signature}\``);
1604
- }
1605
- // Only include docstring if it's short and useful
1606
- if (node.docstring && node.docstring.length < 200) {
1607
- lines.push('', node.docstring);
1608
- }
1609
- if (outline) {
1610
- lines.push('', outline, '', `> Structural outline only. Read \`${node.filePath}\` or call codegraph_node on a specific member for its body.`);
1611
- }
1612
- else if (code) {
1613
- lines.push('', '```' + node.language, code, '```');
1614
- }
1615
- return lines.join('\n');
1616
- }
1617
- formatTaskContext(context) {
1618
- return context.summary || 'No context found';
1619
- }
1620
- textResult(text) {
1621
- return {
1622
- content: [{ type: 'text', text }],
1623
- };
1624
- }
1625
- errorResult(message) {
1626
- return {
1627
- content: [{ type: 'text', text: `Error: ${message}` }],
1628
- isError: true,
1629
- };
1630
- }
1631
- }
1632
- exports.ToolHandler = ToolHandler;
1633
- //# sourceMappingURL=tools.js.map