@cdoing/core 0.1.0

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 (378) hide show
  1. package/dist/agents/coordinator.d.ts +114 -0
  2. package/dist/agents/coordinator.d.ts.map +1 -0
  3. package/dist/agents/coordinator.js +158 -0
  4. package/dist/agents/coordinator.js.map +1 -0
  5. package/dist/context-providers/clipboard.d.ts +13 -0
  6. package/dist/context-providers/clipboard.d.ts.map +1 -0
  7. package/dist/context-providers/clipboard.js +53 -0
  8. package/dist/context-providers/clipboard.js.map +1 -0
  9. package/dist/context-providers/codebase.d.ts +46 -0
  10. package/dist/context-providers/codebase.d.ts.map +1 -0
  11. package/dist/context-providers/codebase.js +273 -0
  12. package/dist/context-providers/codebase.js.map +1 -0
  13. package/dist/context-providers/diff.d.ts +18 -0
  14. package/dist/context-providers/diff.d.ts.map +1 -0
  15. package/dist/context-providers/diff.js +63 -0
  16. package/dist/context-providers/diff.js.map +1 -0
  17. package/dist/context-providers/docs.d.ts +21 -0
  18. package/dist/context-providers/docs.d.ts.map +1 -0
  19. package/dist/context-providers/docs.js +180 -0
  20. package/dist/context-providers/docs.js.map +1 -0
  21. package/dist/context-providers/file-include.d.ts +13 -0
  22. package/dist/context-providers/file-include.d.ts.map +1 -0
  23. package/dist/context-providers/file-include.js +82 -0
  24. package/dist/context-providers/file-include.js.map +1 -0
  25. package/dist/context-providers/folder.d.ts +19 -0
  26. package/dist/context-providers/folder.d.ts.map +1 -0
  27. package/dist/context-providers/folder.js +130 -0
  28. package/dist/context-providers/folder.js.map +1 -0
  29. package/dist/context-providers/git.d.ts +19 -0
  30. package/dist/context-providers/git.d.ts.map +1 -0
  31. package/dist/context-providers/git.js +74 -0
  32. package/dist/context-providers/git.js.map +1 -0
  33. package/dist/context-providers/index.d.ts +26 -0
  34. package/dist/context-providers/index.d.ts.map +1 -0
  35. package/dist/context-providers/index.js +37 -0
  36. package/dist/context-providers/index.js.map +1 -0
  37. package/dist/context-providers/open-files.d.ts +25 -0
  38. package/dist/context-providers/open-files.d.ts.map +1 -0
  39. package/dist/context-providers/open-files.js +134 -0
  40. package/dist/context-providers/open-files.js.map +1 -0
  41. package/dist/context-providers/problems.d.ts +24 -0
  42. package/dist/context-providers/problems.d.ts.map +1 -0
  43. package/dist/context-providers/problems.js +97 -0
  44. package/dist/context-providers/problems.js.map +1 -0
  45. package/dist/context-providers/registry.d.ts +61 -0
  46. package/dist/context-providers/registry.d.ts.map +1 -0
  47. package/dist/context-providers/registry.js +92 -0
  48. package/dist/context-providers/registry.js.map +1 -0
  49. package/dist/context-providers/terminal.d.ts +25 -0
  50. package/dist/context-providers/terminal.d.ts.map +1 -0
  51. package/dist/context-providers/terminal.js +55 -0
  52. package/dist/context-providers/terminal.js.map +1 -0
  53. package/dist/context-providers/tree.d.ts +29 -0
  54. package/dist/context-providers/tree.d.ts.map +1 -0
  55. package/dist/context-providers/tree.js +172 -0
  56. package/dist/context-providers/tree.js.map +1 -0
  57. package/dist/context-providers/types.d.ts +72 -0
  58. package/dist/context-providers/types.d.ts.map +1 -0
  59. package/dist/context-providers/types.js +10 -0
  60. package/dist/context-providers/types.js.map +1 -0
  61. package/dist/context-providers/url.d.ts +27 -0
  62. package/dist/context-providers/url.d.ts.map +1 -0
  63. package/dist/context-providers/url.js +131 -0
  64. package/dist/context-providers/url.js.map +1 -0
  65. package/dist/effort/index.d.ts +78 -0
  66. package/dist/effort/index.d.ts.map +1 -0
  67. package/dist/effort/index.js +146 -0
  68. package/dist/effort/index.js.map +1 -0
  69. package/dist/hooks/index.d.ts +47 -0
  70. package/dist/hooks/index.d.ts.map +1 -0
  71. package/dist/hooks/index.js +151 -0
  72. package/dist/hooks/index.js.map +1 -0
  73. package/dist/index.d.ts +75 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +152 -0
  76. package/dist/index.js.map +1 -0
  77. package/dist/indexing/chunker.d.ts +25 -0
  78. package/dist/indexing/chunker.d.ts.map +1 -0
  79. package/dist/indexing/chunker.js +217 -0
  80. package/dist/indexing/chunker.js.map +1 -0
  81. package/dist/indexing/database.d.ts +49 -0
  82. package/dist/indexing/database.d.ts.map +1 -0
  83. package/dist/indexing/database.js +287 -0
  84. package/dist/indexing/database.js.map +1 -0
  85. package/dist/indexing/index.d.ts +9 -0
  86. package/dist/indexing/index.d.ts.map +1 -0
  87. package/dist/indexing/index.js +13 -0
  88. package/dist/indexing/index.js.map +1 -0
  89. package/dist/indexing/indexer.d.ts +63 -0
  90. package/dist/indexing/indexer.d.ts.map +1 -0
  91. package/dist/indexing/indexer.js +352 -0
  92. package/dist/indexing/indexer.js.map +1 -0
  93. package/dist/indexing/recent-edits-cache.d.ts +77 -0
  94. package/dist/indexing/recent-edits-cache.d.ts.map +1 -0
  95. package/dist/indexing/recent-edits-cache.js +123 -0
  96. package/dist/indexing/recent-edits-cache.js.map +1 -0
  97. package/dist/indexing/types.d.ts +39 -0
  98. package/dist/indexing/types.d.ts.map +1 -0
  99. package/dist/indexing/types.js +6 -0
  100. package/dist/indexing/types.js.map +1 -0
  101. package/dist/mcp/index.d.ts +33 -0
  102. package/dist/mcp/index.d.ts.map +1 -0
  103. package/dist/mcp/index.js +37 -0
  104. package/dist/mcp/index.js.map +1 -0
  105. package/dist/mcp/manager.d.ts +123 -0
  106. package/dist/mcp/manager.d.ts.map +1 -0
  107. package/dist/mcp/manager.js +331 -0
  108. package/dist/mcp/manager.js.map +1 -0
  109. package/dist/oauth.d.ts +33 -0
  110. package/dist/oauth.d.ts.map +1 -0
  111. package/dist/oauth.js +312 -0
  112. package/dist/oauth.js.map +1 -0
  113. package/dist/permissions/index.d.ts +216 -0
  114. package/dist/permissions/index.d.ts.map +1 -0
  115. package/dist/permissions/index.js +938 -0
  116. package/dist/permissions/index.js.map +1 -0
  117. package/dist/plan/index.d.ts +20 -0
  118. package/dist/plan/index.d.ts.map +1 -0
  119. package/dist/plan/index.js +24 -0
  120. package/dist/plan/index.js.map +1 -0
  121. package/dist/plan/manager.d.ts +101 -0
  122. package/dist/plan/manager.d.ts.map +1 -0
  123. package/dist/plan/manager.js +170 -0
  124. package/dist/plan/manager.js.map +1 -0
  125. package/dist/rules/index.d.ts +28 -0
  126. package/dist/rules/index.d.ts.map +1 -0
  127. package/dist/rules/index.js +31 -0
  128. package/dist/rules/index.js.map +1 -0
  129. package/dist/rules/manager.d.ts +77 -0
  130. package/dist/rules/manager.d.ts.map +1 -0
  131. package/dist/rules/manager.js +279 -0
  132. package/dist/rules/manager.js.map +1 -0
  133. package/dist/rules/types.d.ts +34 -0
  134. package/dist/rules/types.d.ts.map +1 -0
  135. package/dist/rules/types.js +9 -0
  136. package/dist/rules/types.js.map +1 -0
  137. package/dist/sandbox/filesystem.d.ts +20 -0
  138. package/dist/sandbox/filesystem.d.ts.map +1 -0
  139. package/dist/sandbox/filesystem.js +141 -0
  140. package/dist/sandbox/filesystem.js.map +1 -0
  141. package/dist/sandbox/index.d.ts +4 -0
  142. package/dist/sandbox/index.d.ts.map +1 -0
  143. package/dist/sandbox/index.js +8 -0
  144. package/dist/sandbox/index.js.map +1 -0
  145. package/dist/sandbox/manager.d.ts +47 -0
  146. package/dist/sandbox/manager.d.ts.map +1 -0
  147. package/dist/sandbox/manager.js +220 -0
  148. package/dist/sandbox/manager.js.map +1 -0
  149. package/dist/sandbox/network.d.ts +14 -0
  150. package/dist/sandbox/network.d.ts.map +1 -0
  151. package/dist/sandbox/network.js +87 -0
  152. package/dist/sandbox/network.js.map +1 -0
  153. package/dist/sandbox/types.d.ts +42 -0
  154. package/dist/sandbox/types.d.ts.map +1 -0
  155. package/dist/sandbox/types.js +25 -0
  156. package/dist/sandbox/types.js.map +1 -0
  157. package/dist/tools/ast-edit.d.ts +57 -0
  158. package/dist/tools/ast-edit.d.ts.map +1 -0
  159. package/dist/tools/ast-edit.js +443 -0
  160. package/dist/tools/ast-edit.js.map +1 -0
  161. package/dist/tools/code-verify.d.ts +8 -0
  162. package/dist/tools/code-verify.d.ts.map +1 -0
  163. package/dist/tools/code-verify.js +159 -0
  164. package/dist/tools/code-verify.js.map +1 -0
  165. package/dist/tools/codebase-search.d.ts +17 -0
  166. package/dist/tools/codebase-search.d.ts.map +1 -0
  167. package/dist/tools/codebase-search.js +104 -0
  168. package/dist/tools/codebase-search.js.map +1 -0
  169. package/dist/tools/file-delete.d.ts +26 -0
  170. package/dist/tools/file-delete.d.ts.map +1 -0
  171. package/dist/tools/file-delete.js +179 -0
  172. package/dist/tools/file-delete.js.map +1 -0
  173. package/dist/tools/file-edit.d.ts +10 -0
  174. package/dist/tools/file-edit.d.ts.map +1 -0
  175. package/dist/tools/file-edit.js +138 -0
  176. package/dist/tools/file-edit.js.map +1 -0
  177. package/dist/tools/file-read.d.ts +12 -0
  178. package/dist/tools/file-read.d.ts.map +1 -0
  179. package/dist/tools/file-read.js +211 -0
  180. package/dist/tools/file-read.js.map +1 -0
  181. package/dist/tools/file-run.d.ts +10 -0
  182. package/dist/tools/file-run.d.ts.map +1 -0
  183. package/dist/tools/file-run.js +179 -0
  184. package/dist/tools/file-run.js.map +1 -0
  185. package/dist/tools/file-write.d.ts +10 -0
  186. package/dist/tools/file-write.d.ts.map +1 -0
  187. package/dist/tools/file-write.js +134 -0
  188. package/dist/tools/file-write.js.map +1 -0
  189. package/dist/tools/glob-search.d.ts +8 -0
  190. package/dist/tools/glob-search.d.ts.map +1 -0
  191. package/dist/tools/glob-search.js +108 -0
  192. package/dist/tools/glob-search.js.map +1 -0
  193. package/dist/tools/grep-search.d.ts +8 -0
  194. package/dist/tools/grep-search.d.ts.map +1 -0
  195. package/dist/tools/grep-search.js +139 -0
  196. package/dist/tools/grep-search.js.map +1 -0
  197. package/dist/tools/list-dir.d.ts +16 -0
  198. package/dist/tools/list-dir.d.ts.map +1 -0
  199. package/dist/tools/list-dir.js +183 -0
  200. package/dist/tools/list-dir.js.map +1 -0
  201. package/dist/tools/multi-edit.d.ts +16 -0
  202. package/dist/tools/multi-edit.d.ts.map +1 -0
  203. package/dist/tools/multi-edit.js +163 -0
  204. package/dist/tools/multi-edit.js.map +1 -0
  205. package/dist/tools/notebook-edit.d.ts +31 -0
  206. package/dist/tools/notebook-edit.d.ts.map +1 -0
  207. package/dist/tools/notebook-edit.js +321 -0
  208. package/dist/tools/notebook-edit.js.map +1 -0
  209. package/dist/tools/registry.d.ts +16 -0
  210. package/dist/tools/registry.d.ts.map +1 -0
  211. package/dist/tools/registry.js +41 -0
  212. package/dist/tools/registry.js.map +1 -0
  213. package/dist/tools/shell-exec.d.ts +12 -0
  214. package/dist/tools/shell-exec.d.ts.map +1 -0
  215. package/dist/tools/shell-exec.js +261 -0
  216. package/dist/tools/shell-exec.js.map +1 -0
  217. package/dist/tools/sub-agent-manager.d.ts +57 -0
  218. package/dist/tools/sub-agent-manager.d.ts.map +1 -0
  219. package/dist/tools/sub-agent-manager.js +153 -0
  220. package/dist/tools/sub-agent-manager.js.map +1 -0
  221. package/dist/tools/sub-agent-status.d.ts +12 -0
  222. package/dist/tools/sub-agent-status.d.ts.map +1 -0
  223. package/dist/tools/sub-agent-status.js +59 -0
  224. package/dist/tools/sub-agent-status.js.map +1 -0
  225. package/dist/tools/sub-agent-terminate.d.ts +12 -0
  226. package/dist/tools/sub-agent-terminate.d.ts.map +1 -0
  227. package/dist/tools/sub-agent-terminate.js +55 -0
  228. package/dist/tools/sub-agent-terminate.js.map +1 -0
  229. package/dist/tools/sub-agent.d.ts +34 -0
  230. package/dist/tools/sub-agent.d.ts.map +1 -0
  231. package/dist/tools/sub-agent.js +140 -0
  232. package/dist/tools/sub-agent.js.map +1 -0
  233. package/dist/tools/system-info.d.ts +24 -0
  234. package/dist/tools/system-info.d.ts.map +1 -0
  235. package/dist/tools/system-info.js +220 -0
  236. package/dist/tools/system-info.js.map +1 -0
  237. package/dist/tools/todo.d.ts +16 -0
  238. package/dist/tools/todo.d.ts.map +1 -0
  239. package/dist/tools/todo.js +144 -0
  240. package/dist/tools/todo.js.map +1 -0
  241. package/dist/tools/types.d.ts +20 -0
  242. package/dist/tools/types.d.ts.map +1 -0
  243. package/dist/tools/types.js +3 -0
  244. package/dist/tools/types.js.map +1 -0
  245. package/dist/tools/view-diff.d.ts +11 -0
  246. package/dist/tools/view-diff.d.ts.map +1 -0
  247. package/dist/tools/view-diff.js +88 -0
  248. package/dist/tools/view-diff.js.map +1 -0
  249. package/dist/tools/view-repo-map.d.ts +18 -0
  250. package/dist/tools/view-repo-map.d.ts.map +1 -0
  251. package/dist/tools/view-repo-map.js +245 -0
  252. package/dist/tools/view-repo-map.js.map +1 -0
  253. package/dist/tools/web-fetch.d.ts +13 -0
  254. package/dist/tools/web-fetch.d.ts.map +1 -0
  255. package/dist/tools/web-fetch.js +106 -0
  256. package/dist/tools/web-fetch.js.map +1 -0
  257. package/dist/tools/web-search.d.ts +10 -0
  258. package/dist/tools/web-search.d.ts.map +1 -0
  259. package/dist/tools/web-search.js +106 -0
  260. package/dist/tools/web-search.js.map +1 -0
  261. package/dist/utils/gitignore.d.ts +10 -0
  262. package/dist/utils/gitignore.d.ts.map +1 -0
  263. package/dist/utils/gitignore.js +104 -0
  264. package/dist/utils/gitignore.js.map +1 -0
  265. package/dist/utils/lazy-apply.d.ts +45 -0
  266. package/dist/utils/lazy-apply.d.ts.map +1 -0
  267. package/dist/utils/lazy-apply.js +164 -0
  268. package/dist/utils/lazy-apply.js.map +1 -0
  269. package/dist/utils/memory.d.ts +36 -0
  270. package/dist/utils/memory.d.ts.map +1 -0
  271. package/dist/utils/memory.js +136 -0
  272. package/dist/utils/memory.js.map +1 -0
  273. package/dist/utils/path-matching.d.ts +24 -0
  274. package/dist/utils/path-matching.d.ts.map +1 -0
  275. package/dist/utils/path-matching.js +116 -0
  276. package/dist/utils/path-matching.js.map +1 -0
  277. package/dist/utils/path-safety.d.ts +13 -0
  278. package/dist/utils/path-safety.d.ts.map +1 -0
  279. package/dist/utils/path-safety.js +54 -0
  280. package/dist/utils/path-safety.js.map +1 -0
  281. package/dist/utils/project-config.d.ts +18 -0
  282. package/dist/utils/project-config.d.ts.map +1 -0
  283. package/dist/utils/project-config.js +76 -0
  284. package/dist/utils/project-config.js.map +1 -0
  285. package/dist/utils/search-match.d.ts +63 -0
  286. package/dist/utils/search-match.d.ts.map +1 -0
  287. package/dist/utils/search-match.js +426 -0
  288. package/dist/utils/search-match.js.map +1 -0
  289. package/dist/utils/shell-paths.d.ts +17 -0
  290. package/dist/utils/shell-paths.d.ts.map +1 -0
  291. package/dist/utils/shell-paths.js +107 -0
  292. package/dist/utils/shell-paths.js.map +1 -0
  293. package/dist/utils/streaming-diff.d.ts +45 -0
  294. package/dist/utils/streaming-diff.d.ts.map +1 -0
  295. package/dist/utils/streaming-diff.js +230 -0
  296. package/dist/utils/streaming-diff.js.map +1 -0
  297. package/dist/utils/todo.d.ts +47 -0
  298. package/dist/utils/todo.d.ts.map +1 -0
  299. package/dist/utils/todo.js +102 -0
  300. package/dist/utils/todo.js.map +1 -0
  301. package/package.json +23 -0
  302. package/src/agents/coordinator.ts +240 -0
  303. package/src/context-providers/clipboard.ts +48 -0
  304. package/src/context-providers/codebase.ts +274 -0
  305. package/src/context-providers/diff.ts +66 -0
  306. package/src/context-providers/docs.ts +160 -0
  307. package/src/context-providers/file-include.ts +54 -0
  308. package/src/context-providers/folder.ts +106 -0
  309. package/src/context-providers/git.ts +72 -0
  310. package/src/context-providers/index.ts +26 -0
  311. package/src/context-providers/open-files.ts +113 -0
  312. package/src/context-providers/problems.ts +100 -0
  313. package/src/context-providers/registry.ts +99 -0
  314. package/src/context-providers/terminal.ts +58 -0
  315. package/src/context-providers/tree.ts +161 -0
  316. package/src/context-providers/types.ts +84 -0
  317. package/src/context-providers/url.ts +138 -0
  318. package/src/effort/index.ts +177 -0
  319. package/src/hooks/index.ts +148 -0
  320. package/src/index.ts +114 -0
  321. package/src/indexing/README.md +267 -0
  322. package/src/indexing/chunker.ts +206 -0
  323. package/src/indexing/database.ts +299 -0
  324. package/src/indexing/index.ts +15 -0
  325. package/src/indexing/indexer.ts +383 -0
  326. package/src/indexing/recent-edits-cache.ts +150 -0
  327. package/src/indexing/types.ts +44 -0
  328. package/src/mcp/index.ts +33 -0
  329. package/src/mcp/manager.ts +385 -0
  330. package/src/oauth.ts +330 -0
  331. package/src/permissions/index.ts +1011 -0
  332. package/src/plan/index.ts +20 -0
  333. package/src/plan/manager.ts +233 -0
  334. package/src/rules/index.ts +28 -0
  335. package/src/rules/manager.ts +276 -0
  336. package/src/rules/types.ts +40 -0
  337. package/src/sandbox/filesystem.ts +135 -0
  338. package/src/sandbox/index.ts +9 -0
  339. package/src/sandbox/manager.ts +213 -0
  340. package/src/sandbox/network.ts +101 -0
  341. package/src/sandbox/types.ts +63 -0
  342. package/src/tools/ast-edit.ts +493 -0
  343. package/src/tools/code-verify.ts +143 -0
  344. package/src/tools/codebase-search.ts +117 -0
  345. package/src/tools/file-delete.ts +155 -0
  346. package/src/tools/file-edit.ts +115 -0
  347. package/src/tools/file-read.ts +195 -0
  348. package/src/tools/file-run.ts +158 -0
  349. package/src/tools/file-write.ts +104 -0
  350. package/src/tools/glob-search.ts +80 -0
  351. package/src/tools/grep-search.ts +120 -0
  352. package/src/tools/list-dir.ts +172 -0
  353. package/src/tools/multi-edit.ts +138 -0
  354. package/src/tools/notebook-edit.ts +342 -0
  355. package/src/tools/registry.ts +43 -0
  356. package/src/tools/shell-exec.ts +251 -0
  357. package/src/tools/sub-agent-manager.ts +183 -0
  358. package/src/tools/sub-agent-status.ts +67 -0
  359. package/src/tools/sub-agent-terminate.ts +62 -0
  360. package/src/tools/sub-agent.ts +162 -0
  361. package/src/tools/system-info.ts +248 -0
  362. package/src/tools/todo.ts +149 -0
  363. package/src/tools/types.ts +21 -0
  364. package/src/tools/view-diff.ts +99 -0
  365. package/src/tools/view-repo-map.ts +249 -0
  366. package/src/tools/web-fetch.ts +118 -0
  367. package/src/tools/web-search.ts +129 -0
  368. package/src/utils/gitignore.ts +73 -0
  369. package/src/utils/lazy-apply.ts +189 -0
  370. package/src/utils/memory.ts +124 -0
  371. package/src/utils/path-matching.ts +84 -0
  372. package/src/utils/path-safety.ts +19 -0
  373. package/src/utils/project-config.ts +41 -0
  374. package/src/utils/search-match.ts +495 -0
  375. package/src/utils/shell-paths.ts +79 -0
  376. package/src/utils/streaming-diff.ts +260 -0
  377. package/src/utils/todo.ts +115 -0
  378. package/tsconfig.json +18 -0
@@ -0,0 +1,493 @@
1
+ /**
2
+ * AST Edit Tool — Tree-sitter powered structural code editing.
3
+ *
4
+ * Uses tree-sitter to parse source code into an AST, then performs
5
+ * targeted edits on specific AST nodes (functions, classes, methods,
6
+ * imports, etc.) by name rather than by string matching.
7
+ *
8
+ * This is more reliable than string-based editing for:
9
+ * - Renaming functions/methods/classes across a file
10
+ * - Replacing entire function bodies
11
+ * - Adding/removing imports
12
+ * - Inserting methods into classes
13
+ * - Extracting or inlining code blocks
14
+ */
15
+
16
+ import * as fs from "fs";
17
+ import type { BaseTool, ToolDefinition, ToolResult } from "./types";
18
+ import { safePath } from "../utils/path-safety";
19
+ import type { SandboxManager } from "../sandbox";
20
+
21
+ // Tree-sitter types (loaded dynamically to keep it optional)
22
+ let Parser: any = null;
23
+ const languageCache = new Map<string, any>();
24
+
25
+ /** Supported languages and their tree-sitter package names */
26
+ const LANGUAGE_MAP: Record<string, string> = {
27
+ ".ts": "tree-sitter-typescript/typescript",
28
+ ".tsx": "tree-sitter-typescript/tsx",
29
+ ".js": "tree-sitter-javascript",
30
+ ".jsx": "tree-sitter-javascript",
31
+ ".py": "tree-sitter-python",
32
+ ".rs": "tree-sitter-rust",
33
+ ".go": "tree-sitter-go",
34
+ ".java": "tree-sitter-java",
35
+ ".c": "tree-sitter-c",
36
+ ".cpp": "tree-sitter-cpp",
37
+ ".rb": "tree-sitter-ruby",
38
+ ".css": "tree-sitter-css",
39
+ ".json": "tree-sitter-json",
40
+ };
41
+
42
+ /** AST node types we can target for editing */
43
+ type ASTNodeType =
44
+ | "function"
45
+ | "class"
46
+ | "method"
47
+ | "import"
48
+ | "variable"
49
+ | "type"
50
+ | "interface"
51
+ | "enum"
52
+ | "block"
53
+ | "expression";
54
+
55
+ interface ASTEditOperation {
56
+ /** Type of AST node to target */
57
+ node_type: ASTNodeType;
58
+ /** Name of the node (function name, class name, etc.) */
59
+ name: string;
60
+ /** Action to perform */
61
+ action: "replace" | "rename" | "delete" | "insert_before" | "insert_after" | "replace_body";
62
+ /** New content (for replace, insert_before, insert_after, replace_body) */
63
+ content?: string;
64
+ /** New name (for rename) */
65
+ new_name?: string;
66
+ }
67
+
68
+ export class ASTEditTool implements BaseTool {
69
+ definition: ToolDefinition = {
70
+ name: "ast_edit",
71
+ description:
72
+ `Perform structural edits on source code using Tree-sitter AST parsing. More reliable than string matching for targeting specific code structures.
73
+
74
+ Supported operations:
75
+ - replace: Replace an entire AST node (function, class, etc.) with new content
76
+ - rename: Rename a function, class, method, variable, or type
77
+ - delete: Remove an AST node entirely
78
+ - insert_before/insert_after: Insert content before or after a named node
79
+ - replace_body: Replace only the body of a function/method/class (keeps signature)
80
+
81
+ Supported languages: TypeScript, JavaScript, Python, Rust, Go, Java, C, C++, Ruby, CSS, JSON.
82
+
83
+ Use this when string-matching edits are fragile or when you need to target code by structure rather than text.`,
84
+ inputSchema: {
85
+ type: "object",
86
+ properties: {
87
+ file_path: {
88
+ type: "string",
89
+ description: "Path to the file to edit",
90
+ },
91
+ operations: {
92
+ type: "array",
93
+ description: "Array of AST edit operations to apply sequentially",
94
+ items: {
95
+ type: "object",
96
+ properties: {
97
+ node_type: {
98
+ type: "string",
99
+ enum: ["function", "class", "method", "import", "variable", "type", "interface", "enum", "block", "expression"],
100
+ description: "Type of AST node to target",
101
+ },
102
+ name: {
103
+ type: "string",
104
+ description: "Name of the node (function name, class name, variable name, import module name, etc.)",
105
+ },
106
+ action: {
107
+ type: "string",
108
+ enum: ["replace", "rename", "delete", "insert_before", "insert_after", "replace_body"],
109
+ description: "Action to perform on the node",
110
+ },
111
+ content: {
112
+ type: "string",
113
+ description: "New content (for replace, insert_before, insert_after, replace_body actions)",
114
+ },
115
+ new_name: {
116
+ type: "string",
117
+ description: "New name (for rename action)",
118
+ },
119
+ },
120
+ required: ["node_type", "name", "action"],
121
+ },
122
+ },
123
+ },
124
+ required: ["file_path", "operations"],
125
+ },
126
+ requiresPermission: true,
127
+ permissionMessage: (input) => {
128
+ const ops = input.operations as unknown[];
129
+ return `AST edit: ${input.file_path} (${Array.isArray(ops) ? ops.length : "?"} operation(s))`;
130
+ },
131
+ };
132
+
133
+ private workingDir: string;
134
+ private sandboxManager?: SandboxManager;
135
+
136
+ constructor(workingDir: string, sandboxManager?: SandboxManager) {
137
+ this.workingDir = workingDir;
138
+ this.sandboxManager = sandboxManager;
139
+ }
140
+
141
+ async execute(input: Record<string, unknown>): Promise<ToolResult> {
142
+ let filePath: string;
143
+ try {
144
+ filePath = safePath(input.file_path as string, this.workingDir);
145
+ } catch (err) {
146
+ return { success: false, output: "", error: (err as Error).message };
147
+ }
148
+
149
+ if (this.sandboxManager) {
150
+ const check = this.sandboxManager.checkFileWrite(filePath);
151
+ if (!check.allowed) {
152
+ return { success: false, output: "", error: check.reason || "Sandbox: write access denied" };
153
+ }
154
+ }
155
+
156
+ if (!fs.existsSync(filePath)) {
157
+ return { success: false, output: "", error: `File not found: ${filePath}` };
158
+ }
159
+
160
+ const operations = input.operations as ASTEditOperation[];
161
+ if (!Array.isArray(operations) || operations.length === 0) {
162
+ return { success: false, output: "", error: "operations must be a non-empty array" };
163
+ }
164
+
165
+ // Initialize tree-sitter
166
+ const initResult = await this.initParser(filePath);
167
+ if (!initResult.success) {
168
+ return initResult;
169
+ }
170
+
171
+ let content = fs.readFileSync(filePath, "utf-8");
172
+ const originalContent = content;
173
+ const results: string[] = [];
174
+
175
+ for (let i = 0; i < operations.length; i++) {
176
+ const op = operations[i];
177
+
178
+ // Validate operation
179
+ if (op.action === "rename" && !op.new_name) {
180
+ return { success: false, output: "", error: `Operation ${i + 1}: rename requires new_name` };
181
+ }
182
+ if ((op.action === "replace" || op.action === "insert_before" || op.action === "insert_after" || op.action === "replace_body") && op.content === undefined) {
183
+ return { success: false, output: "", error: `Operation ${i + 1}: ${op.action} requires content` };
184
+ }
185
+
186
+ try {
187
+ const result = this.applyOperation(content, filePath, op);
188
+ content = result.content;
189
+ results.push(`${i + 1}. ${op.action} ${op.node_type} "${op.name}" — ${result.message}`);
190
+ } catch (err) {
191
+ return { success: false, output: results.join("\n"), error: `Operation ${i + 1} failed: ${(err as Error).message}` };
192
+ }
193
+ }
194
+
195
+ // Write result
196
+ fs.writeFileSync(filePath, content, "utf-8");
197
+
198
+ // Generate diff summary
199
+ const oldLines = originalContent.split("\n");
200
+ const newLines = content.split("\n");
201
+ const diffHeader = `--- a/${input.file_path}\n+++ b/${input.file_path}\n@@ -1,${oldLines.length} +1,${newLines.length} @@`;
202
+
203
+ return {
204
+ success: true,
205
+ output: `AST-edited ${filePath}: ${operations.length} operation(s)\n\n${results.join("\n")}\n\n${diffHeader}`,
206
+ };
207
+ }
208
+
209
+ /**
210
+ * Initialize tree-sitter parser for the given file's language.
211
+ */
212
+ private async initParser(filePath: string): Promise<ToolResult> {
213
+ const ext = filePath.substring(filePath.lastIndexOf(".")).toLowerCase();
214
+ const langPackage = LANGUAGE_MAP[ext];
215
+
216
+ if (!langPackage) {
217
+ return {
218
+ success: false,
219
+ output: "",
220
+ error: `Unsupported file type: ${ext}. Supported: ${Object.keys(LANGUAGE_MAP).join(", ")}`,
221
+ };
222
+ }
223
+
224
+ try {
225
+ if (!Parser) {
226
+ Parser = require("tree-sitter");
227
+ }
228
+
229
+ if (!languageCache.has(langPackage)) {
230
+ const lang = require(langPackage);
231
+ languageCache.set(langPackage, lang);
232
+ }
233
+
234
+ return { success: true, output: "" };
235
+ } catch {
236
+ return {
237
+ success: false,
238
+ output: "",
239
+ error: `Tree-sitter not available. Install with: npm install tree-sitter ${langPackage}`,
240
+ };
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Parse source code and apply a single AST operation.
246
+ */
247
+ private applyOperation(
248
+ content: string,
249
+ filePath: string,
250
+ op: ASTEditOperation,
251
+ ): { content: string; message: string } {
252
+ const ext = filePath.substring(filePath.lastIndexOf(".")).toLowerCase();
253
+ const langPackage = LANGUAGE_MAP[ext]!;
254
+ const language = languageCache.get(langPackage);
255
+
256
+ const parser = new Parser();
257
+ parser.setLanguage(language);
258
+ const tree = parser.parse(content);
259
+
260
+ // Find the target node
261
+ const node = this.findNode(tree.rootNode, op.node_type, op.name);
262
+ if (!node) {
263
+ throw new Error(`Could not find ${op.node_type} named "${op.name}" in AST`);
264
+ }
265
+
266
+ const startIdx = node.startIndex;
267
+ const endIdx = node.endIndex;
268
+
269
+ switch (op.action) {
270
+ case "replace":
271
+ return {
272
+ content: content.substring(0, startIdx) + op.content! + content.substring(endIdx),
273
+ message: `replaced (${node.startPosition.row + 1}:${node.startPosition.column}-${node.endPosition.row + 1}:${node.endPosition.column})`,
274
+ };
275
+
276
+ case "delete":
277
+ // Delete the node and any trailing newline
278
+ let deleteEnd = endIdx;
279
+ if (content[deleteEnd] === "\n") deleteEnd++;
280
+ return {
281
+ content: content.substring(0, startIdx) + content.substring(deleteEnd),
282
+ message: `deleted (${node.startPosition.row + 1}:${node.startPosition.column}-${node.endPosition.row + 1}:${node.endPosition.column})`,
283
+ };
284
+
285
+ case "rename":
286
+ return this.renameNode(content, node, op.name, op.new_name!);
287
+
288
+ case "insert_before":
289
+ return {
290
+ content: content.substring(0, startIdx) + op.content! + "\n" + content.substring(startIdx),
291
+ message: `inserted before (line ${node.startPosition.row + 1})`,
292
+ };
293
+
294
+ case "insert_after":
295
+ return {
296
+ content: content.substring(0, endIdx) + "\n" + op.content! + content.substring(endIdx),
297
+ message: `inserted after (line ${node.endPosition.row + 1})`,
298
+ };
299
+
300
+ case "replace_body": {
301
+ const body = this.findBody(node);
302
+ if (!body) {
303
+ throw new Error(`No body found for ${op.node_type} "${op.name}"`);
304
+ }
305
+ return {
306
+ content: content.substring(0, body.startIndex) + op.content! + content.substring(body.endIndex),
307
+ message: `body replaced (${body.startPosition.row + 1}:${body.startPosition.column}-${body.endPosition.row + 1}:${body.endPosition.column})`,
308
+ };
309
+ }
310
+
311
+ default:
312
+ throw new Error(`Unknown action: ${op.action}`);
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Find an AST node by type and name.
318
+ */
319
+ private findNode(rootNode: any, nodeType: ASTNodeType, name: string): any {
320
+ const matches: any[] = [];
321
+ this.walkTree(rootNode, (node: any) => {
322
+ if (this.matchesNodeType(node, nodeType) && this.getNodeName(node) === name) {
323
+ matches.push(node);
324
+ }
325
+ });
326
+ return matches[0] || null;
327
+ }
328
+
329
+ /**
330
+ * Walk the AST tree depth-first, calling visitor on each node.
331
+ */
332
+ private walkTree(node: any, visitor: (node: any) => void): void {
333
+ visitor(node);
334
+ for (let i = 0; i < node.childCount; i++) {
335
+ this.walkTree(node.child(i), visitor);
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Check if an AST node matches the requested type category.
341
+ */
342
+ private matchesNodeType(node: any, nodeType: ASTNodeType): boolean {
343
+ const type = node.type as string;
344
+
345
+ switch (nodeType) {
346
+ case "function":
347
+ return [
348
+ "function_declaration", "function_definition", "function_item",
349
+ "arrow_function", "method_definition", "function_expression",
350
+ "export_statement", // may wrap a function
351
+ ].includes(type);
352
+
353
+ case "class":
354
+ return [
355
+ "class_declaration", "class_definition", "class_statement",
356
+ "struct_item", "impl_item",
357
+ ].includes(type);
358
+
359
+ case "method":
360
+ return [
361
+ "method_definition", "method_declaration", "function_item",
362
+ "public_method_definition",
363
+ ].includes(type);
364
+
365
+ case "import":
366
+ return [
367
+ "import_statement", "import_declaration", "use_declaration",
368
+ "import_from_statement", "include_statement",
369
+ ].includes(type);
370
+
371
+ case "variable":
372
+ return [
373
+ "variable_declaration", "lexical_declaration", "const_declaration",
374
+ "let_declaration", "variable_declarator", "assignment_expression",
375
+ "static_item", "const_item",
376
+ ].includes(type);
377
+
378
+ case "type":
379
+ return [
380
+ "type_alias_declaration", "type_definition", "typedef_declaration",
381
+ ].includes(type);
382
+
383
+ case "interface":
384
+ return [
385
+ "interface_declaration", "interface_definition",
386
+ ].includes(type);
387
+
388
+ case "enum":
389
+ return [
390
+ "enum_declaration", "enum_definition", "enum_item",
391
+ ].includes(type);
392
+
393
+ case "block":
394
+ return [
395
+ "statement_block", "block", "compound_statement",
396
+ "block_expression",
397
+ ].includes(type);
398
+
399
+ case "expression":
400
+ return [
401
+ "expression_statement", "call_expression", "assignment_expression",
402
+ ].includes(type);
403
+
404
+ default:
405
+ return false;
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Extract the name identifier from an AST node.
411
+ */
412
+ private getNodeName(node: any): string | null {
413
+ // Direct name child
414
+ const nameNode = node.childForFieldName?.("name");
415
+ if (nameNode) return nameNode.text;
416
+
417
+ // Check for declarator (variable declarations)
418
+ const declarator = node.childForFieldName?.("declarator");
419
+ if (declarator) {
420
+ const declName = declarator.childForFieldName?.("name");
421
+ if (declName) return declName.text;
422
+ return declarator.text;
423
+ }
424
+
425
+ // For export statements, check the declaration inside
426
+ if (node.type === "export_statement") {
427
+ const decl = node.childForFieldName?.("declaration");
428
+ if (decl) return this.getNodeName(decl);
429
+ }
430
+
431
+ // For imports, try to get the module/source name
432
+ if (node.type?.includes("import")) {
433
+ const source = node.childForFieldName?.("source") || node.childForFieldName?.("module_name");
434
+ if (source) return source.text?.replace(/['"]/g, "");
435
+ }
436
+
437
+ // For lexical_declaration, check first declarator child
438
+ for (let i = 0; i < node.childCount; i++) {
439
+ const child = node.child(i);
440
+ if (child.type === "variable_declarator") {
441
+ const childName = child.childForFieldName?.("name");
442
+ if (childName) return childName.text;
443
+ }
444
+ }
445
+
446
+ return null;
447
+ }
448
+
449
+ /**
450
+ * Find the body/block node within a function, method, or class.
451
+ */
452
+ private findBody(node: any): any {
453
+ // Common body field names
454
+ const bodyNode = node.childForFieldName?.("body") || node.childForFieldName?.("block");
455
+ if (bodyNode) return bodyNode;
456
+
457
+ // Fallback: find first block-type child
458
+ for (let i = 0; i < node.childCount; i++) {
459
+ const child = node.child(i);
460
+ if (["statement_block", "block", "compound_statement", "class_body"].includes(child.type)) {
461
+ return child;
462
+ }
463
+ }
464
+
465
+ return null;
466
+ }
467
+
468
+ /**
469
+ * Rename a node by replacing all occurrences of the old name with the new name
470
+ * within the node's text range.
471
+ */
472
+ private renameNode(
473
+ content: string,
474
+ node: any,
475
+ oldName: string,
476
+ newName: string,
477
+ ): { content: string; message: string } {
478
+ const nodeText = content.substring(node.startIndex, node.endIndex);
479
+ // Replace identifier occurrences (word-boundary aware)
480
+ const pattern = new RegExp(`\\b${escapeRegex(oldName)}\\b`, "g");
481
+ const updatedText = nodeText.replace(pattern, newName);
482
+ const count = (nodeText.match(pattern) || []).length;
483
+
484
+ return {
485
+ content: content.substring(0, node.startIndex) + updatedText + content.substring(node.endIndex),
486
+ message: `renamed ${count} occurrence(s) of "${oldName}" → "${newName}"`,
487
+ };
488
+ }
489
+ }
490
+
491
+ function escapeRegex(str: string): string {
492
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
493
+ }
@@ -0,0 +1,143 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { exec } from "child_process";
4
+ import type { BaseTool, ToolDefinition, ToolResult } from "./types";
5
+ import { safePath } from "../utils/path-safety";
6
+
7
+ /** Map file extensions to the command that checks their syntax/types */
8
+ const CHECKERS: Record<string, (filePath: string, cwd: string) => string> = {
9
+ // JavaScript — Node's --check flag validates syntax without running
10
+ ".js": (f) => `node --check "${f}"`,
11
+ ".mjs": (f) => `node --check "${f}"`,
12
+ ".cjs": (f) => `node --check "${f}"`,
13
+
14
+ // TypeScript — tsc with --noEmit to type-check without producing output
15
+ ".ts": (f, cwd) => {
16
+ const tsconfig = path.join(cwd, "tsconfig.json");
17
+ if (fs.existsSync(tsconfig)) {
18
+ return `npx tsc --noEmit --pretty "${f}"`;
19
+ }
20
+ return `npx tsc --noEmit --pretty --esModuleInterop --resolveJsonModule "${f}"`;
21
+ },
22
+ ".tsx": (f, cwd) => {
23
+ const tsconfig = path.join(cwd, "tsconfig.json");
24
+ if (fs.existsSync(tsconfig)) {
25
+ return `npx tsc --noEmit --pretty --jsx react-jsx "${f}"`;
26
+ }
27
+ return `npx tsc --noEmit --pretty --esModuleInterop --resolveJsonModule --jsx react-jsx "${f}"`;
28
+ },
29
+
30
+ // Python — py_compile checks syntax
31
+ ".py": (f) => `python3 -m py_compile "${f}"`,
32
+
33
+ // Ruby — -c flag checks syntax
34
+ ".rb": (f) => `ruby -c "${f}"`,
35
+
36
+ // Shell — bash -n checks syntax without executing
37
+ ".sh": (f) => `bash -n "${f}"`,
38
+ ".bash": (f) => `bash -n "${f}"`,
39
+ ".zsh": (f) => `zsh -n "${f}"`,
40
+
41
+ // Go — vet checks for common errors
42
+ ".go": (f) => `go vet "${f}"`,
43
+
44
+ // PHP — lint mode
45
+ ".php": (f) => `php -l "${f}"`,
46
+
47
+ // Perl — check syntax
48
+ ".pl": (f) => `perl -c "${f}"`,
49
+
50
+ // Swift — just parse/typecheck
51
+ ".swift": (f) => `swiftc -typecheck "${f}"`,
52
+
53
+ // JSON — validate structure
54
+ ".json": (f) => `node -e "JSON.parse(require('fs').readFileSync('${f.replace(/'/g, "\\'")}','utf8'));console.log('Valid JSON')"`,
55
+ };
56
+
57
+ export class CodeVerifyTool implements BaseTool {
58
+ definition: ToolDefinition = {
59
+ name: "code_verify",
60
+ description:
61
+ "Verify that code in a file is syntactically correct and (where possible) type-safe. " +
62
+ "Runs language-specific checks: node --check for JS, tsc --noEmit for TS, py_compile for Python, etc. " +
63
+ "Use after writing or editing code to catch errors before running.",
64
+ inputSchema: {
65
+ type: "object",
66
+ properties: {
67
+ file_path: {
68
+ type: "string",
69
+ description: "Path to the file to verify",
70
+ },
71
+ timeout: {
72
+ type: "number",
73
+ description: "Timeout in ms. Default: 30000",
74
+ },
75
+ },
76
+ required: ["file_path"],
77
+ },
78
+ requiresPermission: false,
79
+ permissionMessage: (input) => `Verify code: ${input.file_path}`,
80
+ };
81
+
82
+ private workingDir: string;
83
+ constructor(workingDir: string) {
84
+ this.workingDir = workingDir;
85
+ }
86
+
87
+ async execute(input: Record<string, unknown>): Promise<ToolResult> {
88
+ let filePath: string;
89
+ try {
90
+ filePath = safePath(input.file_path as string, this.workingDir);
91
+ } catch (err) {
92
+ return { success: false, output: "", error: (err as Error).message };
93
+ }
94
+
95
+ const timeout = (input.timeout as number) || 30000;
96
+
97
+ if (!fs.existsSync(filePath)) {
98
+ return { success: false, output: "", error: `File not found: ${filePath}` };
99
+ }
100
+
101
+ const ext = path.extname(filePath).toLowerCase();
102
+ const checkerFn = CHECKERS[ext];
103
+ if (!checkerFn) {
104
+ return {
105
+ success: false,
106
+ output: "",
107
+ error: `No verifier for ${ext} files. Supported: ${Object.keys(CHECKERS).join(", ")}`,
108
+ };
109
+ }
110
+
111
+ const command = checkerFn(filePath, this.workingDir);
112
+
113
+ return new Promise((resolve) => {
114
+ exec(
115
+ command,
116
+ { cwd: this.workingDir, timeout, maxBuffer: 10 * 1024 * 1024, env: { ...process.env } },
117
+ (error, stdout, stderr) => {
118
+ const outputParts: string[] = [];
119
+ if (stdout) outputParts.push(stdout.trimEnd());
120
+ if (stderr) outputParts.push(stderr.trimEnd());
121
+ const output = outputParts.join("\n") || "(no output)";
122
+
123
+ if (error?.killed) {
124
+ return resolve({ success: false, output, error: `Timed out after ${timeout}ms` });
125
+ }
126
+
127
+ if (error) {
128
+ resolve({
129
+ success: false,
130
+ output,
131
+ error: `Verification failed (exit code ${error.code}). See output for details.`,
132
+ });
133
+ } else {
134
+ resolve({
135
+ success: true,
136
+ output: output === "(no output)" ? "Code verification passed — no errors found." : output,
137
+ });
138
+ }
139
+ },
140
+ );
141
+ });
142
+ }
143
+ }