@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,816 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ /* @plan PLAN-20250212-LSP.P31 */
7
+ /* @requirement REQ-DIAG-010, REQ-GRACE-050, REQ-GRACE-055 */
8
+ import fs from 'node:fs/promises';
9
+ import * as path from 'path';
10
+ import * as Diff from 'diff';
11
+ import process from 'node:process';
12
+ import { BaseDeclarativeTool, BaseToolInvocation, Kind, ToolConfirmationOutcome, } from './tools.js';
13
+ import { ToolErrorType } from '../types/tool-error.js';
14
+ import { makeRelative, shortenPath } from '../utils/paths.js';
15
+ import { isNodeError } from '../utils/errors.js';
16
+ import { DEFAULT_CREATE_PATCH_OPTIONS, getDiffStat, } from '../utils/diffOptions.js';
17
+ import { ReadFileTool } from './read-file.js';
18
+ import { fuzzyReplace } from '../utils/fuzzy-replacer.js';
19
+ import { EDIT_TOOL_NAME } from '../types/tool-names.js';
20
+ import { collectLspDiagnosticsBlock } from '../utils/lsp-diagnostics-helper.js';
21
+ import { debugLogger } from '../utils/debugLogger.js';
22
+ import { ensureParentDirectoriesExist } from '../utils/ensure-dirs.js';
23
+ import { validatePathWithinWorkspace } from '../utils/pathValidation.js';
24
+ import { getEmojiFilter, validateEditState, applyReplacement, countLineGuardedOccurrences, applyLineGuardedReplacement, } from './edit-utils.js';
25
+ function createDefaultToolHost() {
26
+ return {
27
+ getTargetDir: () => process.cwd(),
28
+ getWorkspaceRoots: () => [path.parse(process.cwd()).root],
29
+ getApprovalMode: () => 'auto',
30
+ setApprovalMode: () => { },
31
+ isInteractive: () => false,
32
+ hasFeatureFlag: () => false,
33
+ getFileService: () => ({
34
+ shouldGitIgnoreFile: () => false,
35
+ shouldLlxprtIgnoreFile: () => false,
36
+ filterFiles: (paths) => paths,
37
+ }),
38
+ getFileFilteringOptions: () => ({
39
+ respectGitIgnore: true,
40
+ respectLlxprtIgnore: true,
41
+ }),
42
+ getFileExclusions: () => [],
43
+ getReadManyFilesExclusions: () => [],
44
+ getFileFilteringRespectLlxprtIgnore: () => true,
45
+ getLlxprtIgnoreFilePath: () => null,
46
+ recordFileRead: () => { },
47
+ getLlxprtIgnorePatterns: () => [],
48
+ getEphemeralSettings: () => ({}),
49
+ getDebugMode: () => false,
50
+ };
51
+ }
52
+ function getTargetDirCompat(host) {
53
+ return host.getTargetDir?.() ?? process.cwd();
54
+ }
55
+ function getWorkspaceRootsCompat(host) {
56
+ const maybeHost = host;
57
+ return (maybeHost.getWorkspaceContext?.().getDirectories?.() ??
58
+ maybeHost.getWorkspaceRoots?.() ?? [
59
+ maybeHost.getTargetDir?.() ?? process.cwd(),
60
+ ]);
61
+ }
62
+ function toIdeConnectionStatus(status) {
63
+ if (typeof status === 'string') {
64
+ return status === 'connected' || status === 'connecting'
65
+ ? status
66
+ : 'disconnected';
67
+ }
68
+ if (typeof status === 'object' && status !== null && 'status' in status) {
69
+ return toIdeConnectionStatus(status.status);
70
+ }
71
+ return 'disconnected';
72
+ }
73
+ function hasMessageBusShape(value) {
74
+ return (typeof value === 'object' &&
75
+ value !== null &&
76
+ ('requestConfirmation' in value ||
77
+ 'publishPolicyUpdate' in value ||
78
+ 'publish' in value ||
79
+ 'subscribe' in value));
80
+ }
81
+ function hasIdeServiceShape(value) {
82
+ return (typeof value === 'object' &&
83
+ value !== null &&
84
+ ('applyDiff' in value ||
85
+ 'openDiff' in value ||
86
+ 'getConnectionStatus' in value));
87
+ }
88
+ function hasLspServiceShape(value) {
89
+ return (typeof value === 'object' &&
90
+ value !== null &&
91
+ ('waitForDiagnostics' in value ||
92
+ 'getDiagnostics' in value ||
93
+ 'getLspConfig' in value));
94
+ }
95
+ function getLegacyIdeService(host) {
96
+ const maybeHost = host;
97
+ if (typeof maybeHost.getIdeMode !== 'function' ||
98
+ typeof maybeHost.getIdeClient !== 'function') {
99
+ return undefined;
100
+ }
101
+ const getLegacyIdeClient = () => {
102
+ if (maybeHost.getIdeMode?.() !== true) {
103
+ return undefined;
104
+ }
105
+ const ideClient = maybeHost.getIdeClient?.();
106
+ if (typeof ideClient !== 'object' ||
107
+ ideClient === null ||
108
+ !('openDiff' in ideClient)) {
109
+ return undefined;
110
+ }
111
+ return ideClient;
112
+ };
113
+ return {
114
+ applyDiff: async ({ filePath, diff }) => {
115
+ const legacyIdeClient = getLegacyIdeClient();
116
+ if (legacyIdeClient?.openDiff === undefined) {
117
+ return { status: 'rejected', content: undefined };
118
+ }
119
+ const result = await legacyIdeClient.openDiff(filePath, diff);
120
+ return result.status === 'accepted'
121
+ ? { status: 'accepted', content: result.content }
122
+ : { status: 'rejected', content: undefined };
123
+ },
124
+ getConnectionStatus: () => toIdeConnectionStatus(getLegacyIdeClient()?.getConnectionStatus?.()),
125
+ openDiff: async ({ filePath, newContent }) => {
126
+ await getLegacyIdeClient()?.openDiff?.(filePath, newContent);
127
+ },
128
+ };
129
+ }
130
+ function getLegacyLspService(host) {
131
+ const maybeHost = host;
132
+ const lspClient = maybeHost.getLspServiceClient?.();
133
+ if (typeof lspClient !== 'object' || lspClient === null) {
134
+ return undefined;
135
+ }
136
+ return {
137
+ getDiagnostics: () => [],
138
+ waitForDiagnostics: async (filePath, _timeout) => {
139
+ const isAlive = lspClient.isAlive?.();
140
+ if (isAlive !== true) {
141
+ return [];
142
+ }
143
+ const checkFile = lspClient.checkFile;
144
+ if (typeof checkFile !== 'function') {
145
+ return [];
146
+ }
147
+ const diagnostics = await checkFile.call(lspClient, filePath);
148
+ return Array.isArray(diagnostics) ? diagnostics : [];
149
+ },
150
+ getLspConfig: () => maybeHost.getLspConfig?.(),
151
+ };
152
+ }
153
+ export { applyReplacement, countLineGuardedOccurrences, applyLineGuardedReplacement, } from './edit-utils.js';
154
+ class EditToolInvocation extends BaseToolInvocation {
155
+ host;
156
+ ideService;
157
+ lspService;
158
+ constructor(host, ideService, lspService, params, messageBus, toolName, displayName) {
159
+ super(params, messageBus, toolName, displayName);
160
+ this.host = host;
161
+ this.ideService = ideService;
162
+ this.lspService = lspService;
163
+ }
164
+ getToolName() {
165
+ return EditTool.Name;
166
+ }
167
+ getFilePath() {
168
+ // Use absolute_path if provided, otherwise fall back to file_path
169
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: empty string paths are invalid, fall back to file_path
170
+ return this.params.absolute_path || this.params.file_path || '';
171
+ }
172
+ toolLocations() {
173
+ return [{ path: this.getFilePath() }];
174
+ }
175
+ /**
176
+ * Counts occurrences of oldString in content, trying fuzzy then strict.
177
+ */
178
+ countOccurrences(currentContent, finalOldString, finalNewString, expectedReplacements, replaceLine, filePath) {
179
+ if (finalOldString === '') {
180
+ return { occurrences: 0, error: undefined };
181
+ }
182
+ if (replaceLine !== undefined && replaceLine > 0) {
183
+ const lines = currentContent.split('\n');
184
+ if (replaceLine > lines.length) {
185
+ return {
186
+ occurrences: 0,
187
+ error: {
188
+ display: `Failed to edit: replaceBeginLineNumber is out of range.`,
189
+ raw: `Failed to edit: replaceBeginLineNumber=${replaceLine} is out of range for ${filePath} (total lines: ${lines.length}). No edits made.`,
190
+ type: ToolErrorType.INVALID_TOOL_PARAMS,
191
+ },
192
+ };
193
+ }
194
+ const count = countLineGuardedOccurrences(currentContent, finalOldString, replaceLine);
195
+ return { occurrences: count, error: undefined };
196
+ }
197
+ const fuzzyResult = fuzzyReplace(currentContent, finalOldString, finalNewString, expectedReplacements > 1);
198
+ if (fuzzyResult) {
199
+ return { occurrences: fuzzyResult.occurrences, error: undefined };
200
+ }
201
+ let count = 0;
202
+ let pos = currentContent.indexOf(finalOldString);
203
+ while (pos !== -1) {
204
+ count++;
205
+ pos = currentContent.indexOf(finalOldString, pos + finalOldString.length);
206
+ }
207
+ return { occurrences: count, error: undefined };
208
+ }
209
+ /**
210
+ * Validates the edit parameters after reading file content and builds the
211
+ * appropriate error object if any validation fails.
212
+ */
213
+ validateEditState(filteredParams, currentContent, fileExists, filePath, occurrences, expectedReplacements, finalOldString, finalNewString) {
214
+ return validateEditState(filteredParams, currentContent, fileExists, filePath, occurrences, expectedReplacements, finalOldString, finalNewString);
215
+ }
216
+ /**
217
+ * Applies the replacement to produce newContent, handling replaceLine.
218
+ */
219
+ computeNewContent(currentContent, fileExists, isNewFile, filteredParams, finalOldString, finalNewString, expectedReplacements, filePath) {
220
+ const replaceLine = filteredParams.replaceBeginLineNumber;
221
+ if (fileExists &&
222
+ replaceLine !== undefined &&
223
+ replaceLine > 0 &&
224
+ currentContent !== null) {
225
+ const lines = currentContent.split('\n');
226
+ if (replaceLine > lines.length) {
227
+ return {
228
+ newContent: currentContent,
229
+ error: {
230
+ display: `Failed to edit: replaceBeginLineNumber is out of range.`,
231
+ raw: `Failed to edit: replaceBeginLineNumber=${replaceLine} is out of range for ${filePath} (total lines: ${lines.length}). No edits made.`,
232
+ type: ToolErrorType.INVALID_TOOL_PARAMS,
233
+ },
234
+ };
235
+ }
236
+ const result = applyLineGuardedReplacement(currentContent, finalOldString, finalNewString, expectedReplacements, replaceLine);
237
+ return {
238
+ newContent: result,
239
+ error: undefined,
240
+ };
241
+ }
242
+ const newContent = applyReplacement(currentContent, finalOldString, finalNewString, isNewFile, expectedReplacements);
243
+ return { newContent, error: undefined };
244
+ }
245
+ async readTextFile(filePath) {
246
+ const fileSystemService = this.host.getFileSystemService?.();
247
+ if (fileSystemService !== undefined) {
248
+ return fileSystemService.readTextFile(filePath);
249
+ }
250
+ return fs.readFile(filePath, 'utf8');
251
+ }
252
+ async writeTextFile(filePath, content) {
253
+ const fileSystemService = this.host.getFileSystemService?.();
254
+ if (fileSystemService !== undefined) {
255
+ await fileSystemService.writeTextFile(filePath, content);
256
+ return;
257
+ }
258
+ await fs.writeFile(filePath, content, 'utf8');
259
+ }
260
+ /**
261
+ * Reads the current file content, handling ENOENT for new files.
262
+ */
263
+ async readFileState(filePath) {
264
+ try {
265
+ let currentContent = await this.readTextFile(filePath);
266
+ currentContent = currentContent.replace(/\r\n/g, '\n');
267
+ return { currentContent, fileExists: true };
268
+ }
269
+ catch (err) {
270
+ if (!isNodeError(err) || err.code !== 'ENOENT') {
271
+ throw err;
272
+ }
273
+ return { currentContent: null, fileExists: false };
274
+ }
275
+ }
276
+ /**
277
+ * Reads file state and validates the edit, returning resolved values.
278
+ */
279
+ async resolveFileEditState(filteredParams, filePath, expectedReplacements) {
280
+ const { currentContent, fileExists } = await this.readFileState(filePath);
281
+ let isNewFile = false;
282
+ const finalOldString = filteredParams.old_string;
283
+ const finalNewString = filteredParams.new_string;
284
+ let occurrences = 0;
285
+ let error = undefined;
286
+ if (filteredParams.old_string === '' && !fileExists) {
287
+ isNewFile = true;
288
+ }
289
+ else if (!fileExists) {
290
+ error = {
291
+ display: `File not found. Cannot apply edit. Use an empty old_string to create a new file.`,
292
+ raw: `File not found: ${filePath}`,
293
+ type: ToolErrorType.FILE_NOT_FOUND,
294
+ };
295
+ }
296
+ else if (currentContent !== null) {
297
+ const countResult = this.countOccurrences(currentContent, finalOldString, finalNewString, expectedReplacements, filteredParams.replaceBeginLineNumber, filePath);
298
+ occurrences = countResult.occurrences;
299
+ error = countResult.error;
300
+ error ??= this.validateEditState(filteredParams, currentContent, fileExists, filePath, occurrences, expectedReplacements, finalOldString, finalNewString);
301
+ }
302
+ else {
303
+ error = {
304
+ display: `Failed to read content of file.`,
305
+ raw: `Failed to read content of existing file: ${filePath}`,
306
+ type: ToolErrorType.READ_CONTENT_FAILURE,
307
+ };
308
+ }
309
+ return {
310
+ currentContent,
311
+ fileExists,
312
+ isNewFile,
313
+ finalOldString,
314
+ finalNewString,
315
+ occurrences,
316
+ error,
317
+ };
318
+ }
319
+ /**
320
+ * Calculates the potential outcome of an edit operation.
321
+ * @param params Parameters for the edit operation
322
+ * @returns An object describing the potential edit outcome
323
+ * @throws File system errors if reading the file fails unexpectedly (e.g., permissions)
324
+ */
325
+ async calculateEdit(params, _abortSignal) {
326
+ // Apply emoji filtering to edit content
327
+ // NOTE: old_string is NOT filtered because it needs to match existing content exactly
328
+ // Only new_string is filtered to remove emojis from the replacement text
329
+ const filter = getEmojiFilter(this.host);
330
+ const newStringResult = filter.filterFileContent(params.new_string, 'edit');
331
+ // Handle blocking in error mode (only check new_string, not old_string)
332
+ if (newStringResult.blocked) {
333
+ return {
334
+ currentContent: null,
335
+ newContent: '',
336
+ occurrences: 0,
337
+ error: {
338
+ display: 'Cannot edit files with emojis in content',
339
+ raw: 'Emoji filtering blocked the edit operation',
340
+ type: ToolErrorType.INVALID_TOOL_PARAMS,
341
+ },
342
+ isNewFile: false,
343
+ filterResult: newStringResult,
344
+ };
345
+ }
346
+ // Use filtered content for the edit (only filter new_string)
347
+ const filteredParams = {
348
+ ...params,
349
+ new_string: newStringResult.filtered,
350
+ };
351
+ const expectedReplacements = filteredParams.expected_replacements ?? 1;
352
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: empty string paths are invalid, fall back to file_path
353
+ const filePath = params.absolute_path || params.file_path || '';
354
+ const { currentContent, fileExists, isNewFile, finalOldString, finalNewString, occurrences, error, } = await this.resolveFileEditState(filteredParams, filePath, expectedReplacements);
355
+ let newContent;
356
+ let resolvedError = error;
357
+ if (!resolvedError) {
358
+ const contentResult = this.computeNewContent(currentContent, fileExists, isNewFile, filteredParams, finalOldString, finalNewString, expectedReplacements, filePath);
359
+ newContent = contentResult.newContent;
360
+ resolvedError = contentResult.error;
361
+ }
362
+ else {
363
+ newContent = currentContent ?? '';
364
+ }
365
+ if (!resolvedError && fileExists && currentContent === newContent) {
366
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: empty string paths are invalid, fall back to file_path
367
+ const fp = params.absolute_path || params.file_path || '';
368
+ resolvedError = {
369
+ display: 'No changes to apply. The new content is identical to the current content.',
370
+ raw: `No changes to apply. The new content is identical to the current content in file: ${fp}`,
371
+ type: ToolErrorType.EDIT_NO_CHANGE,
372
+ };
373
+ }
374
+ return {
375
+ currentContent,
376
+ newContent,
377
+ occurrences,
378
+ error: resolvedError,
379
+ isNewFile,
380
+ filterResult: newStringResult,
381
+ };
382
+ }
383
+ /**
384
+ * Returns confirmation details for this edit operation.
385
+ * Called by getMessageBusDecision before surfacing operations to the policy engine/message bus.
386
+ */
387
+ getConfirmationDetails() {
388
+ // This is a synchronous method, so we can't calculate the edit here
389
+ // Instead, we'll need to handle confirmation in shouldConfirmExecute, which
390
+ // is invoked when the scheduler needs the diff payload for ASK_USER flows.
391
+ return null;
392
+ }
393
+ /**
394
+ * Handles the confirmation prompt for the Edit tool in the CLI.
395
+ * It needs to calculate the diff to show the user.
396
+ */
397
+ async shouldConfirmExecute(abortSignal) {
398
+ const approvalMode = this.host.getApprovalMode();
399
+ if (approvalMode === 'auto' || approvalMode === 'yolo') {
400
+ return false;
401
+ }
402
+ let editData;
403
+ try {
404
+ editData = await this.calculateEdit(this.params, abortSignal);
405
+ }
406
+ catch (error) {
407
+ if (abortSignal.aborted) {
408
+ throw error;
409
+ }
410
+ const errorMsg = error instanceof Error ? error.message : String(error);
411
+ debugLogger.log(`Error preparing edit: ${errorMsg}`);
412
+ return false;
413
+ }
414
+ if (editData.error) {
415
+ return false;
416
+ }
417
+ // NOTE: Emoji filtering was already applied to new_string in calculateEdit()
418
+ // We should NOT filter the entire file content here
419
+ const filteredNewContent = editData.newContent;
420
+ // Also filter the original new_string parameter for use in onConfirm
421
+ const filter = getEmojiFilter(this.host);
422
+ const filteredNewStringParam = filter.filterFileContent(this.params.new_string, 'edit');
423
+ const filteredNewString = typeof filteredNewStringParam.filtered === 'string'
424
+ ? filteredNewStringParam.filtered
425
+ : this.params.new_string;
426
+ const filePath = this.getFilePath();
427
+ const fileName = path.basename(filePath);
428
+ const fileDiff = Diff.createPatch(fileName, editData.currentContent ?? '', filteredNewContent, 'Current', 'Proposed', DEFAULT_CREATE_PATCH_OPTIONS);
429
+ const ideConfirmation = this.ideService !== undefined &&
430
+ toIdeConnectionStatus(this.ideService.getConnectionStatus()) ===
431
+ 'connected'
432
+ ? this.ideService.applyDiff({ filePath, diff: filteredNewContent })
433
+ : undefined;
434
+ const confirmationDetails = {
435
+ type: 'edit',
436
+ title: `Confirm Edit: ${shortenPath(makeRelative(filePath, getTargetDirCompat(this.host)))}`,
437
+ fileName,
438
+ filePath,
439
+ fileDiff,
440
+ originalContent: editData.currentContent,
441
+ newContent: filteredNewContent,
442
+ onConfirm: async (outcome) => {
443
+ if (outcome === ToolConfirmationOutcome.ProceedAlways) {
444
+ // No need to publish a policy update as the default policy for
445
+ // AUTO_EDIT already reflects always approving edit.
446
+ const legacyAutoEdit = this.host
447
+ .constructor?.name !== 'CoreToolHostAdapter';
448
+ this.host.setApprovalMode(legacyAutoEdit ? 'autoEdit' : 'auto');
449
+ }
450
+ else {
451
+ await this.publishPolicyUpdate(outcome);
452
+ }
453
+ if (ideConfirmation) {
454
+ const result = await ideConfirmation;
455
+ if (result.status === 'accepted' && result.content) {
456
+ // Task(chrstn): See https://github.com/google-gemini/gemini-cli/pull/5618#discussion_r2255413084
457
+ // for info on a possible race condition where the file is modified on disk while being edited.
458
+ // FIX: IDE confirmation is for visual review only
459
+ // The IDE returns the entire file content, not just the replacement text
460
+ // We should use our original calculated replacement, not the IDE's full file content
461
+ // Otherwise we'd replace a small string with the entire file, causing duplication
462
+ // Use the filtered version of the original new_string parameter
463
+ this.params.new_string = filteredNewString;
464
+ }
465
+ }
466
+ else {
467
+ // DON'T modify params - they need to stay as the original strings
468
+ // The filtering has already been applied in calculateEdit()
469
+ }
470
+ },
471
+ ideConfirmation,
472
+ };
473
+ return confirmationDetails;
474
+ }
475
+ getDescription() {
476
+ const filePath = this.getFilePath();
477
+ const relativePath = makeRelative(filePath, getTargetDirCompat(this.host));
478
+ if (this.params.old_string === '') {
479
+ return `Create ${shortenPath(relativePath)}`;
480
+ }
481
+ const oldStringSnippet = this.params.old_string.split('\n')[0].substring(0, 30) +
482
+ (this.params.old_string.length > 30 ? '...' : '');
483
+ const newStringSnippet = this.params.new_string.split('\n')[0].substring(0, 30) +
484
+ (this.params.new_string.length > 30 ? '...' : '');
485
+ if (this.params.old_string === this.params.new_string) {
486
+ return `No file changes to ${shortenPath(relativePath)}`;
487
+ }
488
+ return `${shortenPath(relativePath)}: ${oldStringSnippet} => ${newStringSnippet}`;
489
+ }
490
+ /**
491
+ * Tracks git stats for the edit if logging is enabled.
492
+ */
493
+ async trackGitStats(filePath, currentContent, newContent) {
494
+ if (this.host.getConversationLoggingEnabled?.() !== true)
495
+ return null;
496
+ const gitStatsService = this.host.getGitStatsService?.();
497
+ if (!gitStatsService)
498
+ return null;
499
+ try {
500
+ return await gitStatsService.trackFileEdit(filePath,
501
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: undefined currentContent should default to empty
502
+ currentContent || '', newContent);
503
+ }
504
+ catch (error) {
505
+ debugLogger.warn('Failed to track git stats:', error);
506
+ return null;
507
+ }
508
+ }
509
+ /**
510
+ * Builds the LLM success message parts for a successful edit.
511
+ */
512
+ buildSuccessMessage(editData, filePath) {
513
+ const parts = [
514
+ editData.isNewFile
515
+ ? `Created new file: ${filePath} with provided content.`
516
+ : `Successfully modified file: ${filePath} (${editData.occurrences} replacements).`,
517
+ ];
518
+ if (this.params.modified_by_user === true) {
519
+ parts.push(`User modified the \`new_string\` content to be: ${this.params.new_string}.`);
520
+ }
521
+ if (editData.filterResult?.systemFeedback) {
522
+ parts.push(`\n\n<system-reminder>\n${editData.filterResult.systemFeedback}\n</system-reminder>`);
523
+ }
524
+ return parts;
525
+ }
526
+ /**
527
+ * Appends LSP diagnostics to the message parts.
528
+ */
529
+ async appendDiagnostics(llmParts, filePath) {
530
+ try {
531
+ const diagBlock = this.lspService === undefined
532
+ ? null
533
+ : await collectLspDiagnosticsBlock(this.lspService, this.host, filePath);
534
+ if (diagBlock) {
535
+ llmParts.push(diagBlock);
536
+ }
537
+ }
538
+ catch {
539
+ // LSP failure must never fail the edit (REQ-GRACE-050, REQ-GRACE-055)
540
+ }
541
+ }
542
+ /**
543
+ * Builds the ToolResult for a successful write, including diff, diagnostics,
544
+ * and optional git-stats metadata.
545
+ */
546
+ async buildWriteResult(editData, filePath) {
547
+ const gitStats = await this.trackGitStats(filePath, editData.currentContent, editData.newContent);
548
+ const fileName = path.basename(filePath);
549
+ const originallyProposedContent = this.params.ai_proposed_content ?? editData.newContent;
550
+ const diffStat = getDiffStat(fileName, editData.currentContent ?? '', originallyProposedContent, editData.newContent);
551
+ const fileDiff = Diff.createPatch(fileName, editData.currentContent ?? '', editData.newContent, 'Current', 'Proposed', DEFAULT_CREATE_PATCH_OPTIONS);
552
+ const displayResult = {
553
+ fileDiff,
554
+ fileName,
555
+ filePath: this.params.file_path,
556
+ originalContent: editData.currentContent,
557
+ newContent: editData.newContent,
558
+ diffStat,
559
+ isNewFile: editData.isNewFile,
560
+ };
561
+ const llmSuccessMessageParts = this.buildSuccessMessage(editData, filePath);
562
+ // @plan PLAN-20250212-LSP.P31
563
+ // @requirement REQ-DIAG-010
564
+ await this.appendDiagnostics(llmSuccessMessageParts, filePath);
565
+ const result = {
566
+ llmContent: llmSuccessMessageParts.join('\n\n'),
567
+ returnDisplay: displayResult,
568
+ };
569
+ if (gitStats != null) {
570
+ result.metadata = {
571
+ ...result.metadata,
572
+ gitStats,
573
+ };
574
+ }
575
+ return result;
576
+ }
577
+ /**
578
+ * Executes the edit operation with the given parameters.
579
+ * @param params Parameters for the edit operation
580
+ * @returns Result of the edit operation
581
+ */
582
+ async execute(signal) {
583
+ let editData;
584
+ try {
585
+ editData = await this.calculateEdit(this.params, signal);
586
+ }
587
+ catch (error) {
588
+ if (signal.aborted) {
589
+ throw error;
590
+ }
591
+ const errorMsg = error instanceof Error ? error.message : String(error);
592
+ return {
593
+ llmContent: `Error preparing edit: ${errorMsg}`,
594
+ returnDisplay: `Error preparing edit: ${errorMsg}`,
595
+ error: {
596
+ message: errorMsg,
597
+ type: ToolErrorType.EDIT_PREPARATION_FAILURE,
598
+ },
599
+ };
600
+ }
601
+ if (editData.error) {
602
+ return {
603
+ llmContent: editData.error.raw,
604
+ returnDisplay: `Error: ${editData.error.display}`,
605
+ error: {
606
+ message: editData.error.raw,
607
+ type: editData.error.type,
608
+ },
609
+ };
610
+ }
611
+ const filePath = this.getFilePath();
612
+ try {
613
+ await ensureParentDirectoriesExist(filePath);
614
+ await this.writeTextFile(filePath, editData.newContent);
615
+ return await this.buildWriteResult(editData, filePath);
616
+ }
617
+ catch (error) {
618
+ const errorMsg = error instanceof Error ? error.message : String(error);
619
+ return {
620
+ llmContent: `Error executing edit: ${errorMsg}`,
621
+ returnDisplay: `Error writing file: ${errorMsg}`,
622
+ error: {
623
+ message: errorMsg,
624
+ type: ToolErrorType.FILE_WRITE_FAILURE,
625
+ },
626
+ };
627
+ }
628
+ }
629
+ }
630
+ /**
631
+ * Implementation of the Edit tool logic
632
+ */
633
+ export class EditTool extends BaseDeclarativeTool {
634
+ host;
635
+ static Name = EDIT_TOOL_NAME;
636
+ ideService;
637
+ lspService;
638
+ constructor(host = createDefaultToolHost(), messageBusOrIdeService, ideServiceOrLspService, lspService) {
639
+ const secondArgumentIsMessageBus = hasMessageBusShape(messageBusOrIdeService);
640
+ const explicitIdeService = secondArgumentIsMessageBus
641
+ ? ideServiceOrLspService
642
+ : messageBusOrIdeService;
643
+ const ideService = hasIdeServiceShape(explicitIdeService)
644
+ ? explicitIdeService
645
+ : getLegacyIdeService(host);
646
+ const messageBus = secondArgumentIsMessageBus
647
+ ? messageBusOrIdeService
648
+ : undefined;
649
+ const explicitLspService = secondArgumentIsMessageBus
650
+ ? lspService
651
+ : ideServiceOrLspService;
652
+ const resolvedLspService = hasLspServiceShape(explicitLspService)
653
+ ? explicitLspService
654
+ : getLegacyLspService(host);
655
+ super(EditTool.Name, 'Edit', `Replaces text within a file. By default, replaces a single occurrence, but can replace multiple occurrences when \`expected_replacements\` is specified. This tool requires providing significant context around the change to ensure precise targeting. Always use the ${ReadFileTool.Name} tool to examine the file's current content before attempting a text replacement.
656
+
657
+ The user has the ability to modify the \`new_string\` content. If modified, this will be stated in the response.
658
+
659
+ Expectation for required parameters:
660
+ 1. \`file_path\` MUST be an absolute path; otherwise an error will be thrown.
661
+ 2. \`old_string\` MUST be the exact literal text to replace (including all whitespace, indentation, newlines, and surrounding code etc.).
662
+ 3. \`new_string\` MUST be the exact literal text to replace \`old_string\` with (also including all whitespace, indentation, newlines, and surrounding code etc.). Ensure the resulting code is correct and idiomatic.
663
+ 4. NEVER escape \`old_string\` or \`new_string\`, that would break the exact literal text requirement.
664
+ 5. If you do not provide \`replaceBeginLineNumber\` and the same text appears multiple times in the file, the tool will return an error instead of applying an ambiguous change.
665
+ **Multiple replacements:** Set \`expected_replacements\` to the number of occurrences you want to replace. The tool will replace ALL occurrences that match \`old_string\` exactly. Ensure the number of replacements matches your expectation.`, Kind.Edit, {
666
+ properties: {
667
+ absolute_path: {
668
+ description: process.platform === 'win32'
669
+ ? "The absolute path to the file to modify (e.g., 'C:\\Users\\project\\file.txt'). Must be an absolute path."
670
+ : "The absolute path to the file to modify (e.g., '/home/user/project/file.txt'). Must start with '/'.",
671
+ type: 'string',
672
+ },
673
+ file_path: {
674
+ description: 'Alternative parameter name for absolute_path (for backward compatibility). The absolute path to the file to modify.',
675
+ type: 'string',
676
+ },
677
+ old_string: {
678
+ description: 'The exact literal text to replace, preferably unescaped. For single replacements (default), include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. For multiple replacements, specify expected_replacements parameter. If this string is not the exact literal text (i.e. you escaped it) or does not match exactly, the tool will fail.',
679
+ type: 'string',
680
+ },
681
+ new_string: {
682
+ description: 'The exact literal text to replace `old_string` with, preferably unescaped. Provide the EXACT text. Ensure the resulting code is correct and idiomatic.',
683
+ type: 'string',
684
+ },
685
+ expected_replacements: {
686
+ type: 'number',
687
+ description: 'Number of replacements expected. Defaults to 1 if not specified. Use when you want to replace multiple occurrences.',
688
+ minimum: 1,
689
+ },
690
+ replaceBeginLineNumber: {
691
+ type: 'number',
692
+ description: 'Optional 1-based line number where the replacement should begin. Strongly recommended to always set this to guard against misinterpreting the file structure, especially when similar text appears multiple times.',
693
+ minimum: 1,
694
+ },
695
+ },
696
+ required: ['old_string', 'new_string'],
697
+ type: 'object',
698
+ }, false, false, messageBus);
699
+ this.host = host;
700
+ this.ideService = ideService;
701
+ this.lspService = resolvedLspService;
702
+ }
703
+ /**
704
+ * Validates the parameters for the Edit tool
705
+ * @param params Parameters to validate
706
+ * @returns Error message string or null if valid
707
+ */
708
+ validateToolParamValues(params) {
709
+ // Accept either absolute_path or file_path
710
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: empty string paths are invalid
711
+ const filePath = params.absolute_path || params.file_path || '';
712
+ if (filePath.trim() === '') {
713
+ return "Either 'absolute_path' or 'file_path' parameter must be provided and non-empty.";
714
+ }
715
+ if (!path.isAbsolute(filePath)) {
716
+ return `File path must be absolute: ${filePath}`;
717
+ }
718
+ const pathError = validatePathWithinWorkspace(getWorkspaceRootsCompat(this.host), filePath);
719
+ if (pathError) {
720
+ return pathError;
721
+ }
722
+ // Validate that empty old_string with multiple replacements is not allowed
723
+ const expectedReplacements = params.expected_replacements ?? 1;
724
+ if (params.old_string === '' && expectedReplacements > 1) {
725
+ return `Cannot perform multiple replacements with empty old_string (would cause infinite loop)`;
726
+ }
727
+ const replaceLine = params.replaceBeginLineNumber;
728
+ if (replaceLine !== undefined &&
729
+ (!Number.isFinite(replaceLine) ||
730
+ !Number.isInteger(replaceLine) ||
731
+ replaceLine <= 0)) {
732
+ return `replaceBeginLineNumber must be a positive integer (1-based)`;
733
+ }
734
+ return null;
735
+ }
736
+ createInvocation(params, messageBus, toolName, displayName) {
737
+ // Normalize parameters: if file_path is provided but not absolute_path, copy it over
738
+ const normalizedParams = { ...params };
739
+ if (!normalizedParams.absolute_path && normalizedParams.file_path) {
740
+ normalizedParams.absolute_path = normalizedParams.file_path;
741
+ }
742
+ return new EditToolInvocation(this.host, this.ideService, this.lspService, normalizedParams, messageBus, toolName ?? this.name, displayName ?? this.displayName);
743
+ }
744
+ async readTextFile(filePath) {
745
+ const fileSystemService = this.host.getFileSystemService?.();
746
+ if (fileSystemService !== undefined) {
747
+ return fileSystemService.readTextFile(filePath);
748
+ }
749
+ return fs.readFile(filePath, 'utf8');
750
+ }
751
+ async execute(params, signal = new AbortController().signal) {
752
+ return this.build(params).execute(signal);
753
+ }
754
+ getModifyContext(_) {
755
+ return {
756
+ getFilePath: (params) =>
757
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: empty string paths are invalid, fall back to file_path
758
+ params.absolute_path || params.file_path || '',
759
+ getCurrentContent: async (params) => {
760
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: empty string paths are invalid, fall back to file_path
761
+ const filePath = params.absolute_path || params.file_path || '';
762
+ try {
763
+ return await this.readTextFile(filePath);
764
+ }
765
+ catch (err) {
766
+ if (!isNodeError(err) || err.code !== 'ENOENT')
767
+ throw err;
768
+ return '';
769
+ }
770
+ },
771
+ getProposedContent: async (params) => {
772
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing: empty string paths are invalid, fall back to file_path
773
+ const filePath = params.absolute_path || params.file_path || '';
774
+ try {
775
+ const raw = await this.readTextFile(filePath);
776
+ const currentContent = raw.replace(/\r\n/g, '\n');
777
+ const replaceLine = params.replaceBeginLineNumber;
778
+ if (replaceLine !== undefined &&
779
+ replaceLine > 0 &&
780
+ params.old_string !== '') {
781
+ // When replaceBeginLineNumber is set, only replace occurrences
782
+ // whose start falls on the specified line.
783
+ const expectedReplacements = params.expected_replacements ?? 1;
784
+ const eligibleCount = countLineGuardedOccurrences(currentContent, params.old_string, replaceLine);
785
+ // If eligible count doesn't match expected_replacements, execute()
786
+ // would reject with a mismatch error. Return unchanged content to
787
+ // avoid presenting a partial proposal that would never be written.
788
+ if (eligibleCount !== expectedReplacements) {
789
+ return currentContent;
790
+ }
791
+ return applyLineGuardedReplacement(currentContent, params.old_string, params.new_string, expectedReplacements, replaceLine);
792
+ }
793
+ const isNewFile = params.old_string === '' && currentContent === '';
794
+ return applyReplacement(currentContent, params.old_string, params.new_string, isNewFile, params.expected_replacements ?? 1);
795
+ }
796
+ catch (err) {
797
+ if (!isNodeError(err) || err.code !== 'ENOENT')
798
+ throw err;
799
+ // File does not exist: if old_string is empty, this is a new-file creation.
800
+ if (params.old_string === '') {
801
+ return params.new_string;
802
+ }
803
+ return '';
804
+ }
805
+ },
806
+ createUpdatedParams: (oldContent, modifiedProposedContent, originalParams) => ({
807
+ ...originalParams,
808
+ ai_proposed_content: oldContent,
809
+ old_string: oldContent,
810
+ new_string: modifiedProposedContent,
811
+ modified_by_user: true,
812
+ }),
813
+ };
814
+ }
815
+ }
816
+ //# sourceMappingURL=edit.js.map