@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,883 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ /* eslint-disable complexity, sonarjs/cognitive-complexity, max-lines -- Phase 5: legacy core boundary retained while larger decomposition continues. */
7
+ import fsPromises from 'fs/promises';
8
+ import path from 'path';
9
+ import { EOL } from 'os';
10
+ import { spawn } from 'child_process';
11
+ import { globStream } from 'glob';
12
+ import { BaseDeclarativeTool, BaseToolInvocation, Kind, } from './tools.js';
13
+ import { makeRelative, shortenPath } from '../utils/paths.js';
14
+ import { getErrorMessage, isNodeError } from '../utils/errors.js';
15
+ import { isGitRepository } from '../utils/gitUtils.js';
16
+ import { resolveTextSearchTarget, } from '../utils/resolveTextSearchTarget.js';
17
+ import { ToolErrorType } from '../types/tool-error.js';
18
+ import { limitOutputTokens, formatLimitedOutput, } from '../utils/toolOutputLimiter.js';
19
+ import { debugLogger } from '../utils/debugLogger.js';
20
+ /**
21
+ * Checks if a glob pattern contains brace expansion syntax that git grep doesn't support.
22
+ * Git grep pathspecs don't support shell-style brace expansion like {ts,tsx,js}.
23
+ * Uses indexOf for O(n) complexity instead of regex to avoid ReDoS vulnerability.
24
+ */
25
+ function hasBraceExpansion(pattern) {
26
+ const braceStart = pattern.indexOf('{');
27
+ if (braceStart === -1)
28
+ return false;
29
+ const braceEnd = pattern.indexOf('}', braceStart);
30
+ if (braceEnd === -1)
31
+ return false;
32
+ const commaPos = pattern.indexOf(',', braceStart);
33
+ return commaPos !== -1 && commaPos < braceEnd;
34
+ }
35
+ // --- Interfaces ---
36
+ /**
37
+ * Default timeout for grep operations in milliseconds (1 minute)
38
+ */
39
+ const DEFAULT_TIMEOUT_MS = 60_000;
40
+ /**
41
+ * Maximum allowed timeout for grep operations in milliseconds (5 minutes)
42
+ */
43
+ const MAX_TIMEOUT_MS = 300_000;
44
+ class GrepToolInvocation extends BaseToolInvocation {
45
+ host;
46
+ fileExclusions;
47
+ constructor(host, params, messageBus) {
48
+ super(params, messageBus);
49
+ this.host = host;
50
+ this.fileExclusions = host.getFileExclusions();
51
+ }
52
+ getDirPath() {
53
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: dir_path and path are optional strings, empty string should fall through
54
+ return this.params.dir_path || this.params.path;
55
+ }
56
+ resolveTarget(relativePath) {
57
+ return resolveTextSearchTarget(this.host.getTargetDir(), this.host.getWorkspaceRoots(), relativePath);
58
+ }
59
+ /**
60
+ * Executes a single-file search and returns a formatted ToolResult.
61
+ */
62
+ async executeSingleFileSearch(resolved, combinedSignal, searchDirDisplay) {
63
+ const fileResult = await this.performSingleFileSearch(this.params.pattern, resolved.filePath, resolved.basename, combinedSignal);
64
+ let includeNote = '';
65
+ if (this.params.include) {
66
+ includeNote =
67
+ '\nNote: include filter ignored because a specific file path was provided.';
68
+ }
69
+ if (fileResult.length === 0) {
70
+ const noMatchMsg = `No matches found for pattern "${this.params.pattern}" in file "${searchDirDisplay}".${includeNote}`;
71
+ return { llmContent: noMatchMsg, returnDisplay: 'No matches found' };
72
+ }
73
+ const matchTerm = fileResult.length === 1 ? 'match' : 'matches';
74
+ let llmContent = `Found ${fileResult.length} ${matchTerm} for pattern "${this.params.pattern}" in file "${searchDirDisplay}":${includeNote}
75
+ ---
76
+ File: ${resolved.basename}
77
+ `;
78
+ for (const match of fileResult) {
79
+ llmContent += `L${match.lineNumber}: ${match.line.trim()}
80
+ `;
81
+ }
82
+ llmContent += '---';
83
+ const limited = limitOutputTokens(llmContent.trim(), this.host, 'SearchText');
84
+ if (limited.wasTruncated) {
85
+ const formatted = formatLimitedOutput(limited);
86
+ return {
87
+ llmContent: formatted.llmContent,
88
+ returnDisplay: formatted.returnDisplay,
89
+ };
90
+ }
91
+ return {
92
+ llmContent: llmContent.trim(),
93
+ returnDisplay: `Found ${fileResult.length} ${matchTerm}`,
94
+ };
95
+ }
96
+ /**
97
+ * Collects matches across multiple search directories.
98
+ */
99
+ async collectDirectoryMatches(searchDirectories, combinedSignal, maxResults, maxFiles, maxPerFile, filesWithMatches) {
100
+ let allMatches = [];
101
+ let totalMatchesFound = 0;
102
+ let wasLimited = false;
103
+ for (const searchDir of searchDirectories) {
104
+ if (allMatches.length >= maxResults) {
105
+ wasLimited = true;
106
+ break;
107
+ }
108
+ const matches = await this.performGrepSearch({
109
+ pattern: this.params.pattern,
110
+ path: searchDir,
111
+ include: this.params.include,
112
+ signal: combinedSignal,
113
+ maxResults: maxResults - allMatches.length,
114
+ maxFiles: maxFiles - filesWithMatches.size,
115
+ maxPerFile,
116
+ });
117
+ if (matches.wasLimited === true) {
118
+ wasLimited = true;
119
+ }
120
+ if (searchDirectories.length > 1) {
121
+ const dirName = path.basename(searchDir);
122
+ matches.results.forEach((match) => {
123
+ match.filePath = path.join(dirName, match.filePath);
124
+ });
125
+ }
126
+ matches.results.forEach((match) => {
127
+ filesWithMatches.add(match.filePath);
128
+ });
129
+ totalMatchesFound += matches.totalFound ?? matches.results.length;
130
+ allMatches = allMatches.concat(matches.results);
131
+ }
132
+ return { allMatches, totalMatchesFound, wasLimited };
133
+ }
134
+ /**
135
+ * Applies max_files and max_per_file limits, returning grouped matches,
136
+ * match count, and any limit message.
137
+ */
138
+ applyFileLimits(allMatches, filesWithMatches, maxFiles, maxPerFile) {
139
+ let limitedMatches = allMatches;
140
+ let limitMessage = '';
141
+ let wasLimited = false;
142
+ if (filesWithMatches.size > maxFiles) {
143
+ const filesToKeep = Array.from(filesWithMatches).slice(0, maxFiles);
144
+ limitedMatches = allMatches.filter((match) => filesToKeep.includes(match.filePath));
145
+ limitMessage = `
146
+
147
+ **Note: Results limited to ${maxFiles} files out of ${filesWithMatches.size} files with matches.**`;
148
+ wasLimited = true;
149
+ }
150
+ const matchesByFile = limitedMatches.reduce((acc, match) => {
151
+ const fileKey = match.filePath;
152
+ if (!(fileKey in acc)) {
153
+ acc[fileKey] = [];
154
+ }
155
+ if (acc[fileKey].length < maxPerFile) {
156
+ acc[fileKey].push(match);
157
+ }
158
+ acc[fileKey].sort((a, b) => a.lineNumber - b.lineNumber);
159
+ return acc;
160
+ }, {});
161
+ const matchCount = Object.values(matchesByFile).reduce((sum, matches) => sum + matches.length, 0);
162
+ return {
163
+ matchesByFile,
164
+ matchCount,
165
+ limitedMatches,
166
+ limitMessage,
167
+ wasLimited,
168
+ };
169
+ }
170
+ /**
171
+ * Builds the formatted LLM content string from grouped matches.
172
+ */
173
+ formatMatchOutput(matchesByFile, limitMessage, totalMatchesFound, matchCount, wasLimited, searchLocationDescription) {
174
+ let llmContent = '';
175
+ if (wasLimited || totalMatchesFound > matchCount) {
176
+ llmContent = `Found ${totalMatchesFound} total matches, showing ${matchCount} for pattern "${this.params.pattern}" ${searchLocationDescription}${this.params.include ? ` (filter: "${this.params.include}")` : ''}:
177
+ ---
178
+ `;
179
+ }
180
+ else {
181
+ const matchTerm = matchCount === 1 ? 'match' : 'matches';
182
+ llmContent = `Found ${matchCount} ${matchTerm} for pattern "${this.params.pattern}" ${searchLocationDescription}${this.params.include ? ` (filter: "${this.params.include}")` : ''}:
183
+ ---
184
+ `;
185
+ }
186
+ for (const filePath in matchesByFile) {
187
+ llmContent += `File: ${filePath}
188
+ `;
189
+ matchesByFile[filePath].forEach((match) => {
190
+ const trimmedLine = match.line.trim();
191
+ llmContent += `L${match.lineNumber}: ${trimmedLine}
192
+ `;
193
+ });
194
+ llmContent += '---\n';
195
+ }
196
+ if (limitMessage) {
197
+ llmContent += limitMessage;
198
+ }
199
+ return llmContent;
200
+ }
201
+ /**
202
+ * Applies file and per-file limits to matches and builds the output content.
203
+ */
204
+ buildDirectorySearchResult(allMatches, totalMatchesFound, wasLimited, filesWithMatches, searchLocationDescription, maxFiles, maxPerFile) {
205
+ if (allMatches.length === 0) {
206
+ const noMatchMsg = `No matches found for pattern "${this.params.pattern}" ${searchLocationDescription}${this.params.include ? ` (filter: "${this.params.include}")` : ''}.`;
207
+ return { llmContent: noMatchMsg, returnDisplay: `No matches found` };
208
+ }
209
+ const { matchesByFile, matchCount, limitMessage, wasLimited: limited, } = this.applyFileLimits(allMatches, filesWithMatches, maxFiles, maxPerFile);
210
+ const effectiveWasLimited = wasLimited || limited;
211
+ const llmContent = this.formatMatchOutput(matchesByFile, limitMessage, totalMatchesFound, matchCount, effectiveWasLimited, searchLocationDescription);
212
+ // Apply token limiting as final safety check
213
+ const limitedOutput = limitOutputTokens(llmContent.trim(), this.host, 'SearchText');
214
+ if (limitedOutput.wasTruncated) {
215
+ const formatted = formatLimitedOutput(limitedOutput);
216
+ return {
217
+ llmContent: formatted.llmContent,
218
+ returnDisplay: formatted.returnDisplay,
219
+ };
220
+ }
221
+ const displayCount = effectiveWasLimited || totalMatchesFound > matchCount
222
+ ? `Found ${totalMatchesFound} matches (showing ${matchCount})`
223
+ : // eslint-disable-next-line sonarjs/no-nested-conditional -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
224
+ `Found ${matchCount} ${matchCount === 1 ? 'match' : 'matches'}`;
225
+ return {
226
+ llmContent: llmContent.trim(),
227
+ returnDisplay: displayCount,
228
+ };
229
+ }
230
+ /**
231
+ * Handles abort/timeout errors from execute, returning appropriate ToolResult.
232
+ */
233
+ handleExecuteError(error, timeoutController, timeoutMs, signal) {
234
+ const isAbortError = error instanceof Error &&
235
+ (error.name === 'AbortError' ||
236
+ error.message.includes('aborted') ||
237
+ error.message.includes('This operation was aborted'));
238
+ if (isAbortError) {
239
+ // Check if it was a timeout (our controller aborted but user's didn't)
240
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- caller AbortSignal can change asynchronously outside this stack
241
+ if (timeoutController.signal.aborted && !signal.aborted) {
242
+ const timeoutMessage = `Search operation timed out after ${timeoutMs}ms. To resolve this, you can either:
243
+ ` +
244
+ `1. Increase the timeout (max ${MAX_TIMEOUT_MS}ms) by adding timeout_ms parameter
245
+ ` +
246
+ `2. Use a more specific pattern to reduce search scope
247
+ ` +
248
+ `3. Use a narrower path or include filter`;
249
+ return {
250
+ llmContent: timeoutMessage,
251
+ returnDisplay: `Timed out after ${timeoutMs}ms`,
252
+ error: {
253
+ message: timeoutMessage,
254
+ type: ToolErrorType.TIMEOUT,
255
+ },
256
+ };
257
+ }
258
+ return {
259
+ llmContent: 'Search operation was cancelled by user.',
260
+ returnDisplay: 'Cancelled',
261
+ error: {
262
+ message: 'Search operation was cancelled by user.',
263
+ type: ToolErrorType.EXECUTION_FAILED,
264
+ },
265
+ };
266
+ }
267
+ debugLogger.error(`Error during GrepLogic execution: ${error}`);
268
+ const errorMessage = getErrorMessage(error);
269
+ return {
270
+ llmContent: `Error during grep search operation: ${errorMessage}`,
271
+ returnDisplay: `Error: ${errorMessage}`,
272
+ error: {
273
+ message: errorMessage,
274
+ type: ToolErrorType.GREP_EXECUTION_ERROR,
275
+ },
276
+ };
277
+ }
278
+ /**
279
+ * Executes the directory search after resolving the target.
280
+ */
281
+ async executeDirectorySearch(resolved, workspaceContext, combinedSignal, searchDirDisplay) {
282
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: dirPath is optional string, empty string should fall through
283
+ const ephemeralSettings = this.host.getEphemeralSettings();
284
+ const maxResults = this.params.max_results ??
285
+ ephemeralSettings['tool-output-max-items'] ??
286
+ 1000;
287
+ const maxFiles = this.params.max_files ?? 100;
288
+ const maxPerFile = this.params.max_per_file ?? 50;
289
+ if (resolved.kind === 'file') {
290
+ return this.executeSingleFileSearch(resolved, combinedSignal, searchDirDisplay);
291
+ }
292
+ // Determine which directories to search
293
+ let searchDirectories;
294
+ if (resolved.kind === 'all-workspaces') {
295
+ searchDirectories = workspaceContext;
296
+ }
297
+ else {
298
+ searchDirectories = [resolved.searchDir];
299
+ }
300
+ const filesWithMatches = new Set();
301
+ const { allMatches, totalMatchesFound, wasLimited } = await this.collectDirectoryMatches(searchDirectories, combinedSignal, maxResults, maxFiles, maxPerFile, filesWithMatches);
302
+ let searchLocationDescription;
303
+ if (resolved.kind === 'all-workspaces') {
304
+ const numDirs = workspaceContext.length;
305
+ searchLocationDescription =
306
+ numDirs > 1
307
+ ? `across ${numDirs} workspace directories`
308
+ : `in the workspace directory`;
309
+ }
310
+ else {
311
+ searchLocationDescription = `in path "${searchDirDisplay}"`;
312
+ }
313
+ return this.buildDirectorySearchResult(allMatches, totalMatchesFound, wasLimited, filesWithMatches, searchLocationDescription, maxFiles, maxPerFile);
314
+ }
315
+ async execute(signal) {
316
+ // Set up timeout handling
317
+ const timeoutMs = Math.min(this.params.timeout_ms ?? DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
318
+ const timeoutController = new AbortController();
319
+ const timeoutId = setTimeout(() => timeoutController.abort(), timeoutMs);
320
+ // Combine user abort with timeout abort
321
+ const onUserAbort = () => {
322
+ clearTimeout(timeoutId);
323
+ timeoutController.abort();
324
+ };
325
+ if (signal.aborted) {
326
+ clearTimeout(timeoutId);
327
+ timeoutController.abort();
328
+ return {
329
+ llmContent: 'Search operation was cancelled by user.',
330
+ returnDisplay: 'Cancelled',
331
+ error: {
332
+ message: 'Search operation was cancelled by user.',
333
+ type: ToolErrorType.EXECUTION_FAILED,
334
+ },
335
+ };
336
+ }
337
+ signal.addEventListener('abort', onUserAbort, { once: true });
338
+ const combinedSignal = timeoutController.signal;
339
+ try {
340
+ const workspaceContext = this.host.getWorkspaceRoots();
341
+ const dirPath = this.getDirPath();
342
+ const resolved = this.resolveTarget(dirPath);
343
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: dirPath is optional string, empty string should fall through
344
+ const searchDirDisplay = dirPath || '.';
345
+ return await this.executeDirectorySearch(resolved, workspaceContext, combinedSignal, searchDirDisplay);
346
+ }
347
+ catch (error) {
348
+ return this.handleExecuteError(error, timeoutController, timeoutMs, signal);
349
+ }
350
+ finally {
351
+ clearTimeout(timeoutId);
352
+ signal.removeEventListener('abort', onUserAbort);
353
+ }
354
+ }
355
+ /**
356
+ * Checks if a command is available in the system's PATH.
357
+ * @param {string} command The command name (e.g., 'git', 'grep').
358
+ * @returns {Promise<boolean>} True if the command is available, false otherwise.
359
+ */
360
+ isCommandAvailable(command) {
361
+ return new Promise((resolve) => {
362
+ const checkCommand = process.platform === 'win32' ? 'where' : 'command';
363
+ const checkArgs = process.platform === 'win32' ? [command] : ['-v', command];
364
+ try {
365
+ // eslint-disable-next-line sonarjs/os-command -- Project intentionally invokes platform tooling at this trusted boundary; arguments remain explicit and behavior is preserved.
366
+ const child = spawn(checkCommand, checkArgs, {
367
+ stdio: 'ignore',
368
+ shell: true,
369
+ });
370
+ child.on('close', (code) => resolve(code === 0));
371
+ child.on('error', (err) => {
372
+ debugLogger.debug(`[GrepTool] Failed to start process for '${command}':`, err.message);
373
+ resolve(false);
374
+ });
375
+ }
376
+ catch {
377
+ resolve(false);
378
+ }
379
+ });
380
+ }
381
+ /**
382
+ * Apply limits to search results
383
+ */
384
+ applyLimits(matches, maxResults, maxFiles, maxPerFile) {
385
+ const filesWithMatches = new Map();
386
+ const totalFound = matches.length;
387
+ // Group by file and apply per-file limits
388
+ for (const match of matches) {
389
+ if (!filesWithMatches.has(match.filePath)) {
390
+ filesWithMatches.set(match.filePath, []);
391
+ }
392
+ const fileMatches = filesWithMatches.get(match.filePath);
393
+ if (fileMatches.length < maxPerFile) {
394
+ fileMatches.push(match);
395
+ }
396
+ }
397
+ // Apply file limit
398
+ const limitedFiles = Array.from(filesWithMatches.entries()).slice(0, maxFiles);
399
+ // Flatten and apply total results limit
400
+ const results = [];
401
+ for (const [, fileMatches] of limitedFiles) {
402
+ for (const match of fileMatches) {
403
+ if (results.length >= maxResults) {
404
+ break;
405
+ }
406
+ results.push(match);
407
+ }
408
+ if (results.length >= maxResults) {
409
+ break;
410
+ }
411
+ }
412
+ return {
413
+ results,
414
+ wasLimited: results.length < totalFound || filesWithMatches.size > maxFiles,
415
+ totalFound: totalFound > results.length ? totalFound : undefined,
416
+ };
417
+ }
418
+ /**
419
+ * Parses the standard output of grep-like commands (git grep, system grep).
420
+ * Expects format: filePath:lineNumber:lineContent
421
+ * Handles colons within file paths and line content correctly.
422
+ * @param {string} output The raw stdout string.
423
+ * @param {string} basePath The absolute directory the search was run from, for relative paths.
424
+ * @returns {GrepMatch[]} Array of match objects.
425
+ */
426
+ parseGrepOutput(output, basePath) {
427
+ const results = [];
428
+ if (!output)
429
+ return results;
430
+ const lines = output.split(EOL); // Use OS-specific end-of-line
431
+ // 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.
432
+ for (const line of lines) {
433
+ if (!line.trim())
434
+ continue;
435
+ // Find the index of the first colon.
436
+ const firstColonIndex = line.indexOf(':');
437
+ if (firstColonIndex === -1)
438
+ continue; // Malformed
439
+ // Find the index of the second colon, searching *after* the first one.
440
+ const secondColonIndex = line.indexOf(':', firstColonIndex + 1);
441
+ if (secondColonIndex === -1)
442
+ continue; // Malformed
443
+ // Extract parts based on the found colon indices
444
+ const filePathRaw = line.substring(0, firstColonIndex);
445
+ const lineNumberStr = line.substring(firstColonIndex + 1, secondColonIndex);
446
+ const lineContent = line.substring(secondColonIndex + 1);
447
+ const lineNumber = parseInt(lineNumberStr, 10);
448
+ if (!isNaN(lineNumber)) {
449
+ const absoluteFilePath = path.resolve(basePath, filePathRaw);
450
+ const relativeFilePath = path.relative(basePath, absoluteFilePath);
451
+ results.push({
452
+ filePath: relativeFilePath || path.basename(absoluteFilePath),
453
+ lineNumber,
454
+ line: lineContent,
455
+ });
456
+ }
457
+ }
458
+ return results;
459
+ }
460
+ /**
461
+ * Gets a description of the grep operation
462
+ * @returns A string describing the grep
463
+ */
464
+ getDescription() {
465
+ let description = `'${this.params.pattern}'`;
466
+ if (this.params.include) {
467
+ description += ` in ${this.params.include}`;
468
+ }
469
+ const dirPath = this.getDirPath();
470
+ if (dirPath) {
471
+ try {
472
+ const resolved = this.resolveTarget(dirPath);
473
+ if (resolved.kind === 'file') {
474
+ const relativePath = makeRelative(resolved.filePath, this.host.getTargetDir());
475
+ description += ` in ${shortenPath(relativePath)}`;
476
+ return description;
477
+ }
478
+ }
479
+ catch {
480
+ // Fall through to default path display on validation errors
481
+ }
482
+ const resolvedPath = path.resolve(this.host.getTargetDir(), dirPath);
483
+ if (resolvedPath === this.host.getTargetDir() || dirPath === '.') {
484
+ description += ` within ./`;
485
+ }
486
+ else {
487
+ const relativePath = makeRelative(resolvedPath, this.host.getTargetDir());
488
+ description += ` within ${shortenPath(relativePath)}`;
489
+ }
490
+ }
491
+ else {
492
+ const workspaceContext = this.host.getWorkspaceRoots();
493
+ const directories = workspaceContext;
494
+ if (directories.length > 1) {
495
+ description += ` across all workspace directories`;
496
+ }
497
+ }
498
+ return description;
499
+ }
500
+ async performSingleFileSearch(pattern, filePath, _basename, signal) {
501
+ if (signal.aborted) {
502
+ return [];
503
+ }
504
+ const regex = new RegExp(pattern, 'i');
505
+ const content = await fsPromises.readFile(filePath, 'utf8');
506
+ const lines = content.split(/\r?\n/);
507
+ const matches = [];
508
+ for (let i = 0; i < lines.length; i++) {
509
+ if (regex.test(lines[i])) {
510
+ matches.push({
511
+ filePath: path.basename(filePath),
512
+ lineNumber: i + 1,
513
+ line: lines[i],
514
+ });
515
+ }
516
+ }
517
+ return matches;
518
+ }
519
+ /**
520
+ * Runs git grep as Strategy 1.
521
+ */
522
+ async tryGitGrep(pattern, absolutePath, include, abortSignal, maxResults, maxFiles, maxPerFile, hasBracePattern) {
523
+ const isGit = !hasBracePattern && isGitRepository(absolutePath);
524
+ const gitAvailable = isGit && (await this.isCommandAvailable('git'));
525
+ if (!gitAvailable)
526
+ return null;
527
+ const gitArgs = [
528
+ 'grep',
529
+ '--untracked',
530
+ '-n',
531
+ '-E',
532
+ '--ignore-case',
533
+ pattern,
534
+ ];
535
+ if (include) {
536
+ gitArgs.push('--', include);
537
+ }
538
+ try {
539
+ const output = await new Promise((resolve, reject) => {
540
+ // eslint-disable-next-line sonarjs/no-os-command-from-path -- Project intentionally invokes platform tooling at this trusted boundary; arguments remain explicit and behavior is preserved.
541
+ const child = spawn('git', gitArgs, {
542
+ cwd: absolutePath,
543
+ windowsHide: true,
544
+ });
545
+ const stdoutChunks = [];
546
+ const stderrChunks = [];
547
+ const abortHandler = () => {
548
+ // eslint-disable-next-line sonarjs/nested-control-flow -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
549
+ if (!child.killed) {
550
+ child.kill('SIGTERM');
551
+ }
552
+ reject(new Error('git grep aborted'));
553
+ };
554
+ abortSignal.addEventListener('abort', abortHandler);
555
+ child.stdout.on('data', (chunk) => stdoutChunks.push(chunk));
556
+ child.stderr.on('data', (chunk) => stderrChunks.push(chunk));
557
+ child.on('error', (err) => {
558
+ abortSignal.removeEventListener('abort', abortHandler);
559
+ reject(new Error(`Failed to start git grep: ${err.message}`));
560
+ });
561
+ child.on('close', (code) => {
562
+ abortSignal.removeEventListener('abort', abortHandler);
563
+ const stdoutData = Buffer.concat(stdoutChunks).toString('utf8');
564
+ const stderrData = Buffer.concat(stderrChunks).toString('utf8');
565
+ // eslint-disable-next-line sonarjs/nested-control-flow -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
566
+ if (code === 0)
567
+ resolve(stdoutData);
568
+ else if (code === 1)
569
+ resolve(''); // No matches
570
+ else
571
+ reject(new Error(`git grep exited with code ${code}: ${stderrData}`));
572
+ });
573
+ });
574
+ const matches = this.parseGrepOutput(output, absolutePath);
575
+ return this.applyLimits(matches, maxResults, maxFiles, maxPerFile);
576
+ }
577
+ catch (gitError) {
578
+ debugLogger.debug(`GrepLogic: git grep failed: ${getErrorMessage(gitError)}. Falling back...`);
579
+ return null;
580
+ }
581
+ }
582
+ /**
583
+ * Builds the grep args for system grep, including exclusion patterns.
584
+ */
585
+ buildSystemGrepArgs(pattern, include) {
586
+ const grepArgs = ['-r', '-n', '-H', '-E', '-I'];
587
+ const globExcludes = this.fileExclusions;
588
+ const commonExcludes = globExcludes
589
+ .map((pattern) => {
590
+ let dir = pattern;
591
+ if (dir.startsWith('**/')) {
592
+ dir = dir.substring(3);
593
+ }
594
+ if (dir.endsWith('/**')) {
595
+ dir = dir.slice(0, -3);
596
+ }
597
+ else if (dir.endsWith('/')) {
598
+ dir = dir.slice(0, -1);
599
+ }
600
+ // Only consider patterns that are likely directories
601
+ if (dir && !dir.includes('/') && !dir.includes('*')) {
602
+ return dir;
603
+ }
604
+ return null;
605
+ })
606
+ .filter((dir) => !!dir);
607
+ commonExcludes.forEach((dir) => grepArgs.push(`--exclude-dir=${dir}`));
608
+ if (include) {
609
+ grepArgs.push(`--include=${include}`);
610
+ }
611
+ grepArgs.push(pattern);
612
+ grepArgs.push('.');
613
+ return grepArgs;
614
+ }
615
+ /**
616
+ * Sets up event handlers for a spawned grep child process.
617
+ */
618
+ setupSystemGrepHandlers(child, abortSignal, stdoutChunks, stderrChunks, resolve, reject) {
619
+ const abortHandler = () => {
620
+ if (!child.killed) {
621
+ child.kill('SIGTERM');
622
+ }
623
+ cleanup();
624
+ reject(new Error('system grep aborted'));
625
+ };
626
+ abortSignal.addEventListener('abort', abortHandler);
627
+ const onData = (chunk) => stdoutChunks.push(chunk);
628
+ const onStderr = (chunk) => {
629
+ const stderrStr = chunk.toString();
630
+ if (!stderrStr.includes('Permission denied') &&
631
+ !/grep:.*: Is a directory/i.test(stderrStr)) {
632
+ stderrChunks.push(chunk);
633
+ }
634
+ };
635
+ const onError = (err) => {
636
+ cleanup();
637
+ reject(new Error(`Failed to start system grep: ${err.message}`));
638
+ };
639
+ const onClose = (code) => {
640
+ const stdoutData = Buffer.concat(stdoutChunks).toString('utf8');
641
+ const stderrData = Buffer.concat(stderrChunks).toString('utf8').trim();
642
+ cleanup();
643
+ if (code === 0)
644
+ resolve(stdoutData);
645
+ else if (code === 1)
646
+ resolve(''); // No matches
647
+ else if (stderrData)
648
+ reject(new Error(`System grep exited with code ${code}: ${stderrData}`));
649
+ else
650
+ resolve(''); // Exit code > 1 but no stderr, likely just suppressed errors
651
+ };
652
+ const cleanup = () => {
653
+ abortSignal.removeEventListener('abort', abortHandler);
654
+ child.stdout.removeListener('data', onData);
655
+ child.stderr.removeListener('data', onStderr);
656
+ child.removeListener('error', onError);
657
+ child.removeListener('close', onClose);
658
+ if (child.connected) {
659
+ child.disconnect();
660
+ }
661
+ };
662
+ child.stdout.on('data', onData);
663
+ child.stderr.on('data', onStderr);
664
+ child.on('error', onError);
665
+ child.on('close', onClose);
666
+ return cleanup;
667
+ }
668
+ /**
669
+ * Runs system grep as Strategy 2.
670
+ */
671
+ async trySystemGrep(grepArgs, absolutePath, abortSignal, maxResults, maxFiles, maxPerFile) {
672
+ try {
673
+ const output = await new Promise((resolve, reject) => {
674
+ // eslint-disable-next-line sonarjs/no-os-command-from-path -- Project intentionally invokes platform tooling at this trusted boundary; arguments remain explicit and behavior is preserved.
675
+ const child = spawn('grep', grepArgs, {
676
+ cwd: absolutePath,
677
+ windowsHide: true,
678
+ });
679
+ const stdoutChunks = [];
680
+ const stderrChunks = [];
681
+ this.setupSystemGrepHandlers(child, abortSignal, stdoutChunks, stderrChunks, resolve, reject);
682
+ });
683
+ const matches = this.parseGrepOutput(output, absolutePath);
684
+ return this.applyLimits(matches, maxResults, maxFiles, maxPerFile);
685
+ }
686
+ catch (grepError) {
687
+ debugLogger.debug(`GrepLogic: System grep failed: ${getErrorMessage(grepError)}. Falling back...`);
688
+ return null;
689
+ }
690
+ }
691
+ /**
692
+ * Extracts matches from a single file's content lines.
693
+ */
694
+ extractMatchesFromFile(lines, fileAbsolutePath, absolutePath, regex, maxPerFile, maxResults, allMatches, filesWithMatches) {
695
+ let matchesInFile = 0;
696
+ let totalFound = 0;
697
+ lines.forEach((line, index) => {
698
+ if (regex.test(line)) {
699
+ totalFound++;
700
+ if (matchesInFile < maxPerFile && allMatches.length < maxResults) {
701
+ allMatches.push({
702
+ filePath: path.relative(absolutePath, fileAbsolutePath) ||
703
+ path.basename(fileAbsolutePath),
704
+ lineNumber: index + 1,
705
+ line,
706
+ });
707
+ matchesInFile++;
708
+ filesWithMatches.add(fileAbsolutePath);
709
+ }
710
+ }
711
+ });
712
+ return totalFound;
713
+ }
714
+ /**
715
+ * Pure JavaScript fallback for grep (Strategy 3).
716
+ */
717
+ async javascriptGrepFallback(pattern, absolutePath, include, abortSignal, maxResults, maxFiles, maxPerFile) {
718
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: include is optional string, empty string should fall through to default glob
719
+ const globPattern = include ? include : '**/*';
720
+ const ignorePatterns = this.fileExclusions;
721
+ const filesStream = globStream(globPattern, {
722
+ cwd: absolutePath,
723
+ dot: true,
724
+ ignore: ignorePatterns,
725
+ absolute: true,
726
+ nodir: true,
727
+ signal: abortSignal,
728
+ });
729
+ const regex = new RegExp(pattern, 'i');
730
+ const allMatches = [];
731
+ const filesWithMatches = new Set();
732
+ let totalFound = 0;
733
+ // 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.
734
+ for await (const filePath of filesStream) {
735
+ // Check if we've hit file limit
736
+ if (filesWithMatches.size >= maxFiles &&
737
+ !filesWithMatches.has(filePath)) {
738
+ continue;
739
+ }
740
+ // Check if we've hit total results limit
741
+ if (allMatches.length >= maxResults) {
742
+ break;
743
+ }
744
+ const fileAbsolutePath = filePath;
745
+ try {
746
+ const content = await fsPromises.readFile(fileAbsolutePath, 'utf8');
747
+ const lines = content.split(/\r?\n/);
748
+ totalFound += this.extractMatchesFromFile(lines, fileAbsolutePath, absolutePath, regex, maxPerFile, maxResults, allMatches, filesWithMatches);
749
+ }
750
+ catch (readError) {
751
+ // Ignore errors like permission denied or file gone during read
752
+ if (!isNodeError(readError) || readError.code !== 'ENOENT') {
753
+ debugLogger.debug(`GrepLogic: Could not read/process ${fileAbsolutePath}: ${getErrorMessage(readError)}`);
754
+ }
755
+ }
756
+ }
757
+ return {
758
+ results: allMatches,
759
+ wasLimited: totalFound > allMatches.length,
760
+ totalFound: totalFound > allMatches.length ? totalFound : undefined,
761
+ };
762
+ }
763
+ /**
764
+ * Attempts system grep (Strategy 2), returning null to fall through.
765
+ */
766
+ async trySystemGrepStrategy(pattern, absolutePath, include, signal, maxResults, maxFiles, maxPerFile) {
767
+ debugLogger.debug('GrepLogic: System grep is being considered as fallback strategy.');
768
+ const grepAvailable = await this.isCommandAvailable('grep');
769
+ if (!grepAvailable)
770
+ return null;
771
+ const grepArgs = this.buildSystemGrepArgs(pattern, include);
772
+ return this.trySystemGrep(grepArgs, absolutePath, signal, maxResults, maxFiles, maxPerFile);
773
+ }
774
+ /**
775
+ * Performs the actual search using the prioritized strategies.
776
+ * @param options Search options including pattern, absolute path, and include glob.
777
+ * @returns A promise resolving to search results with limit information.
778
+ */
779
+ async performGrepSearch(options) {
780
+ const { pattern, path: absolutePath, include, maxResults = 1000, maxFiles = 100, maxPerFile = 50, } = options;
781
+ let strategyUsed = 'none';
782
+ try {
783
+ // --- Strategy 1: git grep ---
784
+ const hasBracePattern = typeof include === 'string' &&
785
+ include.length > 0 &&
786
+ hasBraceExpansion(include);
787
+ const gitResult = await this.tryGitGrep(pattern, absolutePath, include, options.signal, maxResults, maxFiles, maxPerFile, hasBracePattern);
788
+ if (gitResult !== null) {
789
+ return gitResult;
790
+ }
791
+ // --- Strategy 2: System grep ---
792
+ strategyUsed = 'system grep';
793
+ const sysResult = await this.trySystemGrepStrategy(pattern, absolutePath, include, options.signal, maxResults, maxFiles, maxPerFile);
794
+ if (sysResult !== null) {
795
+ return sysResult;
796
+ }
797
+ // --- Strategy 3: Pure JavaScript Fallback ---
798
+ debugLogger.debug('GrepLogic: Falling back to JavaScript grep implementation.');
799
+ strategyUsed = 'javascript fallback';
800
+ return await this.javascriptGrepFallback(pattern, absolutePath, include, options.signal, maxResults, maxFiles, maxPerFile);
801
+ }
802
+ catch (error) {
803
+ debugLogger.error(`GrepLogic: Error in performGrepSearch (Strategy: ${strategyUsed}): ${getErrorMessage(error)}`);
804
+ throw error; // Re-throw
805
+ }
806
+ }
807
+ }
808
+ // --- GrepLogic Class ---
809
+ /**
810
+ * Implementation of the Grep tool logic (moved from CLI)
811
+ */
812
+ export class GrepTool extends BaseDeclarativeTool {
813
+ host;
814
+ static Name = 'search_file_content'; // Keep static name
815
+ constructor(host) {
816
+ super(GrepTool.Name, 'SearchText', 'Searches for a regular expression pattern within the content of files in a specified directory (or current working directory). Can filter files by a glob pattern. Returns the lines containing matches, along with their file paths and line numbers.', Kind.Search, {
817
+ properties: {
818
+ pattern: {
819
+ description: "The regular expression (regex) pattern to search for within file contents (e.g., 'function\\s+myFunction', 'import\\s+\\{.*\\}\\s+from\\s+.*').",
820
+ type: 'string',
821
+ },
822
+ dir_path: {
823
+ description: 'Optional: The absolute path to the directory to search within. If omitted, searches the current working directory. Can also be a path to a specific file (will search only that file).',
824
+ type: 'string',
825
+ },
826
+ path: {
827
+ description: 'Alternative parameter name for dir_path (for backward compatibility).',
828
+ type: 'string',
829
+ },
830
+ include: {
831
+ description: "Optional: A glob pattern to filter which files are searched (e.g., '*.js', '*.{ts,tsx}', 'src/**'). If omitted, searches all files (respecting potential global ignores).",
832
+ type: 'string',
833
+ },
834
+ max_results: {
835
+ description: 'Optional: Maximum number of total matches to return. Defaults to tool-output-max-items setting or 1000.',
836
+ type: 'number',
837
+ },
838
+ max_files: {
839
+ description: 'Optional: Maximum number of files to include in results. Defaults to 100.',
840
+ type: 'number',
841
+ },
842
+ max_per_file: {
843
+ description: 'Optional: Maximum number of matches per file to return. Defaults to 50.',
844
+ type: 'number',
845
+ },
846
+ timeout_ms: {
847
+ description: 'Optional: Timeout in milliseconds (default: 60000ms = 1 minute, max: 300000ms = 5 minutes). If the operation times out, an error is returned with suggestions.',
848
+ type: 'number',
849
+ },
850
+ },
851
+ required: ['pattern'],
852
+ type: 'object',
853
+ });
854
+ this.host = host;
855
+ }
856
+ validateToolParamValues(params) {
857
+ try {
858
+ new RegExp(params.pattern);
859
+ }
860
+ catch (error) {
861
+ return `Invalid regular expression pattern provided: ${params.pattern}. Error: ${getErrorMessage(error)}`;
862
+ }
863
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: dir_path and path are optional strings, empty string should fall through
864
+ const dirPath = params.dir_path || params.path;
865
+ if (dirPath) {
866
+ try {
867
+ resolveTextSearchTarget(this.host.getTargetDir(), this.host.getWorkspaceRoots(), dirPath);
868
+ }
869
+ catch (error) {
870
+ return getErrorMessage(error);
871
+ }
872
+ }
873
+ return null;
874
+ }
875
+ createInvocation(params, messageBus) {
876
+ const normalizedParams = { ...params };
877
+ if (!normalizedParams.dir_path && normalizedParams.path) {
878
+ normalizedParams.dir_path = normalizedParams.path;
879
+ }
880
+ return new GrepToolInvocation(this.host, normalizedParams, messageBus);
881
+ }
882
+ }
883
+ //# sourceMappingURL=grep.js.map