@colbymchenry/codegraph 0.6.8 → 0.7.2

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 (207) hide show
  1. package/README.md +179 -476
  2. package/dist/bin/codegraph.d.ts +0 -5
  3. package/dist/bin/codegraph.d.ts.map +1 -1
  4. package/dist/bin/codegraph.js +217 -237
  5. package/dist/bin/codegraph.js.map +1 -1
  6. package/dist/bin/uninstall.d.ts +0 -1
  7. package/dist/bin/uninstall.d.ts.map +1 -1
  8. package/dist/bin/uninstall.js +3 -29
  9. package/dist/bin/uninstall.js.map +1 -1
  10. package/dist/context/index.d.ts +3 -5
  11. package/dist/context/index.d.ts.map +1 -1
  12. package/dist/context/index.js +497 -46
  13. package/dist/context/index.js.map +1 -1
  14. package/dist/db/migrations.d.ts +1 -1
  15. package/dist/db/migrations.d.ts.map +1 -1
  16. package/dist/db/migrations.js +10 -1
  17. package/dist/db/migrations.js.map +1 -1
  18. package/dist/db/queries.d.ts +53 -0
  19. package/dist/db/queries.d.ts.map +1 -1
  20. package/dist/db/queries.js +244 -14
  21. package/dist/db/queries.js.map +1 -1
  22. package/dist/db/schema.sql +1 -16
  23. package/dist/extraction/dfm-extractor.d.ts +31 -0
  24. package/dist/extraction/dfm-extractor.d.ts.map +1 -0
  25. package/dist/extraction/dfm-extractor.js +151 -0
  26. package/dist/extraction/dfm-extractor.js.map +1 -0
  27. package/dist/extraction/grammars.d.ts +9 -1
  28. package/dist/extraction/grammars.d.ts.map +1 -1
  29. package/dist/extraction/grammars.js +34 -2
  30. package/dist/extraction/grammars.js.map +1 -1
  31. package/dist/extraction/index.d.ts +7 -1
  32. package/dist/extraction/index.d.ts.map +1 -1
  33. package/dist/extraction/index.js +373 -22
  34. package/dist/extraction/index.js.map +1 -1
  35. package/dist/extraction/languages/c-cpp.d.ts +4 -0
  36. package/dist/extraction/languages/c-cpp.d.ts.map +1 -0
  37. package/dist/extraction/languages/c-cpp.js +126 -0
  38. package/dist/extraction/languages/c-cpp.js.map +1 -0
  39. package/dist/extraction/languages/csharp.d.ts +3 -0
  40. package/dist/extraction/languages/csharp.d.ts.map +1 -0
  41. package/dist/extraction/languages/csharp.js +72 -0
  42. package/dist/extraction/languages/csharp.js.map +1 -0
  43. package/dist/extraction/languages/dart.d.ts +3 -0
  44. package/dist/extraction/languages/dart.d.ts.map +1 -0
  45. package/dist/extraction/languages/dart.js +192 -0
  46. package/dist/extraction/languages/dart.js.map +1 -0
  47. package/dist/extraction/languages/go.d.ts +3 -0
  48. package/dist/extraction/languages/go.d.ts.map +1 -0
  49. package/dist/extraction/languages/go.js +58 -0
  50. package/dist/extraction/languages/go.js.map +1 -0
  51. package/dist/extraction/languages/index.d.ts +10 -0
  52. package/dist/extraction/languages/index.d.ts.map +1 -0
  53. package/dist/extraction/languages/index.js +43 -0
  54. package/dist/extraction/languages/index.js.map +1 -0
  55. package/dist/extraction/languages/java.d.ts +3 -0
  56. package/dist/extraction/languages/java.d.ts.map +1 -0
  57. package/dist/extraction/languages/java.js +64 -0
  58. package/dist/extraction/languages/java.js.map +1 -0
  59. package/dist/extraction/languages/javascript.d.ts +3 -0
  60. package/dist/extraction/languages/javascript.d.ts.map +1 -0
  61. package/dist/extraction/languages/javascript.js +90 -0
  62. package/dist/extraction/languages/javascript.js.map +1 -0
  63. package/dist/extraction/languages/kotlin.d.ts +3 -0
  64. package/dist/extraction/languages/kotlin.d.ts.map +1 -0
  65. package/dist/extraction/languages/kotlin.js +253 -0
  66. package/dist/extraction/languages/kotlin.js.map +1 -0
  67. package/dist/extraction/languages/pascal.d.ts +3 -0
  68. package/dist/extraction/languages/pascal.d.ts.map +1 -0
  69. package/dist/extraction/languages/pascal.js +66 -0
  70. package/dist/extraction/languages/pascal.js.map +1 -0
  71. package/dist/extraction/languages/php.d.ts +3 -0
  72. package/dist/extraction/languages/php.d.ts.map +1 -0
  73. package/dist/extraction/languages/php.js +107 -0
  74. package/dist/extraction/languages/php.js.map +1 -0
  75. package/dist/extraction/languages/python.d.ts +3 -0
  76. package/dist/extraction/languages/python.d.ts.map +1 -0
  77. package/dist/extraction/languages/python.js +56 -0
  78. package/dist/extraction/languages/python.js.map +1 -0
  79. package/dist/extraction/languages/ruby.d.ts +3 -0
  80. package/dist/extraction/languages/ruby.d.ts.map +1 -0
  81. package/dist/extraction/languages/ruby.js +114 -0
  82. package/dist/extraction/languages/ruby.js.map +1 -0
  83. package/dist/extraction/languages/rust.d.ts +3 -0
  84. package/dist/extraction/languages/rust.d.ts.map +1 -0
  85. package/dist/extraction/languages/rust.js +109 -0
  86. package/dist/extraction/languages/rust.js.map +1 -0
  87. package/dist/extraction/languages/swift.d.ts +3 -0
  88. package/dist/extraction/languages/swift.d.ts.map +1 -0
  89. package/dist/extraction/languages/swift.js +91 -0
  90. package/dist/extraction/languages/swift.js.map +1 -0
  91. package/dist/extraction/languages/typescript.d.ts +3 -0
  92. package/dist/extraction/languages/typescript.d.ts.map +1 -0
  93. package/dist/extraction/languages/typescript.js +129 -0
  94. package/dist/extraction/languages/typescript.js.map +1 -0
  95. package/dist/extraction/liquid-extractor.d.ts +52 -0
  96. package/dist/extraction/liquid-extractor.d.ts.map +1 -0
  97. package/dist/extraction/liquid-extractor.js +313 -0
  98. package/dist/extraction/liquid-extractor.js.map +1 -0
  99. package/dist/extraction/parse-worker.d.ts +8 -0
  100. package/dist/extraction/parse-worker.d.ts.map +1 -0
  101. package/dist/extraction/parse-worker.js +57 -0
  102. package/dist/extraction/parse-worker.js.map +1 -0
  103. package/dist/extraction/svelte-extractor.d.ts +47 -0
  104. package/dist/extraction/svelte-extractor.d.ts.map +1 -0
  105. package/dist/extraction/svelte-extractor.js +230 -0
  106. package/dist/extraction/svelte-extractor.js.map +1 -0
  107. package/dist/extraction/tree-sitter-helpers.d.ts +28 -0
  108. package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -0
  109. package/dist/extraction/tree-sitter-helpers.js +103 -0
  110. package/dist/extraction/tree-sitter-helpers.js.map +1 -0
  111. package/dist/extraction/tree-sitter-types.d.ts +179 -0
  112. package/dist/extraction/tree-sitter-types.d.ts.map +1 -0
  113. package/dist/extraction/tree-sitter-types.js +10 -0
  114. package/dist/extraction/tree-sitter-types.js.map +1 -0
  115. package/dist/extraction/tree-sitter.d.ts +67 -125
  116. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  117. package/dist/extraction/tree-sitter.js +1052 -1855
  118. package/dist/extraction/tree-sitter.js.map +1 -1
  119. package/dist/graph/traversal.d.ts.map +1 -1
  120. package/dist/graph/traversal.js +20 -2
  121. package/dist/graph/traversal.js.map +1 -1
  122. package/dist/index.d.ts +29 -53
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js +88 -114
  125. package/dist/index.js.map +1 -1
  126. package/dist/installer/claude-md-template.d.ts +1 -1
  127. package/dist/installer/claude-md-template.d.ts.map +1 -1
  128. package/dist/installer/claude-md-template.js +15 -15
  129. package/dist/installer/config-writer.d.ts +1 -10
  130. package/dist/installer/config-writer.d.ts.map +1 -1
  131. package/dist/installer/config-writer.js +0 -79
  132. package/dist/installer/config-writer.js.map +1 -1
  133. package/dist/installer/index.d.ts +3 -4
  134. package/dist/installer/index.d.ts.map +1 -1
  135. package/dist/installer/index.js +118 -116
  136. package/dist/installer/index.js.map +1 -1
  137. package/dist/mcp/index.d.ts +5 -0
  138. package/dist/mcp/index.d.ts.map +1 -1
  139. package/dist/mcp/index.js +25 -1
  140. package/dist/mcp/index.js.map +1 -1
  141. package/dist/mcp/tools.d.ts +33 -0
  142. package/dist/mcp/tools.d.ts.map +1 -1
  143. package/dist/mcp/tools.js +405 -21
  144. package/dist/mcp/tools.js.map +1 -1
  145. package/dist/resolution/frameworks/csharp.js +29 -84
  146. package/dist/resolution/frameworks/csharp.js.map +1 -1
  147. package/dist/resolution/frameworks/express.js +44 -48
  148. package/dist/resolution/frameworks/express.js.map +1 -1
  149. package/dist/resolution/frameworks/go.js +34 -70
  150. package/dist/resolution/frameworks/go.js.map +1 -1
  151. package/dist/resolution/frameworks/java.js +29 -87
  152. package/dist/resolution/frameworks/java.js.map +1 -1
  153. package/dist/resolution/frameworks/laravel.js +6 -6
  154. package/dist/resolution/frameworks/laravel.js.map +1 -1
  155. package/dist/resolution/frameworks/python.js +33 -98
  156. package/dist/resolution/frameworks/python.js.map +1 -1
  157. package/dist/resolution/frameworks/react.js +53 -76
  158. package/dist/resolution/frameworks/react.js.map +1 -1
  159. package/dist/resolution/frameworks/ruby.js +12 -24
  160. package/dist/resolution/frameworks/ruby.js.map +1 -1
  161. package/dist/resolution/frameworks/rust.js +26 -66
  162. package/dist/resolution/frameworks/rust.js.map +1 -1
  163. package/dist/resolution/frameworks/svelte.js +11 -31
  164. package/dist/resolution/frameworks/svelte.js.map +1 -1
  165. package/dist/resolution/frameworks/swift.js +42 -160
  166. package/dist/resolution/frameworks/swift.js.map +1 -1
  167. package/dist/resolution/index.d.ts +19 -6
  168. package/dist/resolution/index.d.ts.map +1 -1
  169. package/dist/resolution/index.js +300 -141
  170. package/dist/resolution/index.js.map +1 -1
  171. package/dist/resolution/name-matcher.d.ts +5 -0
  172. package/dist/resolution/name-matcher.d.ts.map +1 -1
  173. package/dist/resolution/name-matcher.js +148 -8
  174. package/dist/resolution/name-matcher.js.map +1 -1
  175. package/dist/resolution/types.d.ts +1 -1
  176. package/dist/resolution/types.d.ts.map +1 -1
  177. package/dist/search/query-utils.d.ts +26 -1
  178. package/dist/search/query-utils.d.ts.map +1 -1
  179. package/dist/search/query-utils.js +209 -9
  180. package/dist/search/query-utils.js.map +1 -1
  181. package/dist/sync/index.d.ts +2 -4
  182. package/dist/sync/index.d.ts.map +1 -1
  183. package/dist/sync/index.js +4 -3
  184. package/dist/sync/index.js.map +1 -1
  185. package/dist/sync/watcher.d.ts +81 -0
  186. package/dist/sync/watcher.d.ts.map +1 -0
  187. package/dist/sync/watcher.js +184 -0
  188. package/dist/sync/watcher.js.map +1 -0
  189. package/dist/types.d.ts +2 -0
  190. package/dist/types.d.ts.map +1 -1
  191. package/dist/types.js.map +1 -1
  192. package/dist/ui/shimmer-progress.d.ts +11 -0
  193. package/dist/ui/shimmer-progress.d.ts.map +1 -0
  194. package/dist/ui/shimmer-progress.js +90 -0
  195. package/dist/ui/shimmer-progress.js.map +1 -0
  196. package/dist/ui/shimmer-worker.d.ts +2 -0
  197. package/dist/ui/shimmer-worker.d.ts.map +1 -0
  198. package/dist/ui/shimmer-worker.js +112 -0
  199. package/dist/ui/shimmer-worker.js.map +1 -0
  200. package/dist/ui/types.d.ts +17 -0
  201. package/dist/ui/types.d.ts.map +1 -0
  202. package/dist/ui/types.js +3 -0
  203. package/dist/ui/types.js.map +1 -0
  204. package/dist/vectors/embedder.js +1 -1
  205. package/dist/vectors/embedder.js.map +1 -1
  206. package/package.json +7 -12
  207. package/scripts/postinstall.js +0 -68
@@ -4,6 +4,12 @@
4
4
  * Defines the tools exposed by the CodeGraph MCP server.
5
5
  */
6
6
  import CodeGraph from '../index';
7
+ /**
8
+ * Calculate the recommended number of codegraph_explore calls based on project size.
9
+ * Larger codebases need more exploration calls to cover their surface area,
10
+ * but smaller ones should use fewer to avoid unnecessary overhead.
11
+ */
12
+ export declare function getExploreBudget(fileCount: number): number;
7
13
  /**
8
14
  * MCP Tool definition
9
15
  */
@@ -59,6 +65,12 @@ export declare class ToolHandler {
59
65
  * Whether a default CodeGraph instance is available
60
66
  */
61
67
  hasDefaultCodeGraph(): boolean;
68
+ /**
69
+ * Get tool definitions with dynamic descriptions based on project size.
70
+ * The codegraph_explore tool description includes a budget recommendation
71
+ * scaled to the number of indexed files.
72
+ */
73
+ getTools(): ToolDefinition[];
62
74
  /**
63
75
  * Get CodeGraph instance for a project
64
76
  *
@@ -105,6 +117,16 @@ export declare class ToolHandler {
105
117
  * Handle codegraph_impact
106
118
  */
107
119
  private handleImpact;
120
+ /** Maximum output for explore tool — sized to stay under MCP client token limits (~10k tokens) */
121
+ private static readonly EXPLORE_MAX_OUTPUT;
122
+ /**
123
+ * Handle codegraph_explore — deep exploration in a single call
124
+ *
125
+ * Strategy: find relevant symbols via graph traversal, group by file,
126
+ * then read contiguous file sections covering all symbols per file.
127
+ * This replaces multiple codegraph_node + Read calls.
128
+ */
129
+ private handleExplore;
108
130
  /**
109
131
  * Handle codegraph_node
110
132
  */
@@ -137,7 +159,18 @@ export declare class ToolHandler {
137
159
  * Find a symbol by name, handling disambiguation when multiple matches exist.
138
160
  * Returns the best match and a note about alternatives if any.
139
161
  */
162
+ /**
163
+ * Check if a node matches a symbol query, supporting both simple names and
164
+ * qualified "Parent.child" notation (e.g., "Session.request" matches a method
165
+ * named "request" inside a class named "Session").
166
+ */
167
+ private matchesSymbol;
140
168
  private findSymbol;
169
+ /**
170
+ * Find ALL symbols matching a name. Used by callers/callees/impact to aggregate
171
+ * results across all matching symbols (e.g., multiple classes with an `execute` method).
172
+ */
173
+ private findAllSymbols;
141
174
  /**
142
175
  * Truncate output if it exceeds the maximum length
143
176
  */
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,SAAuC,MAAM,UAAU,CAAC;AAyB/D;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;QAC3C,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAUD;;;;;;;GAOG;AACH,eAAO,MAAM,KAAK,EAAE,cAAc,EA8KjC,CAAC;AAEF;;;;;GAKG;AACH,qBAAa,WAAW;IAIV,OAAO,CAAC,EAAE;IAFtB,OAAO,CAAC,YAAY,CAAqC;gBAErC,EAAE,EAAE,SAAS,GAAG,IAAI;IAExC;;OAEG;IACH,mBAAmB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAIxC;;OAEG;IACH,mBAAmB,IAAI,OAAO;IAI9B;;;;;;;;OAQG;IACH,OAAO,CAAC,YAAY;IAqCpB;;OAEG;IACH,QAAQ,IAAI,IAAI;IAOhB;;OAEG;IACH,OAAO,CAAC,cAAc;IAOtB;;OAEG;IACG,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;IA2BnF;;OAEG;YACW,YAAY;IAsB1B;;OAEG;YACW,aAAa;IAmC3B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAyB/B;;OAEG;YACW,aAAa;IAuB3B;;OAEG;YACW,aAAa;IAuB3B;;OAEG;YACW,YAAY;IAkB1B;;OAEG;YACW,UAAU;IAuBxB;;OAEG;YACW,YAAY;IA+B1B;;OAEG;YACW,WAAW;IAgDzB;;OAEG;IACH,OAAO,CAAC,WAAW;IAUnB;;OAEG;IACH,OAAO,CAAC,eAAe;IAcvB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA6B1B;;OAEG;IACH,OAAO,CAAC,eAAe;IA4EvB;;;OAGG;IACH,OAAO,CAAC,UAAU;IA4BlB;;OAEG;IACH,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,YAAY;IA4BpB,OAAO,CAAC,iBAAiB;IAwBzB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,WAAW;CAMpB"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,SAAuC,MAAM,UAAU,CAAC;AAW/D;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAM1D;AAgBD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;QAC3C,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAUD;;;;;;;GAOG;AACH,eAAO,MAAM,KAAK,EAAE,cAAc,EAkMjC,CAAC;AAEF;;;;;GAKG;AACH,qBAAa,WAAW;IAIV,OAAO,CAAC,EAAE;IAFtB,OAAO,CAAC,YAAY,CAAqC;gBAErC,EAAE,EAAE,SAAS,GAAG,IAAI;IAExC;;OAEG;IACH,mBAAmB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAIxC;;OAEG;IACH,mBAAmB,IAAI,OAAO;IAI9B;;;;OAIG;IACH,QAAQ,IAAI,cAAc,EAAE;IAqB5B;;;;;;;;OAQG;IACH,OAAO,CAAC,YAAY;IAqCpB;;OAEG;IACH,QAAQ,IAAI,IAAI;IAOhB;;OAEG;IACH,OAAO,CAAC,cAAc;IAOtB;;OAEG;IACG,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;IA6BnF;;OAEG;YACW,YAAY;IAsB1B;;OAEG;YACW,aAAa;IAmC3B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAyB/B;;OAEG;YACW,aAAa;IAgC3B;;OAEG;YACW,aAAa;IAgC3B;;OAEG;YACW,YAAY;IAyC1B,kGAAkG;IAClG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAEnD;;;;;;OAMG;YACW,aAAa;IAgQ3B;;OAEG;YACW,UAAU;IAuBxB;;OAEG;YACW,YAAY;IA+B1B;;OAEG;YACW,WAAW;IAgDzB;;OAEG;IACH,OAAO,CAAC,WAAW;IAUnB;;OAEG;IACH,OAAO,CAAC,eAAe;IAcvB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA6B1B;;OAEG;IACH,OAAO,CAAC,eAAe;IA4EvB;;;OAGG;IACH;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,UAAU;IA8BlB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAqBtB;;OAEG;IACH,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,YAAY;IA4BpB,OAAO,CAAC,iBAAiB;IAwBzB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,WAAW;CAMpB"}
package/dist/mcp/tools.js CHANGED
@@ -39,6 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
39
39
  })();
40
40
  Object.defineProperty(exports, "__esModule", { value: true });
41
41
  exports.ToolHandler = exports.tools = void 0;
42
+ exports.getExploreBudget = getExploreBudget;
42
43
  const index_1 = __importStar(require("../index"));
43
44
  const crypto_1 = require("crypto");
44
45
  const fs_1 = require("fs");
@@ -47,6 +48,22 @@ const os_1 = require("os");
47
48
  const path_1 = require("path");
48
49
  /** Maximum output length to prevent context bloat (characters) */
49
50
  const MAX_OUTPUT_LENGTH = 15000;
51
+ /**
52
+ * Calculate the recommended number of codegraph_explore calls based on project size.
53
+ * Larger codebases need more exploration calls to cover their surface area,
54
+ * but smaller ones should use fewer to avoid unnecessary overhead.
55
+ */
56
+ function getExploreBudget(fileCount) {
57
+ if (fileCount < 500)
58
+ return 1;
59
+ if (fileCount < 5000)
60
+ return 2;
61
+ if (fileCount < 15000)
62
+ return 3;
63
+ if (fileCount < 25000)
64
+ return 4;
65
+ return 5;
66
+ }
50
67
  /**
51
68
  * Mark a Claude session as having consulted MCP tools.
52
69
  * This enables Grep/Glob/Bash commands that would otherwise be blocked.
@@ -207,6 +224,26 @@ exports.tools = [
207
224
  required: ['symbol'],
208
225
  },
209
226
  },
227
+ {
228
+ name: 'codegraph_explore',
229
+ description: 'Deep exploration tool — returns comprehensive context for a topic in a SINGLE call. Groups all relevant source code by file (contiguous sections, not snippets), includes a relationship map, and uses deeper graph traversal. Designed to replace multiple codegraph_node + file Read calls. Use this instead of codegraph_context when you need thorough understanding.',
230
+ inputSchema: {
231
+ type: 'object',
232
+ properties: {
233
+ query: {
234
+ type: 'string',
235
+ description: 'What you want to understand (e.g., "undo redo system", "authentication flow", "how routing works")',
236
+ },
237
+ maxFiles: {
238
+ type: 'number',
239
+ description: 'Maximum number of files to include source code from (default: 12)',
240
+ default: 12,
241
+ },
242
+ projectPath: projectPathProperty,
243
+ },
244
+ required: ['query'],
245
+ },
246
+ },
210
247
  {
211
248
  name: 'codegraph_status',
212
249
  description: 'Get the status of the CodeGraph index, including statistics about indexed files, nodes, and edges.',
@@ -276,6 +313,31 @@ class ToolHandler {
276
313
  hasDefaultCodeGraph() {
277
314
  return this.cg !== null;
278
315
  }
316
+ /**
317
+ * Get tool definitions with dynamic descriptions based on project size.
318
+ * The codegraph_explore tool description includes a budget recommendation
319
+ * scaled to the number of indexed files.
320
+ */
321
+ getTools() {
322
+ if (!this.cg)
323
+ return exports.tools;
324
+ try {
325
+ const stats = this.cg.getStats();
326
+ const budget = getExploreBudget(stats.fileCount);
327
+ return exports.tools.map(tool => {
328
+ if (tool.name === 'codegraph_explore') {
329
+ return {
330
+ ...tool,
331
+ description: `${tool.description} Budget: make at most ${budget} calls for this project (${stats.fileCount.toLocaleString()} files indexed).`,
332
+ };
333
+ }
334
+ return tool;
335
+ });
336
+ }
337
+ catch {
338
+ return exports.tools;
339
+ }
340
+ }
279
341
  /**
280
342
  * Get CodeGraph instance for a project
281
343
  *
@@ -350,6 +412,8 @@ class ToolHandler {
350
412
  return await this.handleCallees(args);
351
413
  case 'codegraph_impact':
352
414
  return await this.handleImpact(args);
415
+ case 'codegraph_explore':
416
+ return await this.handleExplore(args);
353
417
  case 'codegraph_node':
354
418
  return await this.handleNode(args);
355
419
  case 'codegraph_status':
@@ -452,16 +516,25 @@ class ToolHandler {
452
516
  return symbol;
453
517
  const cg = this.getCodeGraph(args.projectPath);
454
518
  const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
455
- const match = this.findSymbol(cg, symbol);
456
- if (!match) {
519
+ const allMatches = this.findAllSymbols(cg, symbol);
520
+ if (allMatches.nodes.length === 0) {
457
521
  return this.textResult(`Symbol "${symbol}" not found in the codebase`);
458
522
  }
459
- const callers = cg.getCallers(match.node.id);
460
- if (callers.length === 0) {
461
- return this.textResult(`No callers found for "${symbol}"${match.note}`);
523
+ // Aggregate callers across all matching symbols
524
+ const seen = new Set();
525
+ const allCallers = [];
526
+ for (const node of allMatches.nodes) {
527
+ for (const c of cg.getCallers(node.id)) {
528
+ if (!seen.has(c.node.id)) {
529
+ seen.add(c.node.id);
530
+ allCallers.push(c.node);
531
+ }
532
+ }
533
+ }
534
+ if (allCallers.length === 0) {
535
+ return this.textResult(`No callers found for "${symbol}"${allMatches.note}`);
462
536
  }
463
- const callerNodes = callers.slice(0, limit).map(c => c.node);
464
- const formatted = this.formatNodeList(callerNodes, `Callers of ${symbol}`) + match.note;
537
+ const formatted = this.formatNodeList(allCallers.slice(0, limit), `Callers of ${symbol}`) + allMatches.note;
465
538
  return this.textResult(this.truncateOutput(formatted));
466
539
  }
467
540
  /**
@@ -473,16 +546,25 @@ class ToolHandler {
473
546
  return symbol;
474
547
  const cg = this.getCodeGraph(args.projectPath);
475
548
  const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
476
- const match = this.findSymbol(cg, symbol);
477
- if (!match) {
549
+ const allMatches = this.findAllSymbols(cg, symbol);
550
+ if (allMatches.nodes.length === 0) {
478
551
  return this.textResult(`Symbol "${symbol}" not found in the codebase`);
479
552
  }
480
- const callees = cg.getCallees(match.node.id);
481
- if (callees.length === 0) {
482
- return this.textResult(`No callees found for "${symbol}"${match.note}`);
553
+ // Aggregate callees across all matching symbols
554
+ const seen = new Set();
555
+ const allCallees = [];
556
+ for (const node of allMatches.nodes) {
557
+ for (const c of cg.getCallees(node.id)) {
558
+ if (!seen.has(c.node.id)) {
559
+ seen.add(c.node.id);
560
+ allCallees.push(c.node);
561
+ }
562
+ }
563
+ }
564
+ if (allCallees.length === 0) {
565
+ return this.textResult(`No callees found for "${symbol}"${allMatches.note}`);
483
566
  }
484
- const calleeNodes = callees.slice(0, limit).map(c => c.node);
485
- const formatted = this.formatNodeList(calleeNodes, `Callees of ${symbol}`) + match.note;
567
+ const formatted = this.formatNodeList(allCallees.slice(0, limit), `Callees of ${symbol}`) + allMatches.note;
486
568
  return this.textResult(this.truncateOutput(formatted));
487
569
  }
488
570
  /**
@@ -494,14 +576,275 @@ class ToolHandler {
494
576
  return symbol;
495
577
  const cg = this.getCodeGraph(args.projectPath);
496
578
  const depth = (0, utils_1.clamp)(args.depth || 2, 1, 10);
497
- const match = this.findSymbol(cg, symbol);
498
- if (!match) {
579
+ const allMatches = this.findAllSymbols(cg, symbol);
580
+ if (allMatches.nodes.length === 0) {
499
581
  return this.textResult(`Symbol "${symbol}" not found in the codebase`);
500
582
  }
501
- const impact = cg.getImpactRadius(match.node.id, depth);
502
- const formatted = this.formatImpact(symbol, impact) + match.note;
583
+ // Aggregate impact across all matching symbols
584
+ const mergedNodes = new Map();
585
+ const mergedEdges = [];
586
+ const seenEdges = new Set();
587
+ for (const node of allMatches.nodes) {
588
+ const impact = cg.getImpactRadius(node.id, depth);
589
+ for (const [id, n] of impact.nodes) {
590
+ mergedNodes.set(id, n);
591
+ }
592
+ for (const e of impact.edges) {
593
+ const key = `${e.source}->${e.target}:${e.kind}`;
594
+ if (!seenEdges.has(key)) {
595
+ seenEdges.add(key);
596
+ mergedEdges.push(e);
597
+ }
598
+ }
599
+ }
600
+ const mergedImpact = {
601
+ nodes: mergedNodes,
602
+ edges: mergedEdges,
603
+ roots: allMatches.nodes.map(n => n.id),
604
+ };
605
+ const formatted = this.formatImpact(symbol, mergedImpact) + allMatches.note;
503
606
  return this.textResult(this.truncateOutput(formatted));
504
607
  }
608
+ /** Maximum output for explore tool — sized to stay under MCP client token limits (~10k tokens) */
609
+ static EXPLORE_MAX_OUTPUT = 35000;
610
+ /**
611
+ * Handle codegraph_explore — deep exploration in a single call
612
+ *
613
+ * Strategy: find relevant symbols via graph traversal, group by file,
614
+ * then read contiguous file sections covering all symbols per file.
615
+ * This replaces multiple codegraph_node + Read calls.
616
+ */
617
+ async handleExplore(args) {
618
+ const query = this.validateString(args.query, 'query');
619
+ if (typeof query !== 'string')
620
+ return query;
621
+ const cg = this.getCodeGraph(args.projectPath);
622
+ const maxFiles = (0, utils_1.clamp)(args.maxFiles || 12, 1, 20);
623
+ const projectRoot = cg.getProjectRoot();
624
+ // Step 1: Find relevant context with generous parameters
625
+ const subgraph = await cg.findRelevantContext(query, {
626
+ searchLimit: 8,
627
+ traversalDepth: 3,
628
+ maxNodes: 80,
629
+ minScore: 0.2,
630
+ });
631
+ if (subgraph.nodes.size === 0) {
632
+ return this.textResult(`No relevant code found for "${query}"`);
633
+ }
634
+ // Step 2: Group nodes by file, score by relevance
635
+ const fileGroups = new Map();
636
+ const entryNodeIds = new Set(subgraph.roots);
637
+ // Build a set of nodes directly connected to entry points (depth 1)
638
+ const connectedToEntry = new Set();
639
+ for (const edge of subgraph.edges) {
640
+ if (entryNodeIds.has(edge.source))
641
+ connectedToEntry.add(edge.target);
642
+ if (entryNodeIds.has(edge.target))
643
+ connectedToEntry.add(edge.source);
644
+ }
645
+ for (const node of subgraph.nodes.values()) {
646
+ // Skip import/export nodes — they add noise without information
647
+ if (node.kind === 'import' || node.kind === 'export')
648
+ continue;
649
+ const group = fileGroups.get(node.filePath) || { nodes: [], score: 0 };
650
+ group.nodes.push(node);
651
+ // Score: entry point nodes worth 10, directly connected worth 3, others worth 1
652
+ if (entryNodeIds.has(node.id)) {
653
+ group.score += 10;
654
+ }
655
+ else if (connectedToEntry.has(node.id)) {
656
+ group.score += 3;
657
+ }
658
+ else {
659
+ group.score += 1;
660
+ }
661
+ fileGroups.set(node.filePath, group);
662
+ }
663
+ // Only include files that have entry points or nodes directly connected to entry points
664
+ const relevantFiles = [...fileGroups.entries()].filter(([, group]) => group.score >= 3);
665
+ // Extract query terms for relevance checking
666
+ const queryTerms = query.toLowerCase().split(/\s+/).filter(t => t.length >= 3);
667
+ // Sort files: highest relevance first, deprioritize low-value files
668
+ const sortedFiles = relevantFiles.sort((a, b) => {
669
+ const aPath = a[0].toLowerCase();
670
+ const bPath = b[0].toLowerCase();
671
+ // Check if any node name or file path relates to query terms
672
+ const hasQueryRelevance = (filePath, nodes) => {
673
+ const fp = filePath.toLowerCase();
674
+ if (queryTerms.some(t => fp.includes(t)))
675
+ return true;
676
+ return nodes.some(n => queryTerms.some(t => n.name.toLowerCase().includes(t)));
677
+ };
678
+ const aRelevant = hasQueryRelevance(aPath, a[1].nodes);
679
+ const bRelevant = hasQueryRelevance(bPath, b[1].nodes);
680
+ if (aRelevant !== bRelevant)
681
+ return aRelevant ? -1 : 1;
682
+ // Deprioritize test files, icon files, and i18n files
683
+ const isLowValue = (p) => /\/(tests?|__tests?__|spec)\//i.test(p) ||
684
+ /\bicons?\b/i.test(p) ||
685
+ /\bi18n\b/i.test(p);
686
+ const aLow = isLowValue(aPath);
687
+ const bLow = isLowValue(bPath);
688
+ if (aLow !== bLow)
689
+ return aLow ? 1 : -1;
690
+ if (a[1].score !== b[1].score)
691
+ return b[1].score - a[1].score;
692
+ return b[1].nodes.length - a[1].nodes.length;
693
+ });
694
+ // Step 3: Build relationship map
695
+ const lines = [
696
+ `## Exploration: ${query}`,
697
+ '',
698
+ `Found ${subgraph.nodes.size} symbols across ${fileGroups.size} files.`,
699
+ '',
700
+ ];
701
+ // Relationship map — show how symbols connect
702
+ const significantEdges = subgraph.edges.filter(e => e.kind !== 'contains' // skip contains — it's implied by file grouping
703
+ );
704
+ if (significantEdges.length > 0) {
705
+ lines.push('### Relationships');
706
+ lines.push('');
707
+ // Group edges by kind for readability
708
+ const byKind = new Map();
709
+ for (const edge of significantEdges) {
710
+ const sourceNode = subgraph.nodes.get(edge.source);
711
+ const targetNode = subgraph.nodes.get(edge.target);
712
+ if (!sourceNode || !targetNode)
713
+ continue;
714
+ const group = byKind.get(edge.kind) || [];
715
+ group.push({ source: sourceNode.name, target: targetNode.name });
716
+ byKind.set(edge.kind, group);
717
+ }
718
+ for (const [kind, edges] of byKind) {
719
+ // Show up to 15 relationships per kind
720
+ const shown = edges.slice(0, 15);
721
+ lines.push(`**${kind}:**`);
722
+ for (const e of shown) {
723
+ lines.push(`- ${e.source} → ${e.target}`);
724
+ }
725
+ if (edges.length > 15) {
726
+ lines.push(`- ... and ${edges.length - 15} more`);
727
+ }
728
+ lines.push('');
729
+ }
730
+ }
731
+ // Step 4: Read contiguous file sections
732
+ lines.push('### Source Code');
733
+ lines.push('');
734
+ let totalChars = lines.join('\n').length;
735
+ let filesIncluded = 0;
736
+ for (const [filePath, group] of sortedFiles) {
737
+ if (filesIncluded >= maxFiles)
738
+ break;
739
+ if (totalChars > ToolHandler.EXPLORE_MAX_OUTPUT * 0.9)
740
+ break;
741
+ const absPath = (0, utils_1.validatePathWithinRoot)(projectRoot, filePath);
742
+ if (!absPath || !(0, fs_1.existsSync)(absPath))
743
+ continue;
744
+ let fileContent;
745
+ try {
746
+ fileContent = (0, fs_1.readFileSync)(absPath, 'utf-8');
747
+ }
748
+ catch {
749
+ continue;
750
+ }
751
+ const fileLines = fileContent.split('\n');
752
+ const lang = group.nodes[0]?.language || '';
753
+ // Cluster nearby symbols to avoid reading huge gaps between distant symbols.
754
+ // Sort by start line, then merge overlapping/adjacent ranges (within 15 lines).
755
+ const ranges = group.nodes
756
+ .filter(n => n.startLine > 0 && n.endLine > 0)
757
+ .map(n => ({ start: n.startLine, end: n.endLine, name: n.name, kind: n.kind }))
758
+ .sort((a, b) => a.start - b.start);
759
+ if (ranges.length === 0)
760
+ continue;
761
+ const GAP_THRESHOLD = 15; // merge sections within 15 lines of each other
762
+ const clusters = [];
763
+ let current = { start: ranges[0].start, end: ranges[0].end, symbols: [`${ranges[0].name}(${ranges[0].kind})`] };
764
+ for (let i = 1; i < ranges.length; i++) {
765
+ const r = ranges[i];
766
+ if (r.start <= current.end + GAP_THRESHOLD) {
767
+ current.end = Math.max(current.end, r.end);
768
+ current.symbols.push(`${r.name}(${r.kind})`);
769
+ }
770
+ else {
771
+ clusters.push(current);
772
+ current = { start: r.start, end: r.end, symbols: [`${r.name}(${r.kind})`] };
773
+ }
774
+ }
775
+ clusters.push(current);
776
+ // Build file section output from clusters
777
+ const contextPadding = 3;
778
+ let fileSection = '';
779
+ const allSymbols = [];
780
+ for (const cluster of clusters) {
781
+ const startIdx = Math.max(0, cluster.start - 1 - contextPadding);
782
+ const endIdx = Math.min(fileLines.length, cluster.end + contextPadding);
783
+ const section = fileLines.slice(startIdx, endIdx).join('\n');
784
+ if (fileSection.length > 0) {
785
+ fileSection += '\n\n// ... (gap) ...\n\n';
786
+ }
787
+ fileSection += section;
788
+ allSymbols.push(...cluster.symbols);
789
+ }
790
+ // Skip if this section would blow the output limit
791
+ if (totalChars + fileSection.length + 200 > ToolHandler.EXPLORE_MAX_OUTPUT) {
792
+ const budget = ToolHandler.EXPLORE_MAX_OUTPUT - totalChars - 200;
793
+ if (budget < 500)
794
+ break;
795
+ const trimmed = fileSection.slice(0, budget) + '\n// ... trimmed ...';
796
+ lines.push(`#### ${filePath} — ${allSymbols.join(', ')}`);
797
+ lines.push('');
798
+ lines.push('```' + lang);
799
+ lines.push(trimmed);
800
+ lines.push('```');
801
+ lines.push('');
802
+ totalChars += trimmed.length + 200;
803
+ filesIncluded++;
804
+ break;
805
+ }
806
+ lines.push(`#### ${filePath} — ${allSymbols.join(', ')}`);
807
+ lines.push('');
808
+ lines.push('```' + lang);
809
+ lines.push(fileSection);
810
+ lines.push('```');
811
+ lines.push('');
812
+ totalChars += fileSection.length + 200;
813
+ filesIncluded++;
814
+ }
815
+ // Add remaining files as references (from both relevant and peripheral files)
816
+ const remainingRelevant = sortedFiles.slice(filesIncluded);
817
+ const peripheralFiles = [...fileGroups.entries()]
818
+ .filter(([, group]) => group.score < 3)
819
+ .sort((a, b) => b[1].score - a[1].score);
820
+ const remainingFiles = [...remainingRelevant, ...peripheralFiles];
821
+ if (remainingFiles.length > 0) {
822
+ lines.push('### Additional relevant files (not shown)');
823
+ lines.push('');
824
+ for (const [filePath, group] of remainingFiles.slice(0, 10)) {
825
+ const symbols = group.nodes.map(n => `${n.name}:${n.startLine}`).join(', ');
826
+ lines.push(`- ${filePath}: ${symbols}`);
827
+ }
828
+ if (remainingFiles.length > 10) {
829
+ lines.push(`- ... and ${remainingFiles.length - 10} more files`);
830
+ }
831
+ }
832
+ // Add completeness signal so agents know they don't need to re-read these files
833
+ lines.push('');
834
+ lines.push('---');
835
+ 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.`);
836
+ // Add explore budget note based on project size
837
+ try {
838
+ const stats = cg.getStats();
839
+ const budget = getExploreBudget(stats.fileCount);
840
+ lines.push('');
841
+ lines.push(`> **Explore budget: ${budget} calls max for this project (${stats.fileCount.toLocaleString()} files indexed).** Stop exploring and synthesize your answer once you've used ${budget} calls — do NOT make additional explore calls beyond this budget.`);
842
+ }
843
+ catch {
844
+ // Stats unavailable — skip budget note
845
+ }
846
+ return this.textResult(lines.join('\n'));
847
+ }
505
848
  /**
506
849
  * Handle codegraph_node
507
850
  */
@@ -710,13 +1053,36 @@ class ToolHandler {
710
1053
  * Find a symbol by name, handling disambiguation when multiple matches exist.
711
1054
  * Returns the best match and a note about alternatives if any.
712
1055
  */
1056
+ /**
1057
+ * Check if a node matches a symbol query, supporting both simple names and
1058
+ * qualified "Parent.child" notation (e.g., "Session.request" matches a method
1059
+ * named "request" inside a class named "Session").
1060
+ */
1061
+ matchesSymbol(node, symbol) {
1062
+ // Simple name match
1063
+ if (node.name === symbol)
1064
+ return true;
1065
+ // File basename match (e.g., "product-card" matches "product-card.liquid")
1066
+ if (node.kind === 'file' && node.name.replace(/\.[^.]+$/, '') === symbol)
1067
+ return true;
1068
+ // Qualified name match: "Parent.child" → look for "::Parent::child" in qualified_name
1069
+ if (symbol.includes('.')) {
1070
+ const parts = symbol.split('.');
1071
+ const qualifiedSuffix = parts.join('::');
1072
+ if (node.qualifiedName.includes(qualifiedSuffix))
1073
+ return true;
1074
+ }
1075
+ return false;
1076
+ }
713
1077
  findSymbol(cg, symbol) {
714
- const results = cg.searchNodes(symbol, { limit: 10 });
1078
+ // Use higher limit for qualified lookups (e.g., "Session.request") since the
1079
+ // target may rank lower in FTS when there are many partial matches
1080
+ const limit = symbol.includes('.') ? 50 : 10;
1081
+ const results = cg.searchNodes(symbol, { limit });
715
1082
  if (results.length === 0 || !results[0]) {
716
1083
  return null;
717
1084
  }
718
- // If only one result, or first is an exact name match, use it directly
719
- const exactMatches = results.filter(r => r.node.name === symbol);
1085
+ const exactMatches = results.filter(r => this.matchesSymbol(r.node, symbol));
720
1086
  if (exactMatches.length === 1) {
721
1087
  return { node: exactMatches[0].node, note: '' };
722
1088
  }
@@ -730,6 +1096,24 @@ class ToolHandler {
730
1096
  // No exact match, use best fuzzy match
731
1097
  return { node: results[0].node, note: '' };
732
1098
  }
1099
+ /**
1100
+ * Find ALL symbols matching a name. Used by callers/callees/impact to aggregate
1101
+ * results across all matching symbols (e.g., multiple classes with an `execute` method).
1102
+ */
1103
+ findAllSymbols(cg, symbol) {
1104
+ const results = cg.searchNodes(symbol, { limit: 50 });
1105
+ if (results.length === 0) {
1106
+ return { nodes: [], note: '' };
1107
+ }
1108
+ const exactMatches = results.filter(r => this.matchesSymbol(r.node, symbol));
1109
+ if (exactMatches.length <= 1) {
1110
+ const node = exactMatches[0]?.node ?? results[0].node;
1111
+ return { nodes: [node], note: '' };
1112
+ }
1113
+ const locations = exactMatches.map(r => `${r.node.kind} at ${r.node.filePath}:${r.node.startLine}`);
1114
+ const note = `\n\n> **Note:** Aggregated results across ${exactMatches.length} symbols named "${symbol}": ${locations.join(', ')}`;
1115
+ return { nodes: exactMatches.map(r => r.node), note };
1116
+ }
733
1117
  /**
734
1118
  * Truncate output if it exceeds the maximum length
735
1119
  */