@vybestack/llxprt-code-tools 0.10.0-nightly.260613.1adad3b34

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 (364) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +294 -0
  3. package/dist/.last_build +0 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +2 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/src/__tests__/fixtures/filesystem-tool-fixtures.d.ts +29 -0
  8. package/dist/src/__tests__/fixtures/filesystem-tool-fixtures.js +30 -0
  9. package/dist/src/__tests__/fixtures/filesystem-tool-fixtures.js.map +1 -0
  10. package/dist/src/__tests__/fixtures/key-storage-fixtures.d.ts +39 -0
  11. package/dist/src/__tests__/fixtures/key-storage-fixtures.js +53 -0
  12. package/dist/src/__tests__/fixtures/key-storage-fixtures.js.map +1 -0
  13. package/dist/src/__tests__/fixtures/provider-formatting-fixtures.d.ts +140 -0
  14. package/dist/src/__tests__/fixtures/provider-formatting-fixtures.js +157 -0
  15. package/dist/src/__tests__/fixtures/provider-formatting-fixtures.js.map +1 -0
  16. package/dist/src/__tests__/red-test-helpers.d.ts +14 -0
  17. package/dist/src/__tests__/red-test-helpers.js +18 -0
  18. package/dist/src/__tests__/red-test-helpers.js.map +1 -0
  19. package/dist/src/formatters/IToolFormatter.d.ts +84 -0
  20. package/dist/src/formatters/IToolFormatter.js +6 -0
  21. package/dist/src/formatters/IToolFormatter.js.map +1 -0
  22. package/dist/src/formatters/ToolFormatter.d.ts +94 -0
  23. package/dist/src/formatters/ToolFormatter.js +379 -0
  24. package/dist/src/formatters/ToolFormatter.js.map +1 -0
  25. package/dist/src/formatters/ToolIdStrategy.d.ts +79 -0
  26. package/dist/src/formatters/ToolIdStrategy.js +173 -0
  27. package/dist/src/formatters/ToolIdStrategy.js.map +1 -0
  28. package/dist/src/formatters/doubleEscapeUtils.d.ts +46 -0
  29. package/dist/src/formatters/doubleEscapeUtils.js +223 -0
  30. package/dist/src/formatters/doubleEscapeUtils.js.map +1 -0
  31. package/dist/src/formatters/index.d.ts +21 -0
  32. package/dist/src/formatters/index.js +12 -0
  33. package/dist/src/formatters/index.js.map +1 -0
  34. package/dist/src/formatters/toolIdNormalization.d.ts +28 -0
  35. package/dist/src/formatters/toolIdNormalization.js +97 -0
  36. package/dist/src/formatters/toolIdNormalization.js.map +1 -0
  37. package/dist/src/formatters/toolNameUtils.d.ts +43 -0
  38. package/dist/src/formatters/toolNameUtils.js +143 -0
  39. package/dist/src/formatters/toolNameUtils.js.map +1 -0
  40. package/dist/src/index.d.ts +88 -0
  41. package/dist/src/index.js +68 -0
  42. package/dist/src/index.js.map +1 -0
  43. package/dist/src/interfaces/IAsyncTaskService.d.ts +57 -0
  44. package/dist/src/interfaces/IAsyncTaskService.js +6 -0
  45. package/dist/src/interfaces/IAsyncTaskService.js.map +1 -0
  46. package/dist/src/interfaces/IIdeService.d.ts +62 -0
  47. package/dist/src/interfaces/IIdeService.js +6 -0
  48. package/dist/src/interfaces/IIdeService.js.map +1 -0
  49. package/dist/src/interfaces/ILspService.d.ts +54 -0
  50. package/dist/src/interfaces/ILspService.js +6 -0
  51. package/dist/src/interfaces/ILspService.js.map +1 -0
  52. package/dist/src/interfaces/IMcpToolService.d.ts +47 -0
  53. package/dist/src/interfaces/IMcpToolService.js +6 -0
  54. package/dist/src/interfaces/IMcpToolService.js.map +1 -0
  55. package/dist/src/interfaces/IPromptRegistryService.d.ts +51 -0
  56. package/dist/src/interfaces/IPromptRegistryService.js +6 -0
  57. package/dist/src/interfaces/IPromptRegistryService.js.map +1 -0
  58. package/dist/src/interfaces/ISettingsService.d.ts +50 -0
  59. package/dist/src/interfaces/ISettingsService.js +6 -0
  60. package/dist/src/interfaces/ISettingsService.js.map +1 -0
  61. package/dist/src/interfaces/IShellExecutionService.d.ts +55 -0
  62. package/dist/src/interfaces/IShellExecutionService.js +6 -0
  63. package/dist/src/interfaces/IShellExecutionService.js.map +1 -0
  64. package/dist/src/interfaces/IShellToolHost.d.ts +176 -0
  65. package/dist/src/interfaces/IShellToolHost.js +6 -0
  66. package/dist/src/interfaces/IShellToolHost.js.map +1 -0
  67. package/dist/src/interfaces/ISkillService.d.ts +69 -0
  68. package/dist/src/interfaces/ISkillService.js +6 -0
  69. package/dist/src/interfaces/ISkillService.js.map +1 -0
  70. package/dist/src/interfaces/IStorageService.d.ts +42 -0
  71. package/dist/src/interfaces/IStorageService.js +6 -0
  72. package/dist/src/interfaces/IStorageService.js.map +1 -0
  73. package/dist/src/interfaces/ISubagentService.d.ts +112 -0
  74. package/dist/src/interfaces/ISubagentService.js +6 -0
  75. package/dist/src/interfaces/ISubagentService.js.map +1 -0
  76. package/dist/src/interfaces/ITaskToolHost.d.ts +185 -0
  77. package/dist/src/interfaces/ITaskToolHost.js +6 -0
  78. package/dist/src/interfaces/ITaskToolHost.js.map +1 -0
  79. package/dist/src/interfaces/ITodoService.d.ts +79 -0
  80. package/dist/src/interfaces/ITodoService.js +6 -0
  81. package/dist/src/interfaces/ITodoService.js.map +1 -0
  82. package/dist/src/interfaces/IToolHost.d.ts +91 -0
  83. package/dist/src/interfaces/IToolHost.js +6 -0
  84. package/dist/src/interfaces/IToolHost.js.map +1 -0
  85. package/dist/src/interfaces/IToolKeyStorage.d.ts +62 -0
  86. package/dist/src/interfaces/IToolKeyStorage.js +6 -0
  87. package/dist/src/interfaces/IToolKeyStorage.js.map +1 -0
  88. package/dist/src/interfaces/IToolMessageBus.d.ts +63 -0
  89. package/dist/src/interfaces/IToolMessageBus.js +6 -0
  90. package/dist/src/interfaces/IToolMessageBus.js.map +1 -0
  91. package/dist/src/interfaces/IToolRegistryHost.d.ts +43 -0
  92. package/dist/src/interfaces/IToolRegistryHost.js +6 -0
  93. package/dist/src/interfaces/IToolRegistryHost.js.map +1 -0
  94. package/dist/src/interfaces/IWebSearchService.d.ts +16 -0
  95. package/dist/src/interfaces/IWebSearchService.js +7 -0
  96. package/dist/src/interfaces/IWebSearchService.js.map +1 -0
  97. package/dist/src/interfaces/index.d.ts +33 -0
  98. package/dist/src/interfaces/index.js +6 -0
  99. package/dist/src/interfaces/index.js.map +1 -0
  100. package/dist/src/tools/activate-skill.d.ts +26 -0
  101. package/dist/src/tools/activate-skill.js +121 -0
  102. package/dist/src/tools/activate-skill.js.map +1 -0
  103. package/dist/src/tools/apply-patch.d.ts +65 -0
  104. package/dist/src/tools/apply-patch.js +528 -0
  105. package/dist/src/tools/apply-patch.js.map +1 -0
  106. package/dist/src/tools/ast-edit/ast-config.d.ts +67 -0
  107. package/dist/src/tools/ast-edit/ast-config.js +72 -0
  108. package/dist/src/tools/ast-edit/ast-config.js.map +1 -0
  109. package/dist/src/tools/ast-edit/ast-edit-invocation.d.ts +40 -0
  110. package/dist/src/tools/ast-edit/ast-edit-invocation.js +410 -0
  111. package/dist/src/tools/ast-edit/ast-edit-invocation.js.map +1 -0
  112. package/dist/src/tools/ast-edit/ast-query-extractor.d.ts +21 -0
  113. package/dist/src/tools/ast-edit/ast-query-extractor.js +178 -0
  114. package/dist/src/tools/ast-edit/ast-query-extractor.js.map +1 -0
  115. package/dist/src/tools/ast-edit/ast-read-file-invocation.d.ts +26 -0
  116. package/dist/src/tools/ast-edit/ast-read-file-invocation.js +149 -0
  117. package/dist/src/tools/ast-edit/ast-read-file-invocation.js.map +1 -0
  118. package/dist/src/tools/ast-edit/constants.d.ts +30 -0
  119. package/dist/src/tools/ast-edit/constants.js +36 -0
  120. package/dist/src/tools/ast-edit/constants.js.map +1 -0
  121. package/dist/src/tools/ast-edit/context-collector.d.ts +25 -0
  122. package/dist/src/tools/ast-edit/context-collector.js +115 -0
  123. package/dist/src/tools/ast-edit/context-collector.js.map +1 -0
  124. package/dist/src/tools/ast-edit/context-optimizer.d.ts +29 -0
  125. package/dist/src/tools/ast-edit/context-optimizer.js +86 -0
  126. package/dist/src/tools/ast-edit/context-optimizer.js.map +1 -0
  127. package/dist/src/tools/ast-edit/cross-file-analyzer.d.ts +41 -0
  128. package/dist/src/tools/ast-edit/cross-file-analyzer.js +294 -0
  129. package/dist/src/tools/ast-edit/cross-file-analyzer.js.map +1 -0
  130. package/dist/src/tools/ast-edit/edit-calculator.d.ts +71 -0
  131. package/dist/src/tools/ast-edit/edit-calculator.js +249 -0
  132. package/dist/src/tools/ast-edit/edit-calculator.js.map +1 -0
  133. package/dist/src/tools/ast-edit/edit-helpers.d.ts +22 -0
  134. package/dist/src/tools/ast-edit/edit-helpers.js +36 -0
  135. package/dist/src/tools/ast-edit/edit-helpers.js.map +1 -0
  136. package/dist/src/tools/ast-edit/language-analysis.d.ts +19 -0
  137. package/dist/src/tools/ast-edit/language-analysis.js +123 -0
  138. package/dist/src/tools/ast-edit/language-analysis.js.map +1 -0
  139. package/dist/src/tools/ast-edit/local-context-analyzer.d.ts +84 -0
  140. package/dist/src/tools/ast-edit/local-context-analyzer.js +267 -0
  141. package/dist/src/tools/ast-edit/local-context-analyzer.js.map +1 -0
  142. package/dist/src/tools/ast-edit/repository-context-provider.d.ts +22 -0
  143. package/dist/src/tools/ast-edit/repository-context-provider.js +139 -0
  144. package/dist/src/tools/ast-edit/repository-context-provider.js.map +1 -0
  145. package/dist/src/tools/ast-edit/types.d.ts +155 -0
  146. package/dist/src/tools/ast-edit/types.js +7 -0
  147. package/dist/src/tools/ast-edit/types.js.map +1 -0
  148. package/dist/src/tools/ast-edit/workspace-context-provider.d.ts +22 -0
  149. package/dist/src/tools/ast-edit/workspace-context-provider.js +39 -0
  150. package/dist/src/tools/ast-edit/workspace-context-provider.js.map +1 -0
  151. package/dist/src/tools/ast-edit.d.ts +43 -0
  152. package/dist/src/tools/ast-edit.js +183 -0
  153. package/dist/src/tools/ast-edit.js.map +1 -0
  154. package/dist/src/tools/ast-grep.d.ts +22 -0
  155. package/dist/src/tools/ast-grep.js +291 -0
  156. package/dist/src/tools/ast-grep.js.map +1 -0
  157. package/dist/src/tools/check-async-tasks.d.ts +46 -0
  158. package/dist/src/tools/check-async-tasks.js +241 -0
  159. package/dist/src/tools/check-async-tasks.js.map +1 -0
  160. package/dist/src/tools/codesearch.d.ts +25 -0
  161. package/dist/src/tools/codesearch.js +153 -0
  162. package/dist/src/tools/codesearch.js.map +1 -0
  163. package/dist/src/tools/delete_line_range.d.ts +41 -0
  164. package/dist/src/tools/delete_line_range.js +238 -0
  165. package/dist/src/tools/delete_line_range.js.map +1 -0
  166. package/dist/src/tools/direct-web-fetch.d.ts +22 -0
  167. package/dist/src/tools/direct-web-fetch.js +215 -0
  168. package/dist/src/tools/direct-web-fetch.js.map +1 -0
  169. package/dist/src/tools/edit-utils.d.ts +53 -0
  170. package/dist/src/tools/edit-utils.js +250 -0
  171. package/dist/src/tools/edit-utils.js.map +1 -0
  172. package/dist/src/tools/edit.d.ts +70 -0
  173. package/dist/src/tools/edit.js +816 -0
  174. package/dist/src/tools/edit.js.map +1 -0
  175. package/dist/src/tools/exa-web-search.d.ts +27 -0
  176. package/dist/src/tools/exa-web-search.js +153 -0
  177. package/dist/src/tools/exa-web-search.js.map +1 -0
  178. package/dist/src/tools/glob.d.ts +55 -0
  179. package/dist/src/tools/glob.js +284 -0
  180. package/dist/src/tools/glob.js.map +1 -0
  181. package/dist/src/tools/google-web-fetch.d.ts +34 -0
  182. package/dist/src/tools/google-web-fetch.js +417 -0
  183. package/dist/src/tools/google-web-fetch.js.map +1 -0
  184. package/dist/src/tools/google-web-search-invocation.d.ts +53 -0
  185. package/dist/src/tools/google-web-search-invocation.js +180 -0
  186. package/dist/src/tools/google-web-search-invocation.js.map +1 -0
  187. package/dist/src/tools/google-web-search.d.ts +16 -0
  188. package/dist/src/tools/google-web-search.js +34 -0
  189. package/dist/src/tools/google-web-search.js.map +1 -0
  190. package/dist/src/tools/grep.d.ts +56 -0
  191. package/dist/src/tools/grep.js +883 -0
  192. package/dist/src/tools/grep.js.map +1 -0
  193. package/dist/src/tools/insert_at_line.d.ts +41 -0
  194. package/dist/src/tools/insert_at_line.js +287 -0
  195. package/dist/src/tools/insert_at_line.js.map +1 -0
  196. package/dist/src/tools/list-subagents.d.ts +31 -0
  197. package/dist/src/tools/list-subagents.js +122 -0
  198. package/dist/src/tools/list-subagents.js.map +1 -0
  199. package/dist/src/tools/ls.d.ts +71 -0
  200. package/dist/src/tools/ls.js +238 -0
  201. package/dist/src/tools/ls.js.map +1 -0
  202. package/dist/src/tools/memoryTool.d.ts +66 -0
  203. package/dist/src/tools/memoryTool.js +435 -0
  204. package/dist/src/tools/memoryTool.js.map +1 -0
  205. package/dist/src/tools/modifiable-tool.d.ts +37 -0
  206. package/dist/src/tools/modifiable-tool.js +120 -0
  207. package/dist/src/tools/modifiable-tool.js.map +1 -0
  208. package/dist/src/tools/read-file.d.ts +49 -0
  209. package/dist/src/tools/read-file.js +279 -0
  210. package/dist/src/tools/read-file.js.map +1 -0
  211. package/dist/src/tools/read-many-files.d.ts +60 -0
  212. package/dist/src/tools/read-many-files.js +529 -0
  213. package/dist/src/tools/read-many-files.js.map +1 -0
  214. package/dist/src/tools/read_line_range.d.ts +47 -0
  215. package/dist/src/tools/read_line_range.js +248 -0
  216. package/dist/src/tools/read_line_range.js.map +1 -0
  217. package/dist/src/tools/ripGrep.d.ts +41 -0
  218. package/dist/src/tools/ripGrep.js +395 -0
  219. package/dist/src/tools/ripGrep.js.map +1 -0
  220. package/dist/src/tools/shell.d.ts +60 -0
  221. package/dist/src/tools/shell.js +735 -0
  222. package/dist/src/tools/shell.js.map +1 -0
  223. package/dist/src/tools/structural-analysis.d.ts +27 -0
  224. package/dist/src/tools/structural-analysis.js +1089 -0
  225. package/dist/src/tools/structural-analysis.js.map +1 -0
  226. package/dist/src/tools/stubs.d.ts +10 -0
  227. package/dist/src/tools/stubs.js +6 -0
  228. package/dist/src/tools/stubs.js.map +1 -0
  229. package/dist/src/tools/task.d.ts +41 -0
  230. package/dist/src/tools/task.js +195 -0
  231. package/dist/src/tools/task.js.map +1 -0
  232. package/dist/src/tools/todo-events.d.ts +22 -0
  233. package/dist/src/tools/todo-events.js +24 -0
  234. package/dist/src/tools/todo-events.js.map +1 -0
  235. package/dist/src/tools/todo-pause.d.ts +29 -0
  236. package/dist/src/tools/todo-pause.js +172 -0
  237. package/dist/src/tools/todo-pause.js.map +1 -0
  238. package/dist/src/tools/todo-read.d.ts +18 -0
  239. package/dist/src/tools/todo-read.js +98 -0
  240. package/dist/src/tools/todo-read.js.map +1 -0
  241. package/dist/src/tools/todo-store.d.ts +35 -0
  242. package/dist/src/tools/todo-store.js +124 -0
  243. package/dist/src/tools/todo-store.js.map +1 -0
  244. package/dist/src/tools/todo-write.d.ts +29 -0
  245. package/dist/src/tools/todo-write.js +366 -0
  246. package/dist/src/tools/todo-write.js.map +1 -0
  247. package/dist/src/tools/tool-registry.d.ts +156 -0
  248. package/dist/src/tools/tool-registry.js +623 -0
  249. package/dist/src/tools/tool-registry.js.map +1 -0
  250. package/dist/src/tools/tools.d.ts +403 -0
  251. package/dist/src/tools/tools.js +519 -0
  252. package/dist/src/tools/tools.js.map +1 -0
  253. package/dist/src/tools/write-file.d.ts +45 -0
  254. package/dist/src/tools/write-file.js +320 -0
  255. package/dist/src/tools/write-file.js.map +1 -0
  256. package/dist/src/types/index.d.ts +17 -0
  257. package/dist/src/types/index.js +9 -0
  258. package/dist/src/types/index.js.map +1 -0
  259. package/dist/src/types/provider-content-types.d.ts +56 -0
  260. package/dist/src/types/provider-content-types.js +6 -0
  261. package/dist/src/types/provider-content-types.js.map +1 -0
  262. package/dist/src/types/todo-schemas.d.ts +263 -0
  263. package/dist/src/types/todo-schemas.js +32 -0
  264. package/dist/src/types/todo-schemas.js.map +1 -0
  265. package/dist/src/types/tool-confirmation-types.d.ts +34 -0
  266. package/dist/src/types/tool-confirmation-types.js +29 -0
  267. package/dist/src/types/tool-confirmation-types.js.map +1 -0
  268. package/dist/src/types/tool-context.d.ts +31 -0
  269. package/dist/src/types/tool-context.js +6 -0
  270. package/dist/src/types/tool-context.js.map +1 -0
  271. package/dist/src/types/tool-error.d.ts +69 -0
  272. package/dist/src/types/tool-error.js +94 -0
  273. package/dist/src/types/tool-error.js.map +1 -0
  274. package/dist/src/types/tool-names.d.ts +57 -0
  275. package/dist/src/types/tool-names.js +64 -0
  276. package/dist/src/types/tool-names.js.map +1 -0
  277. package/dist/src/utils/EmojiFilter.d.ts +134 -0
  278. package/dist/src/utils/EmojiFilter.js +370 -0
  279. package/dist/src/utils/EmojiFilter.js.map +1 -0
  280. package/dist/src/utils/ast-grep-utils.d.ts +42 -0
  281. package/dist/src/utils/ast-grep-utils.js +140 -0
  282. package/dist/src/utils/ast-grep-utils.js.map +1 -0
  283. package/dist/src/utils/debugLogger.d.ts +11 -0
  284. package/dist/src/utils/debugLogger.js +16 -0
  285. package/dist/src/utils/debugLogger.js.map +1 -0
  286. package/dist/src/utils/diffOptions.d.ts +14 -0
  287. package/dist/src/utils/diffOptions.js +46 -0
  288. package/dist/src/utils/diffOptions.js.map +1 -0
  289. package/dist/src/utils/editor.d.ts +39 -0
  290. package/dist/src/utils/editor.js +212 -0
  291. package/dist/src/utils/editor.js.map +1 -0
  292. package/dist/src/utils/ensure-dirs.d.ts +10 -0
  293. package/dist/src/utils/ensure-dirs.js +16 -0
  294. package/dist/src/utils/ensure-dirs.js.map +1 -0
  295. package/dist/src/utils/errors.d.ts +59 -0
  296. package/dist/src/utils/errors.js +178 -0
  297. package/dist/src/utils/errors.js.map +1 -0
  298. package/dist/src/utils/fetch.d.ts +11 -0
  299. package/dist/src/utils/fetch.js +74 -0
  300. package/dist/src/utils/fetch.js.map +1 -0
  301. package/dist/src/utils/fileUtils.d.ts +32 -0
  302. package/dist/src/utils/fileUtils.js +363 -0
  303. package/dist/src/utils/fileUtils.js.map +1 -0
  304. package/dist/src/utils/fuzzy-replacer.d.ts +61 -0
  305. package/dist/src/utils/fuzzy-replacer.js +492 -0
  306. package/dist/src/utils/fuzzy-replacer.js.map +1 -0
  307. package/dist/src/utils/gitLineChanges.d.ts +12 -0
  308. package/dist/src/utils/gitLineChanges.js +171 -0
  309. package/dist/src/utils/gitLineChanges.js.map +1 -0
  310. package/dist/src/utils/gitUtils.d.ts +6 -0
  311. package/dist/src/utils/gitUtils.js +31 -0
  312. package/dist/src/utils/gitUtils.js.map +1 -0
  313. package/dist/src/utils/lsp-diagnostics-helper.d.ts +27 -0
  314. package/dist/src/utils/lsp-diagnostics-helper.js +62 -0
  315. package/dist/src/utils/lsp-diagnostics-helper.js.map +1 -0
  316. package/dist/src/utils/mediaUtils.d.ts +38 -0
  317. package/dist/src/utils/mediaUtils.js +22 -0
  318. package/dist/src/utils/mediaUtils.js.map +1 -0
  319. package/dist/src/utils/pathValidation.d.ts +7 -0
  320. package/dist/src/utils/pathValidation.js +39 -0
  321. package/dist/src/utils/pathValidation.js.map +1 -0
  322. package/dist/src/utils/paths.d.ts +7 -0
  323. package/dist/src/utils/paths.js +158 -0
  324. package/dist/src/utils/paths.js.map +1 -0
  325. package/dist/src/utils/resolveTextSearchTarget.d.ts +17 -0
  326. package/dist/src/utils/resolveTextSearchTarget.js +45 -0
  327. package/dist/src/utils/resolveTextSearchTarget.js.map +1 -0
  328. package/dist/src/utils/retry.d.ts +17 -0
  329. package/dist/src/utils/retry.js +185 -0
  330. package/dist/src/utils/retry.js.map +1 -0
  331. package/dist/src/utils/ripgrepPathResolver.d.ts +23 -0
  332. package/dist/src/utils/ripgrepPathResolver.js +179 -0
  333. package/dist/src/utils/ripgrepPathResolver.js.map +1 -0
  334. package/dist/src/utils/safeJsonStringify.d.ts +9 -0
  335. package/dist/src/utils/safeJsonStringify.js +21 -0
  336. package/dist/src/utils/safeJsonStringify.js.map +1 -0
  337. package/dist/src/utils/schemaValidator.d.ts +13 -0
  338. package/dist/src/utils/schemaValidator.js +275 -0
  339. package/dist/src/utils/schemaValidator.js.map +1 -0
  340. package/dist/src/utils/terminalSerializer.d.ts +33 -0
  341. package/dist/src/utils/terminalSerializer.js +6 -0
  342. package/dist/src/utils/terminalSerializer.js.map +1 -0
  343. package/dist/src/utils/todoContextTracker.d.ts +12 -0
  344. package/dist/src/utils/todoContextTracker.js +28 -0
  345. package/dist/src/utils/todoContextTracker.js.map +1 -0
  346. package/dist/src/utils/todoFormatter.d.ts +24 -0
  347. package/dist/src/utils/todoFormatter.js +157 -0
  348. package/dist/src/utils/todoFormatter.js.map +1 -0
  349. package/dist/src/utils/todoReminderService.d.ts +22 -0
  350. package/dist/src/utils/todoReminderService.js +43 -0
  351. package/dist/src/utils/todoReminderService.js.map +1 -0
  352. package/dist/src/utils/tool-key-storage-facade.d.ts +32 -0
  353. package/dist/src/utils/tool-key-storage-facade.js +56 -0
  354. package/dist/src/utils/tool-key-storage-facade.js.map +1 -0
  355. package/dist/src/utils/tool-key-storage-types.d.ts +56 -0
  356. package/dist/src/utils/tool-key-storage-types.js +56 -0
  357. package/dist/src/utils/tool-key-storage-types.js.map +1 -0
  358. package/dist/src/utils/toolOutputLimiter.d.ts +29 -0
  359. package/dist/src/utils/toolOutputLimiter.js +107 -0
  360. package/dist/src/utils/toolOutputLimiter.js.map +1 -0
  361. package/dist/src/utils/unicodeUtils.d.ts +55 -0
  362. package/dist/src/utils/unicodeUtils.js +129 -0
  363. package/dist/src/utils/unicodeUtils.js.map +1 -0
  364. package/package.json +75 -0
@@ -0,0 +1,1089 @@
1
+ /**
2
+ * Multi-hop AST-based code analysis tool.
3
+ * Analyzes code relationships: call graphs, type hierarchies, symbol references,
4
+ * module dependencies, and exports using @ast-grep/napi.
5
+ *
6
+ * This is name-based (not type-resolved) analysis. See overview.md for limitations.
7
+ *
8
+ * @plan PLAN-20260211-ASTGREP.P07
9
+ */
10
+ /* eslint-disable complexity, sonarjs/cognitive-complexity, max-lines -- Phase 5: legacy core boundary retained while larger decomposition continues. */
11
+ import * as path from 'node:path';
12
+ import { promises as fs } from 'node:fs';
13
+ import FastGlob from 'fast-glob';
14
+ import { BaseDeclarativeTool, BaseToolInvocation, Kind, } from './tools.js';
15
+ import { makeRelative } from '../utils/paths.js';
16
+ import { parse, getAstLanguage, LANGUAGE_MAP, } from '../utils/ast-grep-utils.js';
17
+ const VALID_MODES = [
18
+ 'callers',
19
+ 'callees',
20
+ 'definitions',
21
+ 'hierarchy',
22
+ 'references',
23
+ 'dependencies',
24
+ 'exports',
25
+ ];
26
+ const DEFAULT_DEPTH = 1;
27
+ const MAX_DEPTH = 5;
28
+ const DEFAULT_MAX_NODES = 50;
29
+ class StructuralAnalysisInvocation extends BaseToolInvocation {
30
+ host;
31
+ constructor(host, params, messageBus) {
32
+ super(params, messageBus);
33
+ this.host = host;
34
+ }
35
+ getDescription() {
36
+ const { mode, symbol } = this.params;
37
+ if (symbol)
38
+ return `${mode}: ${symbol}`;
39
+ return `${mode} analysis`;
40
+ }
41
+ makeError(message) {
42
+ return { llmContent: message, returnDisplay: message };
43
+ }
44
+ formatResult(analysisResult) {
45
+ const llmContent = JSON.stringify(analysisResult, null, 2);
46
+ const mode = analysisResult.mode;
47
+ const symbol = analysisResult.symbol ? ` for ${analysisResult.symbol}` : '';
48
+ const displayMessage = `${mode} analysis${symbol} complete${analysisResult.truncated ? ' (truncated)' : ''}`;
49
+ return {
50
+ llmContent,
51
+ returnDisplay: displayMessage,
52
+ metadata: analysisResult,
53
+ };
54
+ }
55
+ validateAndResolveParams() {
56
+ const { mode, language, symbol, depth, maxNodes, target, reverse } = this.params;
57
+ if (!VALID_MODES.includes(mode)) {
58
+ return this.makeError(`Error: Invalid mode "${mode}". Valid modes: ${VALID_MODES.join(', ')}`);
59
+ }
60
+ if (!language) {
61
+ return this.makeError('Error: `language` parameter is required.');
62
+ }
63
+ const resolvedLang = getAstLanguage(language);
64
+ if (!resolvedLang) {
65
+ return this.makeError(`Error: Unrecognized language "${language}". Supported: ${Object.keys(LANGUAGE_MAP).join(', ')}`);
66
+ }
67
+ const targetDir = this.host.getTargetDir();
68
+ let searchPath = this.params.path ?? target ?? targetDir;
69
+ if (!path.isAbsolute(searchPath)) {
70
+ searchPath = path.resolve(targetDir, searchPath);
71
+ }
72
+ const normalizedTarget = targetDir.endsWith(path.sep)
73
+ ? targetDir
74
+ : targetDir + path.sep;
75
+ if (searchPath !== targetDir && !searchPath.startsWith(normalizedTarget)) {
76
+ return this.makeError('Error: Path resolves outside the workspace root.');
77
+ }
78
+ const symbolModes = [
79
+ 'callers',
80
+ 'callees',
81
+ 'definitions',
82
+ 'hierarchy',
83
+ 'references',
84
+ ];
85
+ if (symbolModes.includes(mode) && !symbol) {
86
+ return this.makeError(`Error: \`symbol\` parameter is required for "${mode}" mode.`);
87
+ }
88
+ return {
89
+ mode,
90
+ resolvedLang,
91
+ searchPath,
92
+ targetDir,
93
+ symbol,
94
+ depth: Math.min(depth ?? DEFAULT_DEPTH, MAX_DEPTH),
95
+ maxNodes: maxNodes ?? DEFAULT_MAX_NODES,
96
+ reverse: reverse === true,
97
+ };
98
+ }
99
+ async dispatchMode(mode, resolvedLang, searchPath, targetDir, symbol, effectiveDepth, effectiveMaxNodes, reverse, signal) {
100
+ switch (mode) {
101
+ case 'definitions':
102
+ return this.executeDefinitions(symbol, resolvedLang, searchPath, targetDir, signal);
103
+ case 'hierarchy':
104
+ return this.executeHierarchy(symbol, resolvedLang, searchPath, targetDir, signal);
105
+ case 'callers':
106
+ return this.executeCallers(symbol, resolvedLang, searchPath, targetDir, effectiveDepth, effectiveMaxNodes, signal);
107
+ case 'callees':
108
+ return this.executeCallees(symbol, resolvedLang, searchPath, targetDir, effectiveDepth, effectiveMaxNodes, signal);
109
+ case 'references':
110
+ return this.executeReferences(symbol, resolvedLang, searchPath, targetDir, signal);
111
+ case 'dependencies':
112
+ return this.executeDependencies(searchPath, resolvedLang, targetDir, reverse, signal);
113
+ case 'exports':
114
+ return this.executeExports(searchPath, resolvedLang, targetDir, signal);
115
+ default:
116
+ throw new Error(`Mode "${mode}" is not implemented.`);
117
+ }
118
+ }
119
+ async execute(signal) {
120
+ const params = this.validateAndResolveParams();
121
+ if ('llmContent' in params)
122
+ return params;
123
+ const { mode, resolvedLang, searchPath, targetDir, symbol, depth: effectiveDepth, maxNodes: effectiveMaxNodes, reverse, } = params;
124
+ try {
125
+ const analysisResult = await this.dispatchMode(mode, resolvedLang, searchPath, targetDir, symbol, effectiveDepth, effectiveMaxNodes, reverse, signal);
126
+ return this.formatResult(analysisResult);
127
+ }
128
+ catch (err) {
129
+ if (err instanceof Error && err.message.endsWith('is not implemented.')) {
130
+ return this.makeError(`Error: ${err.message}`);
131
+ }
132
+ const msg = err instanceof Error ? err.message : String(err);
133
+ return this.makeError(`Error in ${mode}: ${msg}`);
134
+ }
135
+ }
136
+ async getFiles(searchPath, lang) {
137
+ const stat = await fs.stat(searchPath).catch(() => null);
138
+ if (stat !== null && stat.isFile() === true) {
139
+ return [searchPath];
140
+ }
141
+ const extensions = this.getExtensionsForLanguage(lang);
142
+ return FastGlob(extensions.map((ext) => `**/*.${ext}`), {
143
+ cwd: searchPath,
144
+ absolute: true,
145
+ dot: false,
146
+ ignore: ['**/node_modules/**', '**/.git/**'],
147
+ });
148
+ }
149
+ getExtensionsForLanguage(lang) {
150
+ const exts = [];
151
+ for (const [ext, mapped] of Object.entries(LANGUAGE_MAP)) {
152
+ if (mapped === lang)
153
+ exts.push(ext);
154
+ }
155
+ return exts.length > 0 ? exts : ['*'];
156
+ }
157
+ escapeRegex(s) {
158
+ // eslint-disable-next-line sonarjs/regular-expr -- Static regex reviewed for lint hardening; behavior preserved.
159
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
160
+ }
161
+ /**
162
+ * AST node kinds that represent function-like containers.
163
+ * Used for callers mode to find the scope a call lives in.
164
+ */
165
+ static FUNCTION_CONTAINER_KINDS = new Set([
166
+ 'method_definition',
167
+ 'function_declaration',
168
+ 'arrow_function',
169
+ ]);
170
+ /**
171
+ * Extract a name from a function-like container node.
172
+ * - method_definition: first property_identifier child
173
+ * - function_declaration: first identifier child
174
+ * - arrow_function: name from parent variable_declarator's identifier
175
+ */
176
+ getContainerName(node) {
177
+ const kind = String(node.kind());
178
+ if (kind === 'method_definition') {
179
+ const nameNode = node
180
+ .children()
181
+ .find((c) => String(c.kind()) === 'property_identifier');
182
+ return nameNode?.text() ?? null;
183
+ }
184
+ if (kind === 'function_declaration') {
185
+ const nameNode = node
186
+ .children()
187
+ .find((c) => String(c.kind()) === 'identifier');
188
+ return nameNode?.text() ?? null;
189
+ }
190
+ if (kind === 'arrow_function') {
191
+ // Walk up to variable_declarator → get its identifier
192
+ const parent = node.parent();
193
+ if (parent && String(parent.kind()) === 'variable_declarator') {
194
+ const nameNode = parent
195
+ .children()
196
+ .find((c) => String(c.kind()) === 'identifier');
197
+ return nameNode?.text() ?? null;
198
+ }
199
+ return null;
200
+ }
201
+ return null;
202
+ }
203
+ async parseFile(filePath, lang) {
204
+ try {
205
+ const content = await fs.readFile(filePath, 'utf-8');
206
+ const result = parse(lang, content);
207
+ return { root: result.root(), content };
208
+ }
209
+ catch {
210
+ return null;
211
+ }
212
+ }
213
+ /**
214
+ * Extracts the function/method name from a call_expression node.
215
+ * Handles: `foo()` → "foo", `obj.bar()` → "bar", `a.b.c()` → "c"
216
+ */
217
+ extractCalleeName(callNode) {
218
+ const children = callNode.children();
219
+ if (children.length === 0)
220
+ return null;
221
+ const callee = children[0]; // first child of call_expression is the callee
222
+ const kind = String(callee.kind());
223
+ if (kind === 'identifier') {
224
+ return callee.text();
225
+ }
226
+ if (kind === 'member_expression') {
227
+ // Last property_identifier child is the method name
228
+ const props = callee
229
+ .children()
230
+ .filter((c) => String(c.kind()) === 'property_identifier');
231
+ if (props.length > 0) {
232
+ return props[props.length - 1].text();
233
+ }
234
+ }
235
+ return null;
236
+ }
237
+ // ===== DEFINITIONS =====
238
+ searchDefinitionPatterns(parsed, symbol, relPath, definitions) {
239
+ const patterns = [
240
+ { pat: `${symbol}($$$PARAMS) { $$$BODY }`, kind: 'method' },
241
+ { pat: `function ${symbol}($$$PARAMS) { $$$BODY }`, kind: 'function' },
242
+ { pat: `class ${symbol} { $$$BODY }`, kind: 'class' },
243
+ { pat: `class ${symbol} extends $PARENT { $$$BODY }`, kind: 'class' },
244
+ ];
245
+ for (const { pat, kind } of patterns) {
246
+ try {
247
+ const matches = parsed.root.findAll(pat);
248
+ for (const m of matches) {
249
+ const range = m.range();
250
+ definitions.push({
251
+ file: relPath,
252
+ line: range.start.line + 1,
253
+ kind,
254
+ text: m.text().substring(0, 200),
255
+ });
256
+ }
257
+ }
258
+ catch {
259
+ // Pattern may not be valid for all languages
260
+ }
261
+ }
262
+ }
263
+ searchDeclarationRules(parsed, symbol, relPath, definitions) {
264
+ try {
265
+ const ruleMatches = parsed.root.findAll({
266
+ rule: {
267
+ any: [
268
+ {
269
+ kind: 'class_declaration',
270
+ has: {
271
+ kind: 'type_identifier',
272
+ regex: `^${this.escapeRegex(symbol)}$`,
273
+ },
274
+ },
275
+ {
276
+ kind: 'interface_declaration',
277
+ has: {
278
+ kind: 'type_identifier',
279
+ regex: `^${this.escapeRegex(symbol)}$`,
280
+ },
281
+ },
282
+ {
283
+ kind: 'type_alias_declaration',
284
+ has: {
285
+ kind: 'type_identifier',
286
+ regex: `^${this.escapeRegex(symbol)}$`,
287
+ },
288
+ },
289
+ ],
290
+ },
291
+ });
292
+ for (const m of ruleMatches) {
293
+ const range = m.range();
294
+ const exists = definitions.some((d) => d.file === relPath && d.line === range.start.line + 1);
295
+ if (!exists) {
296
+ definitions.push({
297
+ file: relPath,
298
+ line: range.start.line + 1,
299
+ kind: String(m.kind()),
300
+ text: m.text().substring(0, 200),
301
+ });
302
+ }
303
+ }
304
+ }
305
+ catch {
306
+ // Rule may not apply
307
+ }
308
+ }
309
+ async executeDefinitions(symbol, lang, searchPath, workspaceRoot, signal) {
310
+ const files = await this.getFiles(searchPath, lang);
311
+ const definitions = [];
312
+ // eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
313
+ for (const file of files) {
314
+ if (signal.aborted)
315
+ break;
316
+ const parsed = await this.parseFile(file, lang);
317
+ if (!parsed)
318
+ continue;
319
+ const relPath = makeRelative(file, workspaceRoot);
320
+ this.searchDefinitionPatterns(parsed, symbol, relPath, definitions);
321
+ this.searchDeclarationRules(parsed, symbol, relPath, definitions);
322
+ }
323
+ return {
324
+ mode: 'definitions',
325
+ symbol,
326
+ truncated: false,
327
+ results: definitions,
328
+ };
329
+ }
330
+ // ===== HIERARCHY =====
331
+ findSymbolParents(parsed, symbol, extendsParent, implementsInterfaces) {
332
+ try {
333
+ const extendsMatches = parsed.root.findAll(`class ${symbol} extends $PARENT { $$$BODY }`);
334
+ for (const m of extendsMatches) {
335
+ const parent = m.getMatch('PARENT');
336
+ if (parent)
337
+ extendsParent.push(parent.text());
338
+ }
339
+ }
340
+ catch {
341
+ /* skip */
342
+ }
343
+ try {
344
+ const implMatches = parsed.root.findAll(`class ${symbol} implements $IFACE { $$$BODY }`);
345
+ for (const m of implMatches) {
346
+ const iface = m.getMatch('IFACE');
347
+ if (iface)
348
+ implementsInterfaces.push(iface.text());
349
+ }
350
+ }
351
+ catch {
352
+ /* skip */
353
+ }
354
+ }
355
+ findSymbolChildren(parsed, symbol, relPath, extendedBy, implementedBy) {
356
+ try {
357
+ const childMatches = parsed.root.findAll(`class $NAME extends ${symbol} { $$$BODY }`);
358
+ for (const m of childMatches) {
359
+ const name = m.getMatch('NAME');
360
+ if (name) {
361
+ extendedBy.push({
362
+ name: name.text(),
363
+ file: relPath,
364
+ line: m.range().start.line + 1,
365
+ });
366
+ }
367
+ }
368
+ }
369
+ catch {
370
+ /* skip */
371
+ }
372
+ try {
373
+ const implByMatches = parsed.root.findAll(`class $NAME implements ${symbol} { $$$BODY }`);
374
+ for (const m of implByMatches) {
375
+ const name = m.getMatch('NAME');
376
+ if (name) {
377
+ implementedBy.push({
378
+ name: name.text(),
379
+ file: relPath,
380
+ line: m.range().start.line + 1,
381
+ });
382
+ }
383
+ }
384
+ }
385
+ catch {
386
+ /* skip */
387
+ }
388
+ }
389
+ async executeHierarchy(symbol, lang, searchPath, workspaceRoot, signal) {
390
+ const files = await this.getFiles(searchPath, lang);
391
+ const extendsParent = [];
392
+ const implementsInterfaces = [];
393
+ const extendedBy = [];
394
+ const implementedBy = [];
395
+ // eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
396
+ for (const file of files) {
397
+ if (signal.aborted)
398
+ break;
399
+ const parsed = await this.parseFile(file, lang);
400
+ if (!parsed)
401
+ continue;
402
+ const relPath = makeRelative(file, workspaceRoot);
403
+ this.findSymbolParents(parsed, symbol, extendsParent, implementsInterfaces);
404
+ this.findSymbolChildren(parsed, symbol, relPath, extendedBy, implementedBy);
405
+ }
406
+ return {
407
+ mode: 'hierarchy',
408
+ symbol,
409
+ truncated: false,
410
+ results: {
411
+ extends: extendsParent,
412
+ implements: implementsInterfaces,
413
+ extendedBy,
414
+ implementedBy,
415
+ },
416
+ };
417
+ }
418
+ // ===== CALLERS =====
419
+ // @plan PLAN-20260211-ASTGREP.P09
420
+ findFunctionContainer(node) {
421
+ let container = node.parent();
422
+ while (container &&
423
+ !StructuralAnalysisInvocation.FUNCTION_CONTAINER_KINDS.has(String(container.kind()))) {
424
+ container = container.parent();
425
+ }
426
+ return container;
427
+ }
428
+ getViaContext(callNode) {
429
+ let node = callNode;
430
+ let parentNode = node.parent();
431
+ while (parentNode) {
432
+ const parentKind = String(parentNode.kind());
433
+ if (parentKind.includes('statement') ||
434
+ StructuralAnalysisInvocation.FUNCTION_CONTAINER_KINDS.has(parentKind)) {
435
+ break;
436
+ }
437
+ node = parentNode;
438
+ parentNode = node.parent();
439
+ }
440
+ return node.text().trim().substring(0, 200);
441
+ }
442
+ buildCallerEntry(callNode, sym, relPath, visited, via) {
443
+ const container = this.findFunctionContainer(callNode);
444
+ if (container === null) {
445
+ return undefined;
446
+ }
447
+ const methodName = this.getContainerName(container);
448
+ if (methodName === null || methodName === '' || methodName === sym) {
449
+ return undefined;
450
+ }
451
+ const key = `${methodName}@${relPath}`;
452
+ if (visited.has(key)) {
453
+ return undefined;
454
+ }
455
+ visited.add(key);
456
+ return {
457
+ method: methodName,
458
+ file: relPath,
459
+ line: container.range().start.line + 1,
460
+ via: via ?? this.getViaContext(callNode),
461
+ };
462
+ }
463
+ findMemberCallCallers(parsed, sym, relPath, visited, ctx) {
464
+ const results = [];
465
+ try {
466
+ const memberCalls = parsed.root.findAll({
467
+ rule: {
468
+ kind: 'member_expression',
469
+ has: {
470
+ kind: 'property_identifier',
471
+ regex: `^${this.escapeRegex(sym)}$`,
472
+ },
473
+ },
474
+ });
475
+ for (const callNode of memberCalls) {
476
+ if (ctx.nodesVisited >= ctx.maxNodes) {
477
+ ctx.truncated = true;
478
+ break;
479
+ }
480
+ const entry = this.buildCallerEntry(callNode, sym, relPath, visited);
481
+ if (entry !== undefined) {
482
+ ctx.nodesVisited++;
483
+ results.push(entry);
484
+ }
485
+ }
486
+ }
487
+ catch {
488
+ /* skip */
489
+ }
490
+ return results;
491
+ }
492
+ findDirectCallCallers(parsed, sym, relPath, visited, ctx) {
493
+ const results = [];
494
+ try {
495
+ const directCallNodes = parsed.root.findAll(`${sym}($$$ARGS)`);
496
+ for (const callNode of directCallNodes) {
497
+ const entry = this.buildCallerEntry(callNode, sym, relPath, visited, `${sym}(...)`);
498
+ if (entry !== undefined) {
499
+ ctx.nodesVisited++;
500
+ results.push(entry);
501
+ }
502
+ }
503
+ }
504
+ catch {
505
+ /* skip */
506
+ }
507
+ return results;
508
+ }
509
+ async findCallersOfFile(file, lang, sym, workspaceRoot, visited, ctx) {
510
+ if (ctx.signal.aborted || ctx.nodesVisited >= ctx.maxNodes)
511
+ return [];
512
+ const parsed = await this.parseFile(file, lang);
513
+ if (!parsed)
514
+ return [];
515
+ const relPath = makeRelative(file, workspaceRoot);
516
+ const memberResults = this.findMemberCallCallers(parsed, sym, relPath, visited, ctx);
517
+ const directResults = this.findDirectCallCallers(parsed, sym, relPath, visited, ctx);
518
+ return [...memberResults, ...directResults];
519
+ }
520
+ async executeCallers(symbol, lang, searchPath, workspaceRoot, depth, maxNodes, signal) {
521
+ const files = await this.getFiles(searchPath, lang);
522
+ const visited = new Set();
523
+ let nodesVisited = 0;
524
+ let truncated = false;
525
+ const findCallersOf = async (sym, currentDepth) => {
526
+ if (currentDepth <= 0 || nodesVisited >= maxNodes || signal.aborted) {
527
+ if (nodesVisited >= maxNodes)
528
+ truncated = true;
529
+ return [];
530
+ }
531
+ const callers = [];
532
+ const ctx = { nodesVisited, maxNodes, truncated, signal };
533
+ const syncTraversalState = () => {
534
+ nodesVisited = ctx.nodesVisited;
535
+ truncated = ctx.truncated;
536
+ };
537
+ // eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
538
+ for (const file of files) {
539
+ const fileResults = await this.findCallersOfFile(file, lang, sym, workspaceRoot, visited, ctx);
540
+ for (const r of fileResults) {
541
+ const entry = {
542
+ method: r.method,
543
+ file: r.file,
544
+ line: r.line,
545
+ via: r.via,
546
+ };
547
+ if (currentDepth > 1 && ctx.nodesVisited < maxNodes) {
548
+ syncTraversalState();
549
+ entry.callers = await findCallersOf(r.method, currentDepth - 1);
550
+ ctx.nodesVisited = nodesVisited;
551
+ ctx.truncated = truncated;
552
+ }
553
+ callers.push(entry);
554
+ }
555
+ }
556
+ nodesVisited = ctx.nodesVisited;
557
+ truncated = ctx.truncated;
558
+ return callers;
559
+ };
560
+ const results = await findCallersOf(symbol, depth);
561
+ return {
562
+ mode: 'callers',
563
+ symbol,
564
+ truncated,
565
+ results,
566
+ };
567
+ }
568
+ // ===== CALLEES =====
569
+ // @plan PLAN-20260211-ASTGREP.P09
570
+ deduplicateCallRanges(callMatches) {
571
+ const ranges = callMatches.map((c) => ({
572
+ node: c,
573
+ start: c.range().start.index,
574
+ end: c.range().end.index,
575
+ }));
576
+ ranges.sort((a, b) => a.start - b.start !== 0 ? a.start - b.start : b.end - a.end);
577
+ const outermost = [];
578
+ for (const r of ranges) {
579
+ const isContained = outermost.some((o) => r.start >= o.start && r.end <= o.end);
580
+ if (!isContained)
581
+ outermost.push(r);
582
+ }
583
+ return outermost;
584
+ }
585
+ async findCalleesOfFile(file, lang, sym, workspaceRoot, visited, ctx) {
586
+ if (ctx.nodesVisited >= ctx.maxNodes)
587
+ return [];
588
+ const parsed = await this.parseFile(file, lang);
589
+ if (!parsed)
590
+ return [];
591
+ const relPath = makeRelative(file, workspaceRoot);
592
+ const results = [];
593
+ try {
594
+ const methodMatches = parsed.root.findAll({
595
+ rule: {
596
+ kind: 'method_definition',
597
+ has: {
598
+ kind: 'property_identifier',
599
+ regex: `^${this.escapeRegex(sym)}$`,
600
+ },
601
+ },
602
+ });
603
+ for (const methodNode of methodMatches) {
604
+ const callMatches = methodNode.findAll({
605
+ rule: { kind: 'call_expression' },
606
+ });
607
+ const outermost = this.deduplicateCallRanges(callMatches);
608
+ for (const { node } of outermost) {
609
+ const callText = node.text().substring(0, 200);
610
+ const key = `${callText}@${relPath}`;
611
+ // eslint-disable-next-line sonarjs/nested-control-flow -- Dedup check inside nested iteration over AST matches
612
+ if (visited.has(key))
613
+ continue;
614
+ visited.add(key);
615
+ ctx.nodesVisited++;
616
+ results.push({
617
+ text: callText,
618
+ file: relPath,
619
+ line: node.range().start.line + 1,
620
+ calleeNode: node,
621
+ });
622
+ }
623
+ }
624
+ }
625
+ catch {
626
+ /* skip */
627
+ }
628
+ return results;
629
+ }
630
+ async executeCallees(symbol, lang, searchPath, workspaceRoot, depth, maxNodes, signal) {
631
+ const files = await this.getFiles(searchPath, lang);
632
+ const visited = new Set();
633
+ let nodesVisited = 0;
634
+ let truncated = false;
635
+ const findCalleesOf = async (sym, currentDepth) => {
636
+ if (currentDepth <= 0 || nodesVisited >= maxNodes || signal.aborted) {
637
+ if (nodesVisited >= maxNodes)
638
+ truncated = true;
639
+ return [];
640
+ }
641
+ const callees = [];
642
+ const ctx = { nodesVisited, maxNodes };
643
+ for (const file of files) {
644
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Signal may be aborted asynchronously between recursive calls while scanning files.
645
+ if (signal.aborted)
646
+ break;
647
+ const calleeResults = await this.findCalleesOfFile(file, lang, sym, workspaceRoot, visited, ctx);
648
+ for (const r of calleeResults) {
649
+ const entry = {
650
+ text: r.text,
651
+ file: r.file,
652
+ line: r.line,
653
+ };
654
+ const calleeName = r.calleeNode
655
+ ? this.extractCalleeName(r.calleeNode)
656
+ : null;
657
+ if (currentDepth > 1 &&
658
+ ctx.nodesVisited < maxNodes &&
659
+ calleeName &&
660
+ calleeName !== sym) {
661
+ nodesVisited = ctx.nodesVisited;
662
+ entry.callees = await findCalleesOf(calleeName, currentDepth - 1);
663
+ ctx.nodesVisited = nodesVisited;
664
+ }
665
+ callees.push(entry);
666
+ }
667
+ }
668
+ nodesVisited = ctx.nodesVisited;
669
+ return callees;
670
+ };
671
+ const results = await findCalleesOf(symbol, depth);
672
+ return {
673
+ mode: 'callees',
674
+ symbol,
675
+ truncated,
676
+ results,
677
+ };
678
+ }
679
+ // ===== REFERENCES =====
680
+ // @plan PLAN-20260211-ASTGREP.P10
681
+ searchDirectCallReferences(parsed, symbol, relPath, addResult) {
682
+ try {
683
+ const memberCalls = parsed.root.findAll(`$OBJ.${symbol}($$$ARGS)`);
684
+ for (const m of memberCalls) {
685
+ addResult('Direct calls', relPath, m.range().start.line + 1, m.text());
686
+ }
687
+ }
688
+ catch {
689
+ /* skip */
690
+ }
691
+ try {
692
+ const standaloneCalls = parsed.root.findAll(`${symbol}($$$ARGS)`);
693
+ for (const m of standaloneCalls) {
694
+ addResult('Direct calls', relPath, m.range().start.line + 1, m.text());
695
+ }
696
+ }
697
+ catch {
698
+ /* skip */
699
+ }
700
+ }
701
+ searchInstantiationReferences(parsed, symbol, relPath, addResult) {
702
+ try {
703
+ const news = parsed.root.findAll(`new ${symbol}($$$ARGS)`);
704
+ for (const m of news) {
705
+ addResult('Instantiations', relPath, m.range().start.line + 1, m.text());
706
+ }
707
+ }
708
+ catch {
709
+ /* skip */
710
+ }
711
+ try {
712
+ const lowerSymbol = symbol.charAt(0).toLowerCase() + symbol.slice(1);
713
+ const instanceCalls = parsed.root.findAll({
714
+ rule: {
715
+ kind: 'call_expression',
716
+ has: {
717
+ kind: 'member_expression',
718
+ has: {
719
+ kind: 'identifier',
720
+ regex: `(?i)${this.escapeRegex(lowerSymbol)}|${this.escapeRegex(symbol)}`,
721
+ },
722
+ },
723
+ },
724
+ });
725
+ for (const m of instanceCalls) {
726
+ addResult('Instance method calls (heuristic)', relPath, m.range().start.line + 1, m.text());
727
+ }
728
+ }
729
+ catch {
730
+ /* skip */
731
+ }
732
+ }
733
+ searchTypeAndHeritageReferences(parsed, symbol, relPath, addResult) {
734
+ try {
735
+ const typeRefs = parsed.root.findAll({
736
+ rule: {
737
+ kind: 'type_annotation',
738
+ has: {
739
+ kind: 'type_identifier',
740
+ regex: `^${this.escapeRegex(symbol)}$`,
741
+ },
742
+ },
743
+ });
744
+ for (const m of typeRefs) {
745
+ addResult('Type annotations', relPath, m.range().start.line + 1, m.text());
746
+ }
747
+ }
748
+ catch {
749
+ /* skip */
750
+ }
751
+ try {
752
+ const heritage = parsed.root.findAll(`class $NAME extends ${symbol} { $$$BODY }`);
753
+ for (const m of heritage) {
754
+ addResult('Extends/Implements', relPath, m.range().start.line + 1, `class ${m.getMatch('NAME')?.text()} extends ${symbol}`);
755
+ }
756
+ }
757
+ catch {
758
+ /* skip */
759
+ }
760
+ try {
761
+ const implHeritage = parsed.root.findAll(`class $NAME implements ${symbol} { $$$BODY }`);
762
+ for (const m of implHeritage) {
763
+ addResult('Extends/Implements', relPath, m.range().start.line + 1, `class ${m.getMatch('NAME')?.text()} implements ${symbol}`);
764
+ }
765
+ }
766
+ catch {
767
+ /* skip */
768
+ }
769
+ }
770
+ searchImportReferences(parsed, symbol, relPath, addResult) {
771
+ try {
772
+ const imports = parsed.root.findAll({
773
+ rule: {
774
+ kind: 'import_specifier',
775
+ has: { kind: 'identifier', regex: `^${this.escapeRegex(symbol)}$` },
776
+ },
777
+ });
778
+ for (const m of imports) {
779
+ addResult('Imports', relPath, m.range().start.line + 1, m.text());
780
+ }
781
+ }
782
+ catch {
783
+ /* skip */
784
+ }
785
+ }
786
+ async executeReferences(symbol, lang, searchPath, workspaceRoot, signal) {
787
+ const files = await this.getFiles(searchPath, lang);
788
+ const categories = {
789
+ 'Direct calls': [],
790
+ 'Instance method calls (heuristic)': [],
791
+ Instantiations: [],
792
+ 'Type annotations': [],
793
+ 'Extends/Implements': [],
794
+ Imports: [],
795
+ };
796
+ const seen = new Set();
797
+ const addResult = (category, file, line, text) => {
798
+ const key = `${category}:${file}:${line}`;
799
+ if (seen.has(key))
800
+ return;
801
+ seen.add(key);
802
+ categories[category].push({ file, line, text: text.substring(0, 200) });
803
+ };
804
+ // eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
805
+ for (const file of files) {
806
+ if (signal.aborted)
807
+ break;
808
+ const parsed = await this.parseFile(file, lang);
809
+ if (!parsed)
810
+ continue;
811
+ const relPath = makeRelative(file, workspaceRoot);
812
+ this.searchDirectCallReferences(parsed, symbol, relPath, addResult);
813
+ this.searchInstantiationReferences(parsed, symbol, relPath, addResult);
814
+ this.searchTypeAndHeritageReferences(parsed, symbol, relPath, addResult);
815
+ this.searchImportReferences(parsed, symbol, relPath, addResult);
816
+ }
817
+ const counts = {};
818
+ for (const [cat, items] of Object.entries(categories)) {
819
+ counts[cat] = items.length;
820
+ }
821
+ return {
822
+ mode: 'references',
823
+ symbol,
824
+ truncated: false,
825
+ results: { categories, counts },
826
+ };
827
+ }
828
+ // ===== DEPENDENCIES =====
829
+ // @plan PLAN-20260211-ASTGREP.P10
830
+ collectNamedAndDefaultImports(parsed, relPath, imports) {
831
+ try {
832
+ const named = parsed.root.findAll(`import { $$$NAMES } from $SOURCE`);
833
+ for (const m of named) {
834
+ const src = m.getMatch('SOURCE');
835
+ if (src) {
836
+ imports.push({
837
+ file: relPath,
838
+ line: m.range().start.line + 1,
839
+ source: src.text().replace(/['"]/g, ''),
840
+ kind: 'named',
841
+ });
842
+ }
843
+ }
844
+ }
845
+ catch {
846
+ /* skip */
847
+ }
848
+ try {
849
+ const defaults = parsed.root.findAll(`import $DEFAULT from $SOURCE`);
850
+ for (const m of defaults) {
851
+ const src = m.getMatch('SOURCE');
852
+ if (src) {
853
+ imports.push({
854
+ file: relPath,
855
+ line: m.range().start.line + 1,
856
+ source: src.text().replace(/['"]/g, ''),
857
+ kind: 'default',
858
+ });
859
+ }
860
+ }
861
+ }
862
+ catch {
863
+ /* skip */
864
+ }
865
+ }
866
+ collectDynamicAndReexports(parsed, relPath, imports) {
867
+ try {
868
+ const dynamic = parsed.root.findAll({
869
+ rule: {
870
+ kind: 'call_expression',
871
+ has: { kind: 'import' },
872
+ },
873
+ });
874
+ for (const m of dynamic) {
875
+ imports.push({
876
+ file: relPath,
877
+ line: m.range().start.line + 1,
878
+ source: m.text(),
879
+ kind: 'dynamic',
880
+ });
881
+ }
882
+ }
883
+ catch {
884
+ /* skip */
885
+ }
886
+ try {
887
+ const reexports = parsed.root.findAll({
888
+ rule: {
889
+ kind: 'export_statement',
890
+ has: { kind: 'string', regex: '.' },
891
+ },
892
+ });
893
+ for (const m of reexports) {
894
+ if (m.text().includes('from')) {
895
+ imports.push({
896
+ file: relPath,
897
+ line: m.range().start.line + 1,
898
+ source: m.text().substring(0, 200),
899
+ kind: 'reexport',
900
+ });
901
+ }
902
+ }
903
+ }
904
+ catch {
905
+ /* skip */
906
+ }
907
+ }
908
+ collectFileImports(parsed, relPath, imports) {
909
+ this.collectNamedAndDefaultImports(parsed, relPath, imports);
910
+ this.collectDynamicAndReexports(parsed, relPath, imports);
911
+ }
912
+ async findReverseImports(searchPath, workspaceRoot, lang, signal) {
913
+ const reverseImports = [];
914
+ const allFiles = await this.getFiles(workspaceRoot, lang);
915
+ const targetRel = makeRelative(searchPath, workspaceRoot);
916
+ const targetBasename = path.basename(searchPath).replace(/\.\w+$/, '');
917
+ // eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
918
+ for (const file of allFiles) {
919
+ if (signal.aborted)
920
+ break;
921
+ const parsed = await this.parseFile(file, lang);
922
+ if (!parsed)
923
+ continue;
924
+ const relPath = makeRelative(file, workspaceRoot);
925
+ if (relPath === targetRel)
926
+ continue;
927
+ const content = parsed.content || '';
928
+ if (content.includes(targetBasename)) {
929
+ reverseImports.push(...this.collectImportMatches(parsed, relPath, targetBasename));
930
+ }
931
+ }
932
+ return reverseImports;
933
+ }
934
+ collectImportMatches(parsed, relPath, targetBasename) {
935
+ const imports = [];
936
+ try {
937
+ const allImports = parsed.root.findAll({
938
+ rule: { kind: 'import_statement' },
939
+ });
940
+ for (const m of allImports) {
941
+ const text = m.text();
942
+ if (text.includes(targetBasename)) {
943
+ imports.push({
944
+ file: relPath,
945
+ line: m.range().start.line + 1,
946
+ source: text.substring(0, 200),
947
+ kind: 'import',
948
+ });
949
+ }
950
+ }
951
+ }
952
+ catch {
953
+ /* skip */
954
+ }
955
+ return imports;
956
+ }
957
+ async executeDependencies(searchPath, lang, workspaceRoot, reverse, signal) {
958
+ const files = await this.getFiles(searchPath, lang);
959
+ const imports = [];
960
+ // eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
961
+ for (const file of files) {
962
+ if (signal.aborted)
963
+ break;
964
+ const parsed = await this.parseFile(file, lang);
965
+ if (!parsed)
966
+ continue;
967
+ const relPath = makeRelative(file, workspaceRoot);
968
+ this.collectFileImports(parsed, relPath, imports);
969
+ }
970
+ const reverseImports = reverse
971
+ ? await this.findReverseImports(searchPath, workspaceRoot, lang, signal)
972
+ : [];
973
+ return {
974
+ mode: 'dependencies',
975
+ truncated: false,
976
+ results: {
977
+ imports,
978
+ reverseImports: reverse ? reverseImports : undefined,
979
+ },
980
+ };
981
+ }
982
+ // ===== EXPORTS =====
983
+ // @plan PLAN-20260211-ASTGREP.P10
984
+ async executeExports(searchPath, lang, workspaceRoot, signal) {
985
+ const files = await this.getFiles(searchPath, lang);
986
+ const exports = [];
987
+ // eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
988
+ for (const file of files) {
989
+ if (signal.aborted)
990
+ break;
991
+ const parsed = await this.parseFile(file, lang);
992
+ if (!parsed)
993
+ continue;
994
+ const relPath = makeRelative(file, workspaceRoot);
995
+ try {
996
+ const exportNodes = parsed.root.findAll({
997
+ rule: { kind: 'export_statement' },
998
+ });
999
+ for (const m of exportNodes) {
1000
+ const text = m.text();
1001
+ let kind = 'export';
1002
+ // eslint-disable-next-line sonarjs/nested-control-flow -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
1003
+ if (/^export\s+default\b/.test(text))
1004
+ kind = 'default';
1005
+ else if (text.includes('class'))
1006
+ kind = 'class';
1007
+ else if (text.includes('function'))
1008
+ kind = 'function';
1009
+ else if (text.includes('const') ||
1010
+ text.includes('let') ||
1011
+ text.includes('var'))
1012
+ kind = 'const';
1013
+ else if (text.includes('interface'))
1014
+ kind = 'interface';
1015
+ else if (text.includes('type '))
1016
+ kind = 'type';
1017
+ else if (text.includes('from'))
1018
+ kind = 'reexport';
1019
+ exports.push({
1020
+ file: relPath,
1021
+ line: m.range().start.line + 1,
1022
+ text: text.substring(0, 200),
1023
+ kind,
1024
+ });
1025
+ }
1026
+ }
1027
+ catch {
1028
+ /* skip */
1029
+ }
1030
+ }
1031
+ return {
1032
+ mode: 'exports',
1033
+ truncated: false,
1034
+ results: exports,
1035
+ };
1036
+ }
1037
+ }
1038
+ export class StructuralAnalysisTool extends BaseDeclarativeTool {
1039
+ host;
1040
+ static Name = 'structural_analysis';
1041
+ constructor(host) {
1042
+ super(StructuralAnalysisTool.Name, 'StructuralAnalysis', 'Performs multi-hop AST-based code analysis: call graphs, type hierarchies, symbol references, ' +
1043
+ 'module dependencies, and exports. This is name-based (not type-resolved) analysis. ' +
1044
+ 'Use for understanding code relationships. Unlike ast_grep (single-query), this chains multiple queries.', Kind.Search, {
1045
+ properties: {
1046
+ mode: {
1047
+ description: `Analysis mode: ${VALID_MODES.join(', ')}`,
1048
+ type: 'string',
1049
+ enum: [...VALID_MODES],
1050
+ },
1051
+ language: {
1052
+ description: 'Programming language (e.g., typescript, python)',
1053
+ type: 'string',
1054
+ },
1055
+ path: {
1056
+ description: 'Directory to search. Defaults to workspace root.',
1057
+ type: 'string',
1058
+ },
1059
+ symbol: {
1060
+ description: 'Symbol name for callers/callees/definitions/hierarchy/references modes.',
1061
+ type: 'string',
1062
+ },
1063
+ depth: {
1064
+ description: 'Recursion depth for callers/callees. Default 1, max 5.',
1065
+ type: 'number',
1066
+ },
1067
+ maxNodes: {
1068
+ description: 'Max symbols to visit during recursive traversal. Default 50.',
1069
+ type: 'number',
1070
+ },
1071
+ target: {
1072
+ description: 'File/directory for dependencies/exports modes.',
1073
+ type: 'string',
1074
+ },
1075
+ reverse: {
1076
+ description: 'For dependencies mode: also find what imports the target.',
1077
+ type: 'boolean',
1078
+ },
1079
+ },
1080
+ required: ['mode', 'language'],
1081
+ type: 'object',
1082
+ });
1083
+ this.host = host;
1084
+ }
1085
+ createInvocation(params, messageBus) {
1086
+ return new StructuralAnalysisInvocation(this.host, params, messageBus);
1087
+ }
1088
+ }
1089
+ //# sourceMappingURL=structural-analysis.js.map