@kolisachint/hoocode-agent 0.4.162 → 0.4.163

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 (293) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/cli/args.d.ts.map +1 -1
  3. package/dist/cli/args.js +0 -9
  4. package/dist/cli/args.js.map +1 -1
  5. package/dist/config.d.ts +0 -40
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +4 -50
  8. package/dist/config.js.map +1 -1
  9. package/dist/core/agent-frontmatter.d.ts +0 -28
  10. package/dist/core/agent-frontmatter.d.ts.map +1 -1
  11. package/dist/core/agent-frontmatter.js.map +1 -1
  12. package/dist/core/auth-guidance.d.ts +0 -1
  13. package/dist/core/auth-guidance.d.ts.map +1 -1
  14. package/dist/core/auth-guidance.js +1 -1
  15. package/dist/core/auth-guidance.js.map +1 -1
  16. package/dist/core/embsearch/chunker.d.ts +0 -2
  17. package/dist/core/embsearch/chunker.d.ts.map +1 -1
  18. package/dist/core/embsearch/chunker.js +1 -1
  19. package/dist/core/embsearch/chunker.js.map +1 -1
  20. package/dist/core/embsearch/index-meta.d.ts +0 -2
  21. package/dist/core/embsearch/index-meta.d.ts.map +1 -1
  22. package/dist/core/embsearch/index-meta.js +0 -6
  23. package/dist/core/embsearch/index-meta.js.map +1 -1
  24. package/dist/core/export-html/ansi-to-html.d.ts +0 -4
  25. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -1
  26. package/dist/core/export-html/ansi-to-html.js +1 -1
  27. package/dist/core/export-html/ansi-to-html.js.map +1 -1
  28. package/dist/core/extensions/index.d.ts +3 -3
  29. package/dist/core/extensions/index.d.ts.map +1 -1
  30. package/dist/core/extensions/index.js +1 -1
  31. package/dist/core/extensions/index.js.map +1 -1
  32. package/dist/core/extensions/plugins/authoring.d.ts +2 -2
  33. package/dist/core/extensions/plugins/authoring.d.ts.map +1 -1
  34. package/dist/core/extensions/plugins/authoring.js +0 -0
  35. package/dist/core/extensions/plugins/authoring.js.map +1 -1
  36. package/dist/core/extensions/plugins/formats/index.d.ts +2 -6
  37. package/dist/core/extensions/plugins/formats/index.d.ts.map +1 -1
  38. package/dist/core/extensions/plugins/formats/index.js +2 -3
  39. package/dist/core/extensions/plugins/formats/index.js.map +1 -1
  40. package/dist/core/extensions/plugins/formats/shared.d.ts +0 -2
  41. package/dist/core/extensions/plugins/formats/shared.d.ts.map +1 -1
  42. package/dist/core/extensions/plugins/formats/shared.js +1 -1
  43. package/dist/core/extensions/plugins/formats/shared.js.map +1 -1
  44. package/dist/core/extensions/plugins/formats/types.d.ts +4 -3
  45. package/dist/core/extensions/plugins/formats/types.d.ts.map +1 -1
  46. package/dist/core/extensions/plugins/formats/types.js.map +1 -1
  47. package/dist/core/extensions/plugins/install.d.ts +0 -2
  48. package/dist/core/extensions/plugins/install.d.ts.map +1 -1
  49. package/dist/core/extensions/plugins/install.js +1 -1
  50. package/dist/core/extensions/plugins/install.js.map +1 -1
  51. package/dist/core/extensions/plugins/manifest.d.ts +0 -6
  52. package/dist/core/extensions/plugins/manifest.d.ts.map +1 -1
  53. package/dist/core/extensions/plugins/manifest.js +0 -6
  54. package/dist/core/extensions/plugins/manifest.js.map +1 -1
  55. package/dist/core/extensions/plugins/marketplace.d.ts +3 -3
  56. package/dist/core/extensions/plugins/marketplace.d.ts.map +1 -1
  57. package/dist/core/extensions/plugins/marketplace.js.map +1 -1
  58. package/dist/core/extensions/types.d.ts +28 -28
  59. package/dist/core/extensions/types.d.ts.map +1 -1
  60. package/dist/core/extensions/types.js.map +1 -1
  61. package/dist/core/keybindings.d.ts +3 -3
  62. package/dist/core/keybindings.d.ts.map +1 -1
  63. package/dist/core/keybindings.js.map +1 -1
  64. package/dist/core/model-resolver.d.ts +0 -7
  65. package/dist/core/model-resolver.d.ts.map +1 -1
  66. package/dist/core/model-resolver.js +0 -56
  67. package/dist/core/model-resolver.js.map +1 -1
  68. package/dist/core/package-resource-discovery.d.ts +0 -10
  69. package/dist/core/package-resource-discovery.d.ts.map +1 -1
  70. package/dist/core/package-resource-discovery.js +7 -7
  71. package/dist/core/package-resource-discovery.js.map +1 -1
  72. package/dist/core/provider-health.d.ts +0 -2
  73. package/dist/core/provider-health.d.ts.map +1 -1
  74. package/dist/core/provider-health.js +1 -1
  75. package/dist/core/provider-health.js.map +1 -1
  76. package/dist/core/resolve-config-value.d.ts +0 -4
  77. package/dist/core/resolve-config-value.d.ts.map +1 -1
  78. package/dist/core/resolve-config-value.js +0 -15
  79. package/dist/core/resolve-config-value.js.map +1 -1
  80. package/dist/core/sdk.d.ts +3 -7
  81. package/dist/core/sdk.d.ts.map +1 -1
  82. package/dist/core/sdk.js +3 -3
  83. package/dist/core/sdk.js.map +1 -1
  84. package/dist/core/search/eval.d.ts +0 -3
  85. package/dist/core/search/eval.d.ts.map +1 -1
  86. package/dist/core/search/eval.js +1 -1
  87. package/dist/core/search/eval.js.map +1 -1
  88. package/dist/core/search/types.d.ts +2 -1
  89. package/dist/core/search/types.d.ts.map +1 -1
  90. package/dist/core/search/types.js.map +1 -1
  91. package/dist/core/session-cwd.d.ts +0 -1
  92. package/dist/core/session-cwd.d.ts.map +1 -1
  93. package/dist/core/session-cwd.js +1 -1
  94. package/dist/core/session-cwd.js.map +1 -1
  95. package/dist/core/settings-manager.d.ts +2 -3
  96. package/dist/core/settings-manager.d.ts.map +1 -1
  97. package/dist/core/settings-manager.js +0 -1
  98. package/dist/core/settings-manager.js.map +1 -1
  99. package/dist/core/settings-types.d.ts +9 -8
  100. package/dist/core/settings-types.d.ts.map +1 -1
  101. package/dist/core/settings-types.js.map +1 -1
  102. package/dist/core/subagent-inbox.d.ts +1 -3
  103. package/dist/core/subagent-inbox.d.ts.map +1 -1
  104. package/dist/core/subagent-inbox.js +1 -1
  105. package/dist/core/subagent-inbox.js.map +1 -1
  106. package/dist/core/subagent-pool.d.ts +0 -13
  107. package/dist/core/subagent-pool.d.ts.map +1 -1
  108. package/dist/core/subagent-pool.js +2 -2
  109. package/dist/core/subagent-pool.js.map +1 -1
  110. package/dist/core/task-store.d.ts +3 -3
  111. package/dist/core/task-store.d.ts.map +1 -1
  112. package/dist/core/task-store.js.map +1 -1
  113. package/dist/core/team-auto.d.ts +0 -2
  114. package/dist/core/team-auto.d.ts.map +1 -1
  115. package/dist/core/team-auto.js +1 -1
  116. package/dist/core/team-auto.js.map +1 -1
  117. package/dist/core/token-budget.d.ts +0 -13
  118. package/dist/core/token-budget.d.ts.map +1 -1
  119. package/dist/core/token-budget.js.map +1 -1
  120. package/dist/core/tools/browser/browser-continue.d.ts +1 -2
  121. package/dist/core/tools/browser/browser-continue.d.ts.map +1 -1
  122. package/dist/core/tools/browser/browser-continue.js.map +1 -1
  123. package/dist/core/tools/browser/browser-run.d.ts +1 -2
  124. package/dist/core/tools/browser/browser-run.d.ts.map +1 -1
  125. package/dist/core/tools/browser/browser-run.js.map +1 -1
  126. package/dist/core/tools/browser/browsertools-shared.d.ts +1 -14
  127. package/dist/core/tools/browser/browsertools-shared.d.ts.map +1 -1
  128. package/dist/core/tools/browser/browsertools-shared.js +4 -4
  129. package/dist/core/tools/browser/browsertools-shared.js.map +1 -1
  130. package/dist/core/tools/browser/index.d.ts +2 -2
  131. package/dist/core/tools/browser/index.d.ts.map +1 -1
  132. package/dist/core/tools/browser/index.js +2 -2
  133. package/dist/core/tools/browser/index.js.map +1 -1
  134. package/dist/core/tools/doc/docedit.d.ts +1 -2
  135. package/dist/core/tools/doc/docedit.d.ts.map +1 -1
  136. package/dist/core/tools/doc/docedit.js.map +1 -1
  137. package/dist/core/tools/doc/docgrep.d.ts +1 -2
  138. package/dist/core/tools/doc/docgrep.d.ts.map +1 -1
  139. package/dist/core/tools/doc/docgrep.js.map +1 -1
  140. package/dist/core/tools/doc/docpeek.d.ts +1 -2
  141. package/dist/core/tools/doc/docpeek.d.ts.map +1 -1
  142. package/dist/core/tools/doc/docpeek.js.map +1 -1
  143. package/dist/core/tools/doc/docread.d.ts +1 -2
  144. package/dist/core/tools/doc/docread.d.ts.map +1 -1
  145. package/dist/core/tools/doc/docread.js.map +1 -1
  146. package/dist/core/tools/doc/docscan.d.ts +1 -2
  147. package/dist/core/tools/doc/docscan.d.ts.map +1 -1
  148. package/dist/core/tools/doc/docscan.js.map +1 -1
  149. package/dist/core/tools/doc/docwrite.d.ts +1 -2
  150. package/dist/core/tools/doc/docwrite.d.ts.map +1 -1
  151. package/dist/core/tools/doc/docwrite.js.map +1 -1
  152. package/dist/core/tools/doc/index.d.ts +6 -6
  153. package/dist/core/tools/doc/index.d.ts.map +1 -1
  154. package/dist/core/tools/doc/index.js +6 -6
  155. package/dist/core/tools/doc/index.js.map +1 -1
  156. package/dist/core/tools/edit-diff.d.ts +0 -38
  157. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  158. package/dist/core/tools/edit-diff.js +2 -9
  159. package/dist/core/tools/edit-diff.js.map +1 -1
  160. package/dist/core/tools/filetools-shared.d.ts +7 -10
  161. package/dist/core/tools/filetools-shared.d.ts.map +1 -1
  162. package/dist/core/tools/filetools-shared.js +2 -2
  163. package/dist/core/tools/filetools-shared.js.map +1 -1
  164. package/dist/core/tools/index.d.ts +0 -4
  165. package/dist/core/tools/index.d.ts.map +1 -1
  166. package/dist/core/tools/index.js +0 -7
  167. package/dist/core/tools/index.js.map +1 -1
  168. package/dist/core/tools/native-search.d.ts +3 -2
  169. package/dist/core/tools/native-search.d.ts.map +1 -1
  170. package/dist/core/tools/native-search.js.map +1 -1
  171. package/dist/core/tools/path-utils.d.ts +0 -10
  172. package/dist/core/tools/path-utils.d.ts.map +1 -1
  173. package/dist/core/tools/path-utils.js +2 -2
  174. package/dist/core/tools/path-utils.js.map +1 -1
  175. package/dist/core/tools/plugins.d.ts +1 -20
  176. package/dist/core/tools/plugins.d.ts.map +1 -1
  177. package/dist/core/tools/plugins.js +2 -2
  178. package/dist/core/tools/plugins.js.map +1 -1
  179. package/dist/core/tools/propose-plugin.d.ts +0 -12
  180. package/dist/core/tools/propose-plugin.d.ts.map +1 -1
  181. package/dist/core/tools/propose-plugin.js +0 -1
  182. package/dist/core/tools/propose-plugin.js.map +1 -1
  183. package/dist/core/tools/read-dedup.d.ts +0 -2
  184. package/dist/core/tools/read-dedup.d.ts.map +1 -1
  185. package/dist/core/tools/read-dedup.js +1 -1
  186. package/dist/core/tools/read-dedup.js.map +1 -1
  187. package/dist/core/tools/render-utils.d.ts +0 -5
  188. package/dist/core/tools/render-utils.d.ts.map +1 -1
  189. package/dist/core/tools/render-utils.js.map +1 -1
  190. package/dist/core/tools/search.d.ts +1 -4
  191. package/dist/core/tools/search.d.ts.map +1 -1
  192. package/dist/core/tools/search.js +0 -4
  193. package/dist/core/tools/search.js.map +1 -1
  194. package/dist/core/tools/webfetch.d.ts +1 -2
  195. package/dist/core/tools/webfetch.d.ts.map +1 -1
  196. package/dist/core/tools/webfetch.js.map +1 -1
  197. package/dist/core/tools/websearch.d.ts +1 -2
  198. package/dist/core/tools/websearch.d.ts.map +1 -1
  199. package/dist/core/tools/websearch.js.map +1 -1
  200. package/dist/core/tools/webtools-shared.d.ts +3 -5
  201. package/dist/core/tools/webtools-shared.d.ts.map +1 -1
  202. package/dist/core/tools/webtools-shared.js +1 -1
  203. package/dist/core/tools/webtools-shared.js.map +1 -1
  204. package/dist/core/warm-subagent-pool-instance.d.ts +0 -4
  205. package/dist/core/warm-subagent-pool-instance.d.ts.map +1 -1
  206. package/dist/core/warm-subagent-pool-instance.js +0 -10
  207. package/dist/core/warm-subagent-pool-instance.js.map +1 -1
  208. package/dist/core/wordmark.d.ts +1 -9
  209. package/dist/core/wordmark.d.ts.map +1 -1
  210. package/dist/core/wordmark.js +2 -26
  211. package/dist/core/wordmark.js.map +1 -1
  212. package/dist/extensions/core/config.d.ts +4 -3
  213. package/dist/extensions/core/config.d.ts.map +1 -1
  214. package/dist/extensions/core/config.js.map +1 -1
  215. package/dist/extensions/core/mcp-loader.d.ts +0 -18
  216. package/dist/extensions/core/mcp-loader.d.ts.map +1 -1
  217. package/dist/extensions/core/mcp-loader.js.map +1 -1
  218. package/dist/extensions/core/prompt-reactive/policy.d.ts +0 -8
  219. package/dist/extensions/core/prompt-reactive/policy.d.ts.map +1 -1
  220. package/dist/extensions/core/prompt-reactive/policy.js +1 -1
  221. package/dist/extensions/core/prompt-reactive/policy.js.map +1 -1
  222. package/dist/migrations.d.ts +0 -16
  223. package/dist/migrations.d.ts.map +1 -1
  224. package/dist/migrations.js +2 -2
  225. package/dist/migrations.js.map +1 -1
  226. package/dist/modes/interactive/brand.d.ts +0 -4
  227. package/dist/modes/interactive/brand.d.ts.map +1 -1
  228. package/dist/modes/interactive/brand.js +0 -4
  229. package/dist/modes/interactive/brand.js.map +1 -1
  230. package/dist/modes/interactive/components/index.d.ts +0 -4
  231. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  232. package/dist/modes/interactive/components/index.js +0 -5
  233. package/dist/modes/interactive/components/index.js.map +1 -1
  234. package/dist/modes/interactive/components/read-output.d.ts +0 -9
  235. package/dist/modes/interactive/components/read-output.d.ts.map +1 -1
  236. package/dist/modes/interactive/components/read-output.js +0 -20
  237. package/dist/modes/interactive/components/read-output.js.map +1 -1
  238. package/dist/modes/interactive/components/session-selector-search.d.ts +0 -17
  239. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
  240. package/dist/modes/interactive/components/session-selector-search.js +2 -2
  241. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  242. package/dist/modes/interactive/components/settings-selector.d.ts +4 -13
  243. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  244. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  245. package/dist/modes/interactive/login-controller.d.ts +1 -1
  246. package/dist/modes/interactive/login-controller.d.ts.map +1 -1
  247. package/dist/modes/interactive/login-controller.js +1 -1
  248. package/dist/modes/interactive/login-controller.js.map +1 -1
  249. package/dist/modes/interactive/resource-display.d.ts +0 -20
  250. package/dist/modes/interactive/resource-display.d.ts.map +1 -1
  251. package/dist/modes/interactive/resource-display.js +7 -7
  252. package/dist/modes/interactive/resource-display.js.map +1 -1
  253. package/dist/modes/interactive/theme/theme.d.ts +0 -4
  254. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  255. package/dist/modes/interactive/theme/theme.js +0 -7
  256. package/dist/modes/interactive/theme/theme.js.map +1 -1
  257. package/dist/modes/rpc/rpc-mode.d.ts +0 -1
  258. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  259. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  260. package/dist/modes/rpc/rpc-types.d.ts +0 -1
  261. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  262. package/dist/modes/rpc/rpc-types.js +3 -0
  263. package/dist/modes/rpc/rpc-types.js.map +1 -1
  264. package/dist/package-manager-cli.d.ts +0 -1
  265. package/dist/package-manager-cli.d.ts.map +1 -1
  266. package/dist/package-manager-cli.js.map +1 -1
  267. package/dist/utils/changelog.d.ts +0 -4
  268. package/dist/utils/changelog.d.ts.map +1 -1
  269. package/dist/utils/changelog.js +1 -1
  270. package/dist/utils/changelog.js.map +1 -1
  271. package/dist/utils/clipboard-native.d.ts +1 -1
  272. package/dist/utils/clipboard-native.d.ts.map +1 -1
  273. package/dist/utils/clipboard-native.js.map +1 -1
  274. package/dist/utils/paths.d.ts +0 -2
  275. package/dist/utils/paths.d.ts.map +1 -1
  276. package/dist/utils/paths.js +1 -1
  277. package/dist/utils/paths.js.map +1 -1
  278. package/dist/utils/plan/parser.d.ts +5 -4
  279. package/dist/utils/plan/parser.d.ts.map +1 -1
  280. package/dist/utils/plan/parser.js.map +1 -1
  281. package/dist/utils/tools-manager.d.ts +0 -1
  282. package/dist/utils/tools-manager.d.ts.map +1 -1
  283. package/dist/utils/tools-manager.js +1 -1
  284. package/dist/utils/tools-manager.js.map +1 -1
  285. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  286. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  287. package/examples/extensions/sandbox/package.json +1 -1
  288. package/examples/extensions/with-deps/package.json +1 -1
  289. package/package.json +4 -7
  290. package/dist/core/index.d.ts +0 -12
  291. package/dist/core/index.d.ts.map +0 -1
  292. package/dist/core/index.js +0 -12
  293. package/dist/core/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"docscan.js","sourceRoot":"","sources":["../../../../src/core/tools/doc/docscan.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,2DAA2D,CAAC;AACpF,OAAO,EAAE,KAAK,IAAI,QAAQ,EAAE,MAAM,2CAA2C,CAAC;AAE9E,OAAO,EAAiB,YAAY,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAClG,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAEnE,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,CAAC;IACjC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC;QACjB,WAAW,EAAE,mGAAmG;KAChH,CAAC;IACF,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,kDAAkD,EAAE,CAAC,CAAC;IACvG,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,6DAA6D,EAAE,CAAC,CAAC;CACjH,CAAC,CAAC;AAeH;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAc,EAAU;IACtD,MAAM,MAAM,GACX,YAAY,IAAI,CAAC,SAAS,QAAM,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,KAAK,UAAU;QACrE,WAAW,IAAI,CAAC,MAAM,MAAM,IAAI,CAAC,YAAY,aAAa,CAAC;IAC5D,MAAM,KAAK,GAAa,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACrC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3F,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5E,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,UAAU,IAAI,OAAO,KAAK,KAAK,CAAC,cAAc,MAAM,OAAO,EAAE,CAAC,CAAC;IAClG,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,2BAA2B,CAAC,KAAK,CAAC,CAAC;IAClE,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC;IACzC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpC,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,CAAC;IACrB,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CACT,iBAAiB,YAAY,mBAAmB,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,6BAA6B;YACzG,6DAA6D,CAC9D,CAAC;IACH,CAAC;IACD,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QACnB,KAAK,CAAC,IAAI,CACT,MAAM,SAAS,cAAc,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,qCAAmC,IAAI,gBAAgB,CAC9G,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACxB;AAED,SAAS,iBAAiB,CAAC,IAAoE,EAAU;IACxG,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7B,MAAM,WAAW,GAChB,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IACxG,IAAI,IAAI,GAAG,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACpG,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,IAAI,EAAE,MAAM,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACpE,IAAI,IAAI,EAAE,KAAK,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACjE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,IAAI,IAAI,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7E,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,SAAS,mBAAmB,CAC3B,MAA2D,EAC3D,OAAgC,EAChC,UAAmB,EACV;IACT,MAAM,MAAM,GAAG,aAAa,CAAC,MAAa,EAAE,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/D,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC9C,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC;IAC1C,IAAI,IAAI,GAAG,KAAK,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IACzF,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QACnB,IAAI,IAAI,GAAG,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,SAAS,cAAc,CAAC,IAAI,OAAO,CAAC,kBAAkB,EAAE,WAAW,CAAC,GAAG,CAAC;IACnH,CAAC;IACD,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,MAAM,UAAU,2BAA2B,CAC1C,GAAW,EACX,OAA4B,EAC2C;IACvE,OAAO;QACN,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,SAAS;QAChB,WAAW,EACV,2fAAyf;QAC1f,aAAa,EAAE,6EAA6E;QAC5F,gBAAgB,EAAE;YACjB,2NAA2N;YAC3N,wOAAoO;SACpO;QACD,UAAU,EAAE,aAAa;QACzB,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAoB,EAAE,MAAoB,EAAE;YAC3F,IAAI,MAAM,EAAE,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;YAC1D,MAAM,YAAY,GAAG,eAAe,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAChD,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE;gBAC1D,MAAM;gBACN,KAAK;gBACL,WAAW,EAAE,OAAO,EAAE,WAAW;aACjC,CAAC,CAAC;YACH,OAAO;gBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChE,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE;aACjF,CAAC;QAAA,CACF;QACD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;YACjC,MAAM,IAAI,GAAI,OAAO,CAAC,aAAkC,IAAI,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/E,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC;YACtC,OAAO,IAAI,CAAC;QAAA,CACZ;QACD,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE;YAC9C,MAAM,IAAI,GAAI,OAAO,CAAC,aAAkC,IAAI,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/E,IAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,MAAa,EAAE,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;YAC9E,OAAO,IAAI,CAAC;QAAA,CACZ;KACD,CAAC;AAAA,CACF;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,OAA4B,EAAmC;IAC7G,OAAO,kBAAkB,CAAC,2BAA2B,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;AAAA,CACrE","sourcesContent":["import type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../../modes/interactive/components/keybinding-hints.js\";\nimport { theme as appTheme } from \"../../../modes/interactive/theme/theme.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../../extensions/types.js\";\nimport { type ScanView, scanDocument, truncateRenderToTokenBudget } from \"../filetools-shared.js\";\nimport { resolveReadPath } from \"../path-utils.js\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"../render-utils.js\";\nimport { wrapToolDefinition } from \"../tool-definition-wrapper.js\";\n\nconst docScanSchema = Type.Object({\n\tpath: Type.String({\n\t\tdescription: \"Path to the document to scan (relative or absolute). XML, drawio, OOXML (docx/xlsx/pptx), or PDF.\",\n\t}),\n\toffset: Type.Optional(Type.Number({ description: \"Skip the first N blocks (pagination). Default 0.\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of blocks to return. Default: all remaining.\" })),\n});\n\nexport type DocScanToolInput = Static<typeof docScanSchema>;\n\nexport interface DocScanToolDetails {\n\tfileType?: string;\n\treturned?: number;\n\ttotal?: number;\n}\n\nexport interface DocScanToolOptions {\n\t/** Timeout (seconds) for the filetools invocation. */\n\ttimeoutSecs?: number;\n}\n\n/**\n * Render a scan manifest as compact, id-addressed preview lines. Each line\n * carries the block id (feeds DocPeek/DocGrep) plus a section label, token\n * estimate, and a short preview — structure without hydrating full content.\n */\nexport function renderScanView(view: ScanView): string {\n\tconst header =\n\t\t`document ${view.file_type} — ${view.returned}/${view.total} blocks ` +\n\t\t`(offset ${view.offset}, ~${view.total_tokens} tok total)`;\n\tconst lines: string[] = [header, \"\"];\n\tfor (const block of view.blocks) {\n\t\tconst section = block.section_name ? ` ${block.section_name}#${block.section_number}` : \"\";\n\t\tconst preview = block.preview ? ` :: ${JSON.stringify(block.preview)}` : \"\";\n\t\tlines.push(`#${block.id} [${block.block_type}]${section} ~${block.token_estimate}tok${preview}`);\n\t}\n\n\tconst { text, droppedLines } = truncateRenderToTokenBudget(lines);\n\tconst seen = view.offset + view.returned;\n\tconst remaining = view.total - seen;\n\tconst parts = [text];\n\tif (droppedLines > 0) {\n\t\tparts.push(\n\t\t\t`\\n[Truncated: ${droppedLines} more block line${droppedLines === 1 ? \"\" : \"s\"} omitted from this render. ` +\n\t\t\t\t`Re-run with a tighter limit, or DocGrep/DocPeek to target.]`,\n\t\t);\n\t}\n\tif (remaining > 0) {\n\t\tparts.push(\n\t\t\t`\\n[${remaining} more block${remaining === 1 ? \"\" : \"s\"} not shown — re-run with offset:${seen} to continue.]`,\n\t\t);\n\t}\n\treturn parts.join(\"\\n\");\n}\n\nfunction formatDocScanCall(args: { path?: string; offset?: number; limit?: number } | undefined): string {\n\tconst path = str(args?.path);\n\tconst pathDisplay =\n\t\tpath === null ? invalidArgText(appTheme) : path ? shortenPath(path) : appTheme.fg(\"toolOutput\", \"...\");\n\tlet text = appTheme.fg(\"toolTitle\", appTheme.bold(\"DocScan \")) + appTheme.fg(\"accent\", pathDisplay);\n\tconst range: string[] = [];\n\tif (args?.offset !== undefined) range.push(`offset ${args.offset}`);\n\tif (args?.limit !== undefined) range.push(`limit ${args.limit}`);\n\tif (range.length > 0) text += appTheme.fg(\"muted\", ` (${range.join(\", \")})`);\n\treturn text;\n}\n\nfunction formatDocScanResult(\n\tresult: { content: Array<{ type: string; text?: string }> },\n\toptions: ToolRenderResultOptions,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result as any, showImages).trim();\n\tif (!output) return \"\";\n\tconst lines = output.split(\"\\n\");\n\tconst maxLines = options.expanded ? lines.length : 15;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\tlet text = `\\n${displayLines.map((line) => appTheme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `${appTheme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t}\n\treturn text;\n}\n\nexport function createDocScanToolDefinition(\n\tcwd: string,\n\toptions?: DocScanToolOptions,\n): ToolDefinition<typeof docScanSchema, DocScanToolDetails | undefined> {\n\treturn {\n\t\tname: \"DocScan\",\n\t\tlabel: \"DocScan\",\n\t\tdescription:\n\t\t\t\"Cheaply outline a structured/binary document (XML, drawio, docx/xlsx/pptx, PDF) without hydrating it: returns a paginated manifest of blocks, each with a structural-path id, type, section label, token estimate, and a short preview. This is the cheap first step of the token-sensitive loop — far smaller than a full DocRead. Pass the path ids it returns to DocPeek to hydrate just those blocks (the hydrated nodes carry the editable el_ #ids for DocEdit). Off by default; enabled with --enable-filetools.\",\n\t\tpromptSnippet: \"Outline a structured/binary document into a cheap, paginated block manifest\",\n\t\tpromptGuidelines: [\n\t\t\t\"Start here for large structured/binary documents instead of a full DocRead: DocScan returns a paginated outline (structural-path block ids + previews) that is much cheaper in tokens. Page through it with offset/limit.\",\n\t\t\t\"Flow: DocScan to see structure → DocPeek the path ids you want (hydrates them and reveals their editable el_ #ids) → DocEdit. Or jump straight in with DocGrep, which finds blocks by text and returns editable el_ #ids directly.\",\n\t\t],\n\t\tparameters: docScanSchema,\n\t\tasync execute(_toolCallId, { path, offset, limit }: DocScanToolInput, signal?: AbortSignal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tconst absolutePath = resolveReadPath(path, cwd);\n\t\t\tconst view = await scanDocument(absolutePath, cwd, signal, {\n\t\t\t\toffset,\n\t\t\t\tlimit,\n\t\t\t\ttimeoutSecs: options?.timeoutSecs,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: renderScanView(view) }],\n\t\t\t\tdetails: { fileType: view.file_type, returned: view.returned, total: view.total },\n\t\t\t};\n\t\t},\n\t\trenderCall(args, _theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatDocScanCall(args));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, _theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatDocScanResult(result as any, options, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createDocScanTool(cwd: string, options?: DocScanToolOptions): AgentTool<typeof docScanSchema> {\n\treturn wrapToolDefinition(createDocScanToolDefinition(cwd, options));\n}\n"]}
1
+ {"version":3,"file":"docscan.js","sourceRoot":"","sources":["../../../../src/core/tools/doc/docscan.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../../modes/interactive/components/keybinding-hints.js\";\nimport { theme as appTheme } from \"../../../modes/interactive/theme/theme.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../../extensions/types.js\";\nimport { type ScanView, scanDocument, truncateRenderToTokenBudget } from \"../filetools-shared.js\";\nimport { resolveReadPath } from \"../path-utils.js\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"../render-utils.js\";\nimport { wrapToolDefinition } from \"../tool-definition-wrapper.js\";\n\nconst docScanSchema = Type.Object({\n\tpath: Type.String({\n\t\tdescription: \"Path to the document to scan (relative or absolute). XML, drawio, OOXML (docx/xlsx/pptx), or PDF.\",\n\t}),\n\toffset: Type.Optional(Type.Number({ description: \"Skip the first N blocks (pagination). Default 0.\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of blocks to return. Default: all remaining.\" })),\n});\n\ntype DocScanToolInput = Static<typeof docScanSchema>;\n\nexport interface DocScanToolDetails {\n\tfileType?: string;\n\treturned?: number;\n\ttotal?: number;\n}\n\nexport interface DocScanToolOptions {\n\t/** Timeout (seconds) for the filetools invocation. */\n\ttimeoutSecs?: number;\n}\n\n/**\n * Render a scan manifest as compact, id-addressed preview lines. Each line\n * carries the block id (feeds DocPeek/DocGrep) plus a section label, token\n * estimate, and a short preview — structure without hydrating full content.\n */\nexport function renderScanView(view: ScanView): string {\n\tconst header =\n\t\t`document ${view.file_type} — ${view.returned}/${view.total} blocks ` +\n\t\t`(offset ${view.offset}, ~${view.total_tokens} tok total)`;\n\tconst lines: string[] = [header, \"\"];\n\tfor (const block of view.blocks) {\n\t\tconst section = block.section_name ? ` ${block.section_name}#${block.section_number}` : \"\";\n\t\tconst preview = block.preview ? ` :: ${JSON.stringify(block.preview)}` : \"\";\n\t\tlines.push(`#${block.id} [${block.block_type}]${section} ~${block.token_estimate}tok${preview}`);\n\t}\n\n\tconst { text, droppedLines } = truncateRenderToTokenBudget(lines);\n\tconst seen = view.offset + view.returned;\n\tconst remaining = view.total - seen;\n\tconst parts = [text];\n\tif (droppedLines > 0) {\n\t\tparts.push(\n\t\t\t`\\n[Truncated: ${droppedLines} more block line${droppedLines === 1 ? \"\" : \"s\"} omitted from this render. ` +\n\t\t\t\t`Re-run with a tighter limit, or DocGrep/DocPeek to target.]`,\n\t\t);\n\t}\n\tif (remaining > 0) {\n\t\tparts.push(\n\t\t\t`\\n[${remaining} more block${remaining === 1 ? \"\" : \"s\"} not shown — re-run with offset:${seen} to continue.]`,\n\t\t);\n\t}\n\treturn parts.join(\"\\n\");\n}\n\nfunction formatDocScanCall(args: { path?: string; offset?: number; limit?: number } | undefined): string {\n\tconst path = str(args?.path);\n\tconst pathDisplay =\n\t\tpath === null ? invalidArgText(appTheme) : path ? shortenPath(path) : appTheme.fg(\"toolOutput\", \"...\");\n\tlet text = appTheme.fg(\"toolTitle\", appTheme.bold(\"DocScan \")) + appTheme.fg(\"accent\", pathDisplay);\n\tconst range: string[] = [];\n\tif (args?.offset !== undefined) range.push(`offset ${args.offset}`);\n\tif (args?.limit !== undefined) range.push(`limit ${args.limit}`);\n\tif (range.length > 0) text += appTheme.fg(\"muted\", ` (${range.join(\", \")})`);\n\treturn text;\n}\n\nfunction formatDocScanResult(\n\tresult: { content: Array<{ type: string; text?: string }> },\n\toptions: ToolRenderResultOptions,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result as any, showImages).trim();\n\tif (!output) return \"\";\n\tconst lines = output.split(\"\\n\");\n\tconst maxLines = options.expanded ? lines.length : 15;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\tlet text = `\\n${displayLines.map((line) => appTheme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `${appTheme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t}\n\treturn text;\n}\n\nexport function createDocScanToolDefinition(\n\tcwd: string,\n\toptions?: DocScanToolOptions,\n): ToolDefinition<typeof docScanSchema, DocScanToolDetails | undefined> {\n\treturn {\n\t\tname: \"DocScan\",\n\t\tlabel: \"DocScan\",\n\t\tdescription:\n\t\t\t\"Cheaply outline a structured/binary document (XML, drawio, docx/xlsx/pptx, PDF) without hydrating it: returns a paginated manifest of blocks, each with a structural-path id, type, section label, token estimate, and a short preview. This is the cheap first step of the token-sensitive loop — far smaller than a full DocRead. Pass the path ids it returns to DocPeek to hydrate just those blocks (the hydrated nodes carry the editable el_ #ids for DocEdit). Off by default; enabled with --enable-filetools.\",\n\t\tpromptSnippet: \"Outline a structured/binary document into a cheap, paginated block manifest\",\n\t\tpromptGuidelines: [\n\t\t\t\"Start here for large structured/binary documents instead of a full DocRead: DocScan returns a paginated outline (structural-path block ids + previews) that is much cheaper in tokens. Page through it with offset/limit.\",\n\t\t\t\"Flow: DocScan to see structure → DocPeek the path ids you want (hydrates them and reveals their editable el_ #ids) → DocEdit. Or jump straight in with DocGrep, which finds blocks by text and returns editable el_ #ids directly.\",\n\t\t],\n\t\tparameters: docScanSchema,\n\t\tasync execute(_toolCallId, { path, offset, limit }: DocScanToolInput, signal?: AbortSignal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tconst absolutePath = resolveReadPath(path, cwd);\n\t\t\tconst view = await scanDocument(absolutePath, cwd, signal, {\n\t\t\t\toffset,\n\t\t\t\tlimit,\n\t\t\t\ttimeoutSecs: options?.timeoutSecs,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: renderScanView(view) }],\n\t\t\t\tdetails: { fileType: view.file_type, returned: view.returned, total: view.total },\n\t\t\t};\n\t\t},\n\t\trenderCall(args, _theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatDocScanCall(args));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, _theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatDocScanResult(result as any, options, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createDocScanTool(cwd: string, options?: DocScanToolOptions): AgentTool<typeof docScanSchema> {\n\treturn wrapToolDefinition(createDocScanToolDefinition(cwd, options));\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { AgentTool } from "@kolisachint/hoocode-agent-core";
2
- import { type Static, Type } from "typebox";
2
+ import { Type } from "typebox";
3
3
  import type { ToolDefinition } from "../../extensions/types.js";
4
4
  declare const docWriteSchema: Type.TObject<{
5
5
  path: Type.TString;
@@ -29,7 +29,6 @@ declare const docWriteSchema: Type.TObject<{
29
29
  path: Type.TString;
30
30
  }>]>>;
31
31
  }>;
32
- export type DocWriteToolInput = Static<typeof docWriteSchema>;
33
32
  export interface DocWriteToolDetails {
34
33
  ops?: number;
35
34
  out?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"docwrite.d.ts","sourceRoot":"","sources":["../../../../src/core/tools/doc/docwrite.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAQhE,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQlB,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC;AAE9D,MAAM,WAAW,mBAAmB;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,mBAAmB;IACnC,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAmBD,wBAAgB,4BAA4B,CAC3C,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,mBAAmB,GAC3B,cAAc,CAAC,OAAO,cAAc,EAAE,mBAAmB,GAAG,SAAS,CAAC,CA+DxE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC,OAAO,cAAc,CAAC,CAE/G","sourcesContent":["import { mkdir as fsMkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Container, Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { theme as appTheme } from \"../../../modes/interactive/theme/theme.js\";\nimport type { ToolDefinition } from \"../../extensions/types.js\";\nimport { withFileMutationQueue } from \"../file-mutation-queue.js\";\nimport { patchOpsSchema, reconstructDocument, StalePatchError, toPatch } from \"../filetools-shared.js\";\nimport { resolveReadPath, resolveToCwd } from \"../path-utils.js\";\nimport { invalidArgText, shortenPath, str } from \"../render-utils.js\";\nimport { wrapToolDefinition } from \"../tool-definition-wrapper.js\";\nimport { renderEnvelopeText } from \"./docread.js\";\n\nconst docWriteSchema = Type.Object({\n\tpath: Type.String({\n\t\tdescription: \"Path to the SOURCE document. Must have been opened with DocRead first. Left untouched.\",\n\t}),\n\tout: Type.String({\n\t\tdescription: \"Output path for the reconstructed document (relative or absolute). Created/overwritten.\",\n\t}),\n\tpatch: patchOpsSchema,\n});\n\nexport type DocWriteToolInput = Static<typeof docWriteSchema>;\n\nexport interface DocWriteToolDetails {\n\tops?: number;\n\tout?: string;\n}\n\nexport interface DocWriteToolOptions {\n\t/** Timeout (seconds) for the filetools invocation. */\n\ttimeoutSecs?: number;\n}\n\nfunction formatDocWriteCall(args: { path?: string; out?: string; patch?: unknown[] } | undefined): string {\n\tconst path = str(args?.path);\n\tconst out = str(args?.out);\n\tconst pathDisplay =\n\t\tpath === null ? invalidArgText(appTheme) : path ? shortenPath(path) : appTheme.fg(\"toolOutput\", \"...\");\n\tconst outDisplay =\n\t\tout === null ? invalidArgText(appTheme) : out ? shortenPath(out) : appTheme.fg(\"toolOutput\", \"...\");\n\tlet text =\n\t\tappTheme.fg(\"toolTitle\", appTheme.bold(\"DocWrite \")) +\n\t\tappTheme.fg(\"accent\", pathDisplay) +\n\t\tappTheme.fg(\"muted\", \" -> \") +\n\t\tappTheme.fg(\"accent\", outDisplay);\n\tconst ops = Array.isArray(args?.patch) ? args.patch.length : undefined;\n\tif (ops !== undefined) text += appTheme.fg(\"muted\", ` (${ops} op${ops === 1 ? \"\" : \"s\"})`);\n\treturn text;\n}\n\nexport function createDocWriteToolDefinition(\n\tcwd: string,\n\toptions?: DocWriteToolOptions,\n): ToolDefinition<typeof docWriteSchema, DocWriteToolDetails | undefined> {\n\treturn {\n\t\tname: \"DocWrite\",\n\t\tlabel: \"DocWrite\",\n\t\tdescription:\n\t\t\t\"Apply an id-based patch to a structured/binary document and write the result to a NEW path, leaving the source untouched (save-as). Targets node ids from a DocRead extract of the source; if the cache is missing or the source changed on disk (e.g. an external script rewrote it) it is re-extracted automatically, so a separate DocRead is not required. If the patch references ids that no longer exist, the call fails and returns the current structure with fresh ids so you can re-issue. This is the canonical way to rewrite these formats: never fall back to ad-hoc scripts (python/openpyxl, docx, PyPDF2, unzip, sed) to produce the output — that bypasses the lossless id-map and corrupts the file. Off by default; enabled with --enable-filetools.\",\n\t\tpromptSnippet: \"Reconstruct a patched structured/binary document to a new path\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use DocWrite to save an edited document to a different file: pass the source path (opened with DocRead), an `out` path, and an id-based patch. The source is left unchanged.\",\n\t\t\t\"Keep the patch minimal and scan before you write: prefer a DocRead readonly:true glimpse to plan the change, and avoid re-running a full writable DocRead between writes — it auto-extracts the source and is token-heavy.\",\n\t\t],\n\t\tparameters: docWriteSchema,\n\t\tasync execute(_toolCallId, { path, out, patch }: DocWriteToolInput, signal?: AbortSignal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tconst absolutePath = resolveReadPath(path, cwd);\n\t\t\tconst outPath = resolveToCwd(out, cwd);\n\n\t\t\treturn withFileMutationQueue(outPath, async () => {\n\t\t\t\tawait fsMkdir(dirname(outPath), { recursive: true });\n\t\t\t\ttry {\n\t\t\t\t\tawait reconstructDocument(absolutePath, toPatch(patch), outPath, cwd, signal, {\n\t\t\t\t\t\ttimeoutSecs: options?.timeoutSecs,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\tif (err instanceof StalePatchError) {\n\t\t\t\t\t\t// The source was rewritten out-of-band: the cache auto-refreshed\n\t\t\t\t\t\t// but the patch targets ids that no longer exist. Surface the\n\t\t\t\t\t\t// current structure so the agent can re-issue without a DocRead.\n\t\t\t\t\t\tthrow new Error(`${err.message}\\n\\n${renderEnvelopeText(err.envelope, false)}`);\n\t\t\t\t\t}\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\ttext: `Wrote ${patch.length} op${patch.length === 1 ? \"\" : \"s\"} from ${path} to ${out}.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { ops: patch.length, out },\n\t\t\t\t};\n\t\t\t});\n\t\t},\n\t\trenderCall(args, _theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatDocWriteCall(args));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, _options, _theme, context) {\n\t\t\tif (!context.isError) {\n\t\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\t\tcomponent.clear();\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tconst output = (result.content as Array<{ type: string; text?: string }>)\n\t\t\t\t.filter((c) => c.type === \"text\")\n\t\t\t\t.map((c) => c.text || \"\")\n\t\t\t\t.join(\"\\n\");\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(output ? `\\n${appTheme.fg(\"error\", output)}` : \"\");\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createDocWriteTool(cwd: string, options?: DocWriteToolOptions): AgentTool<typeof docWriteSchema> {\n\treturn wrapToolDefinition(createDocWriteToolDefinition(cwd, options));\n}\n"]}
1
+ {"version":3,"file":"docwrite.d.ts","sourceRoot":"","sources":["../../../../src/core/tools/doc/docwrite.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAQhE,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQlB,CAAC;AAIH,MAAM,WAAW,mBAAmB;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,mBAAmB;IACnC,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAmBD,wBAAgB,4BAA4B,CAC3C,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,mBAAmB,GAC3B,cAAc,CAAC,OAAO,cAAc,EAAE,mBAAmB,GAAG,SAAS,CAAC,CA+DxE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC,OAAO,cAAc,CAAC,CAE/G","sourcesContent":["import { mkdir as fsMkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Container, Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { theme as appTheme } from \"../../../modes/interactive/theme/theme.js\";\nimport type { ToolDefinition } from \"../../extensions/types.js\";\nimport { withFileMutationQueue } from \"../file-mutation-queue.js\";\nimport { patchOpsSchema, reconstructDocument, StalePatchError, toPatch } from \"../filetools-shared.js\";\nimport { resolveReadPath, resolveToCwd } from \"../path-utils.js\";\nimport { invalidArgText, shortenPath, str } from \"../render-utils.js\";\nimport { wrapToolDefinition } from \"../tool-definition-wrapper.js\";\nimport { renderEnvelopeText } from \"./docread.js\";\n\nconst docWriteSchema = Type.Object({\n\tpath: Type.String({\n\t\tdescription: \"Path to the SOURCE document. Must have been opened with DocRead first. Left untouched.\",\n\t}),\n\tout: Type.String({\n\t\tdescription: \"Output path for the reconstructed document (relative or absolute). Created/overwritten.\",\n\t}),\n\tpatch: patchOpsSchema,\n});\n\ntype DocWriteToolInput = Static<typeof docWriteSchema>;\n\nexport interface DocWriteToolDetails {\n\tops?: number;\n\tout?: string;\n}\n\nexport interface DocWriteToolOptions {\n\t/** Timeout (seconds) for the filetools invocation. */\n\ttimeoutSecs?: number;\n}\n\nfunction formatDocWriteCall(args: { path?: string; out?: string; patch?: unknown[] } | undefined): string {\n\tconst path = str(args?.path);\n\tconst out = str(args?.out);\n\tconst pathDisplay =\n\t\tpath === null ? invalidArgText(appTheme) : path ? shortenPath(path) : appTheme.fg(\"toolOutput\", \"...\");\n\tconst outDisplay =\n\t\tout === null ? invalidArgText(appTheme) : out ? shortenPath(out) : appTheme.fg(\"toolOutput\", \"...\");\n\tlet text =\n\t\tappTheme.fg(\"toolTitle\", appTheme.bold(\"DocWrite \")) +\n\t\tappTheme.fg(\"accent\", pathDisplay) +\n\t\tappTheme.fg(\"muted\", \" -> \") +\n\t\tappTheme.fg(\"accent\", outDisplay);\n\tconst ops = Array.isArray(args?.patch) ? args.patch.length : undefined;\n\tif (ops !== undefined) text += appTheme.fg(\"muted\", ` (${ops} op${ops === 1 ? \"\" : \"s\"})`);\n\treturn text;\n}\n\nexport function createDocWriteToolDefinition(\n\tcwd: string,\n\toptions?: DocWriteToolOptions,\n): ToolDefinition<typeof docWriteSchema, DocWriteToolDetails | undefined> {\n\treturn {\n\t\tname: \"DocWrite\",\n\t\tlabel: \"DocWrite\",\n\t\tdescription:\n\t\t\t\"Apply an id-based patch to a structured/binary document and write the result to a NEW path, leaving the source untouched (save-as). Targets node ids from a DocRead extract of the source; if the cache is missing or the source changed on disk (e.g. an external script rewrote it) it is re-extracted automatically, so a separate DocRead is not required. If the patch references ids that no longer exist, the call fails and returns the current structure with fresh ids so you can re-issue. This is the canonical way to rewrite these formats: never fall back to ad-hoc scripts (python/openpyxl, docx, PyPDF2, unzip, sed) to produce the output — that bypasses the lossless id-map and corrupts the file. Off by default; enabled with --enable-filetools.\",\n\t\tpromptSnippet: \"Reconstruct a patched structured/binary document to a new path\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use DocWrite to save an edited document to a different file: pass the source path (opened with DocRead), an `out` path, and an id-based patch. The source is left unchanged.\",\n\t\t\t\"Keep the patch minimal and scan before you write: prefer a DocRead readonly:true glimpse to plan the change, and avoid re-running a full writable DocRead between writes — it auto-extracts the source and is token-heavy.\",\n\t\t],\n\t\tparameters: docWriteSchema,\n\t\tasync execute(_toolCallId, { path, out, patch }: DocWriteToolInput, signal?: AbortSignal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tconst absolutePath = resolveReadPath(path, cwd);\n\t\t\tconst outPath = resolveToCwd(out, cwd);\n\n\t\t\treturn withFileMutationQueue(outPath, async () => {\n\t\t\t\tawait fsMkdir(dirname(outPath), { recursive: true });\n\t\t\t\ttry {\n\t\t\t\t\tawait reconstructDocument(absolutePath, toPatch(patch), outPath, cwd, signal, {\n\t\t\t\t\t\ttimeoutSecs: options?.timeoutSecs,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\tif (err instanceof StalePatchError) {\n\t\t\t\t\t\t// The source was rewritten out-of-band: the cache auto-refreshed\n\t\t\t\t\t\t// but the patch targets ids that no longer exist. Surface the\n\t\t\t\t\t\t// current structure so the agent can re-issue without a DocRead.\n\t\t\t\t\t\tthrow new Error(`${err.message}\\n\\n${renderEnvelopeText(err.envelope, false)}`);\n\t\t\t\t\t}\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\ttext: `Wrote ${patch.length} op${patch.length === 1 ? \"\" : \"s\"} from ${path} to ${out}.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { ops: patch.length, out },\n\t\t\t\t};\n\t\t\t});\n\t\t},\n\t\trenderCall(args, _theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatDocWriteCall(args));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, _options, _theme, context) {\n\t\t\tif (!context.isError) {\n\t\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\t\tcomponent.clear();\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tconst output = (result.content as Array<{ type: string; text?: string }>)\n\t\t\t\t.filter((c) => c.type === \"text\")\n\t\t\t\t.map((c) => c.text || \"\")\n\t\t\t\t.join(\"\\n\");\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(output ? `\\n${appTheme.fg(\"error\", output)}` : \"\");\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createDocWriteTool(cwd: string, options?: DocWriteToolOptions): AgentTool<typeof docWriteSchema> {\n\treturn wrapToolDefinition(createDocWriteToolDefinition(cwd, options));\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"docwrite.js","sourceRoot":"","sources":["../../../../src/core/tools/doc/docwrite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,KAAK,IAAI,QAAQ,EAAE,MAAM,2CAA2C,CAAC;AAE9E,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACvG,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC;QACjB,WAAW,EAAE,wFAAwF;KACrG,CAAC;IACF,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC;QAChB,WAAW,EAAE,yFAAyF;KACtG,CAAC;IACF,KAAK,EAAE,cAAc;CACrB,CAAC,CAAC;AAcH,SAAS,kBAAkB,CAAC,IAAoE,EAAU;IACzG,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7B,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC3B,MAAM,WAAW,GAChB,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IACxG,MAAM,UAAU,GACf,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IACrG,IAAI,IAAI,GACP,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACpD,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC;QAClC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IACvE,IAAI,GAAG,KAAK,SAAS;QAAE,IAAI,IAAI,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;IAC3F,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,MAAM,UAAU,4BAA4B,CAC3C,GAAW,EACX,OAA6B,EAC4C;IACzE,OAAO;QACN,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,UAAU;QACjB,WAAW,EACV,6uBAA2uB;QAC5uB,aAAa,EAAE,gEAAgE;QAC/E,gBAAgB,EAAE;YACjB,8KAA8K;YAC9K,8NAA4N;SAC5N;QACD,UAAU,EAAE,cAAc;QAC1B,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAqB,EAAE,MAAoB,EAAE;YACzF,IAAI,MAAM,EAAE,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;YAC1D,MAAM,YAAY,GAAG,eAAe,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAChD,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAEvC,OAAO,qBAAqB,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC;gBACjD,MAAM,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBACrD,IAAI,CAAC;oBACJ,MAAM,mBAAmB,CAAC,YAAY,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE;wBAC7E,WAAW,EAAE,OAAO,EAAE,WAAW;qBACjC,CAAC,CAAC;gBACJ,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,IAAI,GAAG,YAAY,eAAe,EAAE,CAAC;wBACpC,iEAAiE;wBACjE,8DAA8D;wBAC9D,iEAAiE;wBACjE,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,CAAC,OAAO,OAAO,kBAAkB,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;oBACjF,CAAC;oBACD,MAAM,GAAG,CAAC;gBACX,CAAC;gBACD,OAAO;oBACN,OAAO,EAAE;wBACR;4BACC,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,SAAS,KAAK,CAAC,MAAM,MAAM,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,SAAS,IAAI,OAAO,GAAG,GAAG;yBACxF;qBACD;oBACD,OAAO,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE;iBACnC,CAAC;YAAA,CACF,CAAC,CAAC;QAAA,CACH;QACD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;YACjC,MAAM,IAAI,GAAI,OAAO,CAAC,aAAkC,IAAI,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/E,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;YACvC,OAAO,IAAI,CAAC;QAAA,CACZ;QACD,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;YAC/C,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;gBACtB,MAAM,SAAS,GAAI,OAAO,CAAC,aAAuC,IAAI,IAAI,SAAS,EAAE,CAAC;gBACtF,SAAS,CAAC,KAAK,EAAE,CAAC;gBAClB,OAAO,SAAS,CAAC;YAClB,CAAC;YACD,MAAM,MAAM,GAAI,MAAM,CAAC,OAAkD;iBACvE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;iBAChC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;iBACxB,IAAI,CAAC,IAAI,CAAC,CAAC;YACb,MAAM,IAAI,GAAI,OAAO,CAAC,aAAkC,IAAI,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/E,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAChE,OAAO,IAAI,CAAC;QAAA,CACZ;KACD,CAAC;AAAA,CACF;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAW,EAAE,OAA6B,EAAoC;IAChH,OAAO,kBAAkB,CAAC,4BAA4B,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;AAAA,CACtE","sourcesContent":["import { mkdir as fsMkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Container, Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { theme as appTheme } from \"../../../modes/interactive/theme/theme.js\";\nimport type { ToolDefinition } from \"../../extensions/types.js\";\nimport { withFileMutationQueue } from \"../file-mutation-queue.js\";\nimport { patchOpsSchema, reconstructDocument, StalePatchError, toPatch } from \"../filetools-shared.js\";\nimport { resolveReadPath, resolveToCwd } from \"../path-utils.js\";\nimport { invalidArgText, shortenPath, str } from \"../render-utils.js\";\nimport { wrapToolDefinition } from \"../tool-definition-wrapper.js\";\nimport { renderEnvelopeText } from \"./docread.js\";\n\nconst docWriteSchema = Type.Object({\n\tpath: Type.String({\n\t\tdescription: \"Path to the SOURCE document. Must have been opened with DocRead first. Left untouched.\",\n\t}),\n\tout: Type.String({\n\t\tdescription: \"Output path for the reconstructed document (relative or absolute). Created/overwritten.\",\n\t}),\n\tpatch: patchOpsSchema,\n});\n\nexport type DocWriteToolInput = Static<typeof docWriteSchema>;\n\nexport interface DocWriteToolDetails {\n\tops?: number;\n\tout?: string;\n}\n\nexport interface DocWriteToolOptions {\n\t/** Timeout (seconds) for the filetools invocation. */\n\ttimeoutSecs?: number;\n}\n\nfunction formatDocWriteCall(args: { path?: string; out?: string; patch?: unknown[] } | undefined): string {\n\tconst path = str(args?.path);\n\tconst out = str(args?.out);\n\tconst pathDisplay =\n\t\tpath === null ? invalidArgText(appTheme) : path ? shortenPath(path) : appTheme.fg(\"toolOutput\", \"...\");\n\tconst outDisplay =\n\t\tout === null ? invalidArgText(appTheme) : out ? shortenPath(out) : appTheme.fg(\"toolOutput\", \"...\");\n\tlet text =\n\t\tappTheme.fg(\"toolTitle\", appTheme.bold(\"DocWrite \")) +\n\t\tappTheme.fg(\"accent\", pathDisplay) +\n\t\tappTheme.fg(\"muted\", \" -> \") +\n\t\tappTheme.fg(\"accent\", outDisplay);\n\tconst ops = Array.isArray(args?.patch) ? args.patch.length : undefined;\n\tif (ops !== undefined) text += appTheme.fg(\"muted\", ` (${ops} op${ops === 1 ? \"\" : \"s\"})`);\n\treturn text;\n}\n\nexport function createDocWriteToolDefinition(\n\tcwd: string,\n\toptions?: DocWriteToolOptions,\n): ToolDefinition<typeof docWriteSchema, DocWriteToolDetails | undefined> {\n\treturn {\n\t\tname: \"DocWrite\",\n\t\tlabel: \"DocWrite\",\n\t\tdescription:\n\t\t\t\"Apply an id-based patch to a structured/binary document and write the result to a NEW path, leaving the source untouched (save-as). Targets node ids from a DocRead extract of the source; if the cache is missing or the source changed on disk (e.g. an external script rewrote it) it is re-extracted automatically, so a separate DocRead is not required. If the patch references ids that no longer exist, the call fails and returns the current structure with fresh ids so you can re-issue. This is the canonical way to rewrite these formats: never fall back to ad-hoc scripts (python/openpyxl, docx, PyPDF2, unzip, sed) to produce the output — that bypasses the lossless id-map and corrupts the file. Off by default; enabled with --enable-filetools.\",\n\t\tpromptSnippet: \"Reconstruct a patched structured/binary document to a new path\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use DocWrite to save an edited document to a different file: pass the source path (opened with DocRead), an `out` path, and an id-based patch. The source is left unchanged.\",\n\t\t\t\"Keep the patch minimal and scan before you write: prefer a DocRead readonly:true glimpse to plan the change, and avoid re-running a full writable DocRead between writes — it auto-extracts the source and is token-heavy.\",\n\t\t],\n\t\tparameters: docWriteSchema,\n\t\tasync execute(_toolCallId, { path, out, patch }: DocWriteToolInput, signal?: AbortSignal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tconst absolutePath = resolveReadPath(path, cwd);\n\t\t\tconst outPath = resolveToCwd(out, cwd);\n\n\t\t\treturn withFileMutationQueue(outPath, async () => {\n\t\t\t\tawait fsMkdir(dirname(outPath), { recursive: true });\n\t\t\t\ttry {\n\t\t\t\t\tawait reconstructDocument(absolutePath, toPatch(patch), outPath, cwd, signal, {\n\t\t\t\t\t\ttimeoutSecs: options?.timeoutSecs,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\tif (err instanceof StalePatchError) {\n\t\t\t\t\t\t// The source was rewritten out-of-band: the cache auto-refreshed\n\t\t\t\t\t\t// but the patch targets ids that no longer exist. Surface the\n\t\t\t\t\t\t// current structure so the agent can re-issue without a DocRead.\n\t\t\t\t\t\tthrow new Error(`${err.message}\\n\\n${renderEnvelopeText(err.envelope, false)}`);\n\t\t\t\t\t}\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\ttext: `Wrote ${patch.length} op${patch.length === 1 ? \"\" : \"s\"} from ${path} to ${out}.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { ops: patch.length, out },\n\t\t\t\t};\n\t\t\t});\n\t\t},\n\t\trenderCall(args, _theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatDocWriteCall(args));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, _options, _theme, context) {\n\t\t\tif (!context.isError) {\n\t\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\t\tcomponent.clear();\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tconst output = (result.content as Array<{ type: string; text?: string }>)\n\t\t\t\t.filter((c) => c.type === \"text\")\n\t\t\t\t.map((c) => c.text || \"\")\n\t\t\t\t.join(\"\\n\");\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(output ? `\\n${appTheme.fg(\"error\", output)}` : \"\");\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createDocWriteTool(cwd: string, options?: DocWriteToolOptions): AgentTool<typeof docWriteSchema> {\n\treturn wrapToolDefinition(createDocWriteToolDefinition(cwd, options));\n}\n"]}
1
+ {"version":3,"file":"docwrite.js","sourceRoot":"","sources":["../../../../src/core/tools/doc/docwrite.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir as fsMkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Container, Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { theme as appTheme } from \"../../../modes/interactive/theme/theme.js\";\nimport type { ToolDefinition } from \"../../extensions/types.js\";\nimport { withFileMutationQueue } from \"../file-mutation-queue.js\";\nimport { patchOpsSchema, reconstructDocument, StalePatchError, toPatch } from \"../filetools-shared.js\";\nimport { resolveReadPath, resolveToCwd } from \"../path-utils.js\";\nimport { invalidArgText, shortenPath, str } from \"../render-utils.js\";\nimport { wrapToolDefinition } from \"../tool-definition-wrapper.js\";\nimport { renderEnvelopeText } from \"./docread.js\";\n\nconst docWriteSchema = Type.Object({\n\tpath: Type.String({\n\t\tdescription: \"Path to the SOURCE document. Must have been opened with DocRead first. Left untouched.\",\n\t}),\n\tout: Type.String({\n\t\tdescription: \"Output path for the reconstructed document (relative or absolute). Created/overwritten.\",\n\t}),\n\tpatch: patchOpsSchema,\n});\n\ntype DocWriteToolInput = Static<typeof docWriteSchema>;\n\nexport interface DocWriteToolDetails {\n\tops?: number;\n\tout?: string;\n}\n\nexport interface DocWriteToolOptions {\n\t/** Timeout (seconds) for the filetools invocation. */\n\ttimeoutSecs?: number;\n}\n\nfunction formatDocWriteCall(args: { path?: string; out?: string; patch?: unknown[] } | undefined): string {\n\tconst path = str(args?.path);\n\tconst out = str(args?.out);\n\tconst pathDisplay =\n\t\tpath === null ? invalidArgText(appTheme) : path ? shortenPath(path) : appTheme.fg(\"toolOutput\", \"...\");\n\tconst outDisplay =\n\t\tout === null ? invalidArgText(appTheme) : out ? shortenPath(out) : appTheme.fg(\"toolOutput\", \"...\");\n\tlet text =\n\t\tappTheme.fg(\"toolTitle\", appTheme.bold(\"DocWrite \")) +\n\t\tappTheme.fg(\"accent\", pathDisplay) +\n\t\tappTheme.fg(\"muted\", \" -> \") +\n\t\tappTheme.fg(\"accent\", outDisplay);\n\tconst ops = Array.isArray(args?.patch) ? args.patch.length : undefined;\n\tif (ops !== undefined) text += appTheme.fg(\"muted\", ` (${ops} op${ops === 1 ? \"\" : \"s\"})`);\n\treturn text;\n}\n\nexport function createDocWriteToolDefinition(\n\tcwd: string,\n\toptions?: DocWriteToolOptions,\n): ToolDefinition<typeof docWriteSchema, DocWriteToolDetails | undefined> {\n\treturn {\n\t\tname: \"DocWrite\",\n\t\tlabel: \"DocWrite\",\n\t\tdescription:\n\t\t\t\"Apply an id-based patch to a structured/binary document and write the result to a NEW path, leaving the source untouched (save-as). Targets node ids from a DocRead extract of the source; if the cache is missing or the source changed on disk (e.g. an external script rewrote it) it is re-extracted automatically, so a separate DocRead is not required. If the patch references ids that no longer exist, the call fails and returns the current structure with fresh ids so you can re-issue. This is the canonical way to rewrite these formats: never fall back to ad-hoc scripts (python/openpyxl, docx, PyPDF2, unzip, sed) to produce the output — that bypasses the lossless id-map and corrupts the file. Off by default; enabled with --enable-filetools.\",\n\t\tpromptSnippet: \"Reconstruct a patched structured/binary document to a new path\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use DocWrite to save an edited document to a different file: pass the source path (opened with DocRead), an `out` path, and an id-based patch. The source is left unchanged.\",\n\t\t\t\"Keep the patch minimal and scan before you write: prefer a DocRead readonly:true glimpse to plan the change, and avoid re-running a full writable DocRead between writes — it auto-extracts the source and is token-heavy.\",\n\t\t],\n\t\tparameters: docWriteSchema,\n\t\tasync execute(_toolCallId, { path, out, patch }: DocWriteToolInput, signal?: AbortSignal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tconst absolutePath = resolveReadPath(path, cwd);\n\t\t\tconst outPath = resolveToCwd(out, cwd);\n\n\t\t\treturn withFileMutationQueue(outPath, async () => {\n\t\t\t\tawait fsMkdir(dirname(outPath), { recursive: true });\n\t\t\t\ttry {\n\t\t\t\t\tawait reconstructDocument(absolutePath, toPatch(patch), outPath, cwd, signal, {\n\t\t\t\t\t\ttimeoutSecs: options?.timeoutSecs,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\tif (err instanceof StalePatchError) {\n\t\t\t\t\t\t// The source was rewritten out-of-band: the cache auto-refreshed\n\t\t\t\t\t\t// but the patch targets ids that no longer exist. Surface the\n\t\t\t\t\t\t// current structure so the agent can re-issue without a DocRead.\n\t\t\t\t\t\tthrow new Error(`${err.message}\\n\\n${renderEnvelopeText(err.envelope, false)}`);\n\t\t\t\t\t}\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\ttext: `Wrote ${patch.length} op${patch.length === 1 ? \"\" : \"s\"} from ${path} to ${out}.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { ops: patch.length, out },\n\t\t\t\t};\n\t\t\t});\n\t\t},\n\t\trenderCall(args, _theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatDocWriteCall(args));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, _options, _theme, context) {\n\t\t\tif (!context.isError) {\n\t\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\t\tcomponent.clear();\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tconst output = (result.content as Array<{ type: string; text?: string }>)\n\t\t\t\t.filter((c) => c.type === \"text\")\n\t\t\t\t.map((c) => c.text || \"\")\n\t\t\t\t.join(\"\\n\");\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(output ? `\\n${appTheme.fg(\"error\", output)}` : \"\");\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createDocWriteTool(cwd: string, options?: DocWriteToolOptions): AgentTool<typeof docWriteSchema> {\n\treturn wrapToolDefinition(createDocWriteToolDefinition(cwd, options));\n}\n"]}
@@ -2,10 +2,10 @@
2
2
  * Structured document tools (DocRead/DocEdit/DocWrite/DocScan/DocGrep/DocPeek),
3
3
  * backed by the `filetools` document engine. See docs/doc-tools-flow.md.
4
4
  */
5
- export { createDocEditTool, createDocEditToolDefinition, type DocEditToolDetails, type DocEditToolInput, type DocEditToolOptions, } from "./docedit.js";
6
- export { createDocGrepTool, createDocGrepToolDefinition, type DocGrepToolDetails, type DocGrepToolInput, type DocGrepToolOptions, } from "./docgrep.js";
7
- export { createDocPeekTool, createDocPeekToolDefinition, type DocPeekToolDetails, type DocPeekToolInput, type DocPeekToolOptions, } from "./docpeek.js";
8
- export { createDocReadTool, createDocReadToolDefinition, type DocReadToolDetails, type DocReadToolInput, type DocReadToolOptions, } from "./docread.js";
9
- export { createDocScanTool, createDocScanToolDefinition, type DocScanToolDetails, type DocScanToolInput, type DocScanToolOptions, } from "./docscan.js";
10
- export { createDocWriteTool, createDocWriteToolDefinition, type DocWriteToolDetails, type DocWriteToolInput, type DocWriteToolOptions, } from "./docwrite.js";
5
+ export { createDocEditToolDefinition, type DocEditToolOptions, } from "./docedit.js";
6
+ export { createDocGrepToolDefinition, type DocGrepToolOptions, } from "./docgrep.js";
7
+ export { createDocPeekToolDefinition, type DocPeekToolOptions, } from "./docpeek.js";
8
+ export { createDocReadToolDefinition, type DocReadToolOptions, } from "./docread.js";
9
+ export { createDocScanToolDefinition, type DocScanToolOptions, } from "./docscan.js";
10
+ export { createDocWriteToolDefinition, type DocWriteToolOptions, } from "./docwrite.js";
11
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/tools/doc/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,kBAAkB,EAClB,4BAA4B,EAC5B,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACxB,MAAM,eAAe,CAAC","sourcesContent":["/**\n * Structured document tools (DocRead/DocEdit/DocWrite/DocScan/DocGrep/DocPeek),\n * backed by the `filetools` document engine. See docs/doc-tools-flow.md.\n */\n\nexport {\n\tcreateDocEditTool,\n\tcreateDocEditToolDefinition,\n\ttype DocEditToolDetails,\n\ttype DocEditToolInput,\n\ttype DocEditToolOptions,\n} from \"./docedit.js\";\nexport {\n\tcreateDocGrepTool,\n\tcreateDocGrepToolDefinition,\n\ttype DocGrepToolDetails,\n\ttype DocGrepToolInput,\n\ttype DocGrepToolOptions,\n} from \"./docgrep.js\";\nexport {\n\tcreateDocPeekTool,\n\tcreateDocPeekToolDefinition,\n\ttype DocPeekToolDetails,\n\ttype DocPeekToolInput,\n\ttype DocPeekToolOptions,\n} from \"./docpeek.js\";\nexport {\n\tcreateDocReadTool,\n\tcreateDocReadToolDefinition,\n\ttype DocReadToolDetails,\n\ttype DocReadToolInput,\n\ttype DocReadToolOptions,\n} from \"./docread.js\";\nexport {\n\tcreateDocScanTool,\n\tcreateDocScanToolDefinition,\n\ttype DocScanToolDetails,\n\ttype DocScanToolInput,\n\ttype DocScanToolOptions,\n} from \"./docscan.js\";\nexport {\n\tcreateDocWriteTool,\n\tcreateDocWriteToolDefinition,\n\ttype DocWriteToolDetails,\n\ttype DocWriteToolInput,\n\ttype DocWriteToolOptions,\n} from \"./docwrite.js\";\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/tools/doc/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,2BAA2B,EAC3B,KAAK,kBAAkB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,2BAA2B,EAC3B,KAAK,kBAAkB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,2BAA2B,EAC3B,KAAK,kBAAkB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,2BAA2B,EAC3B,KAAK,kBAAkB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,2BAA2B,EAC3B,KAAK,kBAAkB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,4BAA4B,EAC5B,KAAK,mBAAmB,GACxB,MAAM,eAAe,CAAC","sourcesContent":["/**\n * Structured document tools (DocRead/DocEdit/DocWrite/DocScan/DocGrep/DocPeek),\n * backed by the `filetools` document engine. See docs/doc-tools-flow.md.\n */\n\nexport {\n\tcreateDocEditToolDefinition,\n\ttype DocEditToolOptions,\n} from \"./docedit.js\";\nexport {\n\tcreateDocGrepToolDefinition,\n\ttype DocGrepToolOptions,\n} from \"./docgrep.js\";\nexport {\n\tcreateDocPeekToolDefinition,\n\ttype DocPeekToolOptions,\n} from \"./docpeek.js\";\nexport {\n\tcreateDocReadToolDefinition,\n\ttype DocReadToolOptions,\n} from \"./docread.js\";\nexport {\n\tcreateDocScanToolDefinition,\n\ttype DocScanToolOptions,\n} from \"./docscan.js\";\nexport {\n\tcreateDocWriteToolDefinition,\n\ttype DocWriteToolOptions,\n} from \"./docwrite.js\";\n"]}
@@ -2,10 +2,10 @@
2
2
  * Structured document tools (DocRead/DocEdit/DocWrite/DocScan/DocGrep/DocPeek),
3
3
  * backed by the `filetools` document engine. See docs/doc-tools-flow.md.
4
4
  */
5
- export { createDocEditTool, createDocEditToolDefinition, } from "./docedit.js";
6
- export { createDocGrepTool, createDocGrepToolDefinition, } from "./docgrep.js";
7
- export { createDocPeekTool, createDocPeekToolDefinition, } from "./docpeek.js";
8
- export { createDocReadTool, createDocReadToolDefinition, } from "./docread.js";
9
- export { createDocScanTool, createDocScanToolDefinition, } from "./docscan.js";
10
- export { createDocWriteTool, createDocWriteToolDefinition, } from "./docwrite.js";
5
+ export { createDocEditToolDefinition, } from "./docedit.js";
6
+ export { createDocGrepToolDefinition, } from "./docgrep.js";
7
+ export { createDocPeekToolDefinition, } from "./docpeek.js";
8
+ export { createDocReadToolDefinition, } from "./docread.js";
9
+ export { createDocScanToolDefinition, } from "./docscan.js";
10
+ export { createDocWriteToolDefinition, } from "./docwrite.js";
11
11
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/core/tools/doc/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,iBAAiB,EACjB,2BAA2B,GAI3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,GAI3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,GAI3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,GAI3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,iBAAiB,EACjB,2BAA2B,GAI3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,kBAAkB,EAClB,4BAA4B,GAI5B,MAAM,eAAe,CAAC","sourcesContent":["/**\n * Structured document tools (DocRead/DocEdit/DocWrite/DocScan/DocGrep/DocPeek),\n * backed by the `filetools` document engine. See docs/doc-tools-flow.md.\n */\n\nexport {\n\tcreateDocEditTool,\n\tcreateDocEditToolDefinition,\n\ttype DocEditToolDetails,\n\ttype DocEditToolInput,\n\ttype DocEditToolOptions,\n} from \"./docedit.js\";\nexport {\n\tcreateDocGrepTool,\n\tcreateDocGrepToolDefinition,\n\ttype DocGrepToolDetails,\n\ttype DocGrepToolInput,\n\ttype DocGrepToolOptions,\n} from \"./docgrep.js\";\nexport {\n\tcreateDocPeekTool,\n\tcreateDocPeekToolDefinition,\n\ttype DocPeekToolDetails,\n\ttype DocPeekToolInput,\n\ttype DocPeekToolOptions,\n} from \"./docpeek.js\";\nexport {\n\tcreateDocReadTool,\n\tcreateDocReadToolDefinition,\n\ttype DocReadToolDetails,\n\ttype DocReadToolInput,\n\ttype DocReadToolOptions,\n} from \"./docread.js\";\nexport {\n\tcreateDocScanTool,\n\tcreateDocScanToolDefinition,\n\ttype DocScanToolDetails,\n\ttype DocScanToolInput,\n\ttype DocScanToolOptions,\n} from \"./docscan.js\";\nexport {\n\tcreateDocWriteTool,\n\tcreateDocWriteToolDefinition,\n\ttype DocWriteToolDetails,\n\ttype DocWriteToolInput,\n\ttype DocWriteToolOptions,\n} from \"./docwrite.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/core/tools/doc/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,2BAA2B,GAE3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,2BAA2B,GAE3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,2BAA2B,GAE3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,2BAA2B,GAE3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,2BAA2B,GAE3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,4BAA4B,GAE5B,MAAM,eAAe,CAAC","sourcesContent":["/**\n * Structured document tools (DocRead/DocEdit/DocWrite/DocScan/DocGrep/DocPeek),\n * backed by the `filetools` document engine. See docs/doc-tools-flow.md.\n */\n\nexport {\n\tcreateDocEditToolDefinition,\n\ttype DocEditToolOptions,\n} from \"./docedit.js\";\nexport {\n\tcreateDocGrepToolDefinition,\n\ttype DocGrepToolOptions,\n} from \"./docgrep.js\";\nexport {\n\tcreateDocPeekToolDefinition,\n\ttype DocPeekToolOptions,\n} from \"./docpeek.js\";\nexport {\n\tcreateDocReadToolDefinition,\n\ttype DocReadToolOptions,\n} from \"./docread.js\";\nexport {\n\tcreateDocScanToolDefinition,\n\ttype DocScanToolOptions,\n} from \"./docscan.js\";\nexport {\n\tcreateDocWriteToolDefinition,\n\ttype DocWriteToolOptions,\n} from \"./docwrite.js\";\n"]}
@@ -5,32 +5,6 @@
5
5
  export declare function detectLineEnding(content: string): "\r\n" | "\n";
6
6
  export declare function normalizeToLF(text: string): string;
7
7
  export declare function restoreLineEndings(text: string, ending: "\r\n" | "\n"): string;
8
- /**
9
- * Normalize text for fuzzy matching. Applies progressive transformations:
10
- * - Normalize line endings to LF
11
- * - Strip trailing whitespace from each line
12
- * - Normalize tabs to spaces (2 spaces per tab)
13
- * - Collapse multiple spaces to single space
14
- * - Normalize smart quotes to ASCII equivalents
15
- * - Normalize Unicode dashes/hyphens to ASCII hyphen
16
- * - Normalize special Unicode spaces to regular space
17
- */
18
- export declare function normalizeForFuzzyMatch(text: string): string;
19
- export interface FuzzyMatchResult {
20
- /** Whether a match was found */
21
- found: boolean;
22
- /** The index where the match starts (in the content that should be used for replacement) */
23
- index: number;
24
- /** Length of the matched text */
25
- matchLength: number;
26
- /** Whether fuzzy matching was used (false = exact match) */
27
- usedFuzzyMatch: boolean;
28
- /**
29
- * The content to use for replacement operations.
30
- * When exact match: original content. When fuzzy match: normalized content.
31
- */
32
- contentForReplacement: string;
33
- }
34
8
  export interface Edit {
35
9
  oldText: string;
36
10
  newText: string;
@@ -44,13 +18,6 @@ export interface AppliedEditsResult {
44
18
  baseContent: string;
45
19
  newContent: string;
46
20
  }
47
- /**
48
- * Find oldText in content, trying exact match first, then fuzzy match.
49
- * When fuzzy matching is used, the returned contentForReplacement is the
50
- * fuzzy-normalized version of the content (trailing whitespace stripped,
51
- * Unicode quotes/dashes normalized to ASCII).
52
- */
53
- export declare function fuzzyFindText(content: string, oldText: string): FuzzyMatchResult;
54
21
  /** Strip UTF-8 BOM if present, return both the BOM (if any) and the text without it */
55
22
  export declare function stripBom(content: string): {
56
23
  bom: string;
@@ -85,9 +52,4 @@ export interface EditDiffError {
85
52
  * Used for preview rendering in the TUI before the tool executes.
86
53
  */
87
54
  export declare function computeEditsDiff(path: string, edits: Edit[], cwd: string): Promise<EditDiffResult | EditDiffError>;
88
- /**
89
- * Compute the diff for a single edit operation without applying it.
90
- * Kept as a convenience wrapper for single-edit callers.
91
- */
92
- export declare function computeEditDiff(path: string, oldText: string, newText: string, cwd: string): Promise<EditDiffResult | EditDiffError>;
93
55
  //# sourceMappingURL=edit-diff.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"edit-diff.d.ts","sourceRoot":"","sources":["../../../src/core/tools/edit-diff.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM/D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAE9E;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA+C3D;AAED,MAAM,WAAW,gBAAgB;IAChC,gCAAgC;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,4FAA4F;IAC5F,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,cAAc,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,qBAAqB,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,IAAI;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AASD,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAsChF;AAED,uFAAuF;AACvF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAEvE;AAsHD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC5C,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,IAAI,EAAE,EACb,IAAI,EAAE,MAAM,GACV,kBAAkB,CA2FpB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CACjC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,YAAY,SAAI,GACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAuHxD;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACrC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,IAAI,EAAE,EACb,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,cAAc,GAAG,aAAa,CAAC,CAyBzC;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,cAAc,GAAG,aAAa,CAAC,CAEzC","sourcesContent":["/**\n * Shared diff computation utilities for the edit tool.\n * Used by both edit.ts (for execution) and tool-execution.ts (for preview rendering).\n */\n\nimport * as Diff from \"diff\";\nimport { constants } from \"fs\";\nimport { access, readFile } from \"fs/promises\";\nimport { resolveToCwd } from \"./path-utils.js\";\n\n/** Cache for normalized text to avoid redundant processing. Max 100 entries. */\nconst normalizeCache = new Map<string, string>();\nconst MAX_CACHE_SIZE = 100;\n\nexport function detectLineEnding(content: string): \"\\r\\n\" | \"\\n\" {\n\tconst crlfIdx = content.indexOf(\"\\r\\n\");\n\tconst lfIdx = content.indexOf(\"\\n\");\n\tif (lfIdx === -1) return \"\\n\";\n\tif (crlfIdx === -1) return \"\\n\";\n\treturn crlfIdx < lfIdx ? \"\\r\\n\" : \"\\n\";\n}\n\nexport function normalizeToLF(text: string): string {\n\treturn text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n}\n\nexport function restoreLineEndings(text: string, ending: \"\\r\\n\" | \"\\n\"): string {\n\treturn ending === \"\\r\\n\" ? text.replace(/\\n/g, \"\\r\\n\") : text;\n}\n\n/**\n * Normalize text for fuzzy matching. Applies progressive transformations:\n * - Normalize line endings to LF\n * - Strip trailing whitespace from each line\n * - Normalize tabs to spaces (2 spaces per tab)\n * - Collapse multiple spaces to single space\n * - Normalize smart quotes to ASCII equivalents\n * - Normalize Unicode dashes/hyphens to ASCII hyphen\n * - Normalize special Unicode spaces to regular space\n */\nexport function normalizeForFuzzyMatch(text: string): string {\n\t// Check cache first\n\tconst cached = normalizeCache.get(text);\n\tif (cached !== undefined) return cached;\n\n\tconst normalized = text\n\t\t.normalize(\"NFKC\")\n\t\t// Normalize line endings to LF\n\t\t.replace(/\\r\\n/g, \"\\n\")\n\t\t.replace(/\\r/g, \"\\n\")\n\t\t// Strip trailing whitespace per line\n\t\t.split(\"\\n\")\n\t\t.map((line) => {\n\t\t\t// Normalize tabs to 2 spaces\n\t\t\tlet normalized = line.replace(/\\t/g, \" \");\n\t\t\t// Collapse multiple spaces to single space (but preserve leading indentation pattern)\n\t\t\t// Only collapse spaces that are NOT at the start of the line (indentation)\n\t\t\tconst leadingSpaces = normalized.match(/^(\\s*)/)?.[1] ?? \"\";\n\t\t\tconst rest = normalized.slice(leadingSpaces.length);\n\t\t\tnormalized = leadingSpaces + rest.replace(/ {2,}/g, \" \");\n\t\t\treturn normalized.trimEnd();\n\t\t})\n\t\t.join(\"\\n\")\n\t\t// Smart single quotes → '\n\t\t.replace(/[\\u2018\\u2019\\u201A\\u201B]/g, \"'\")\n\t\t// Smart double quotes → \"\n\t\t.replace(/[\\u201C\\u201D\\u201E\\u201F]/g, '\"')\n\t\t// Various dashes/hyphens → -\n\t\t// U+2010 hyphen, U+2011 non-breaking hyphen, U+2012 figure dash,\n\t\t// U+2013 en-dash, U+2014 em-dash, U+2015 horizontal bar, U+2212 minus\n\t\t.replace(/[\\u2010\\u2011\\u2012\\u2013\\u2014\\u2015\\u2212]/g, \"-\")\n\t\t// Special spaces → regular space\n\t\t// U+00A0 NBSP, U+2002-U+200A various spaces, U+202F narrow NBSP,\n\t\t// U+205F medium math space, U+3000 ideographic space\n\t\t.replace(/[\\u00A0\\u2002-\\u200A\\u202F\\u205F\\u3000]/g, \" \");\n\n\t// Cache the result (with size limit)\n\tif (normalizeCache.size >= MAX_CACHE_SIZE) {\n\t\t// Remove oldest entry (first key)\n\t\tconst firstKey = normalizeCache.keys().next().value;\n\t\tif (firstKey !== undefined) {\n\t\t\tnormalizeCache.delete(firstKey);\n\t\t}\n\t}\n\tnormalizeCache.set(text, normalized);\n\n\treturn normalized;\n}\n\nexport interface FuzzyMatchResult {\n\t/** Whether a match was found */\n\tfound: boolean;\n\t/** The index where the match starts (in the content that should be used for replacement) */\n\tindex: number;\n\t/** Length of the matched text */\n\tmatchLength: number;\n\t/** Whether fuzzy matching was used (false = exact match) */\n\tusedFuzzyMatch: boolean;\n\t/**\n\t * The content to use for replacement operations.\n\t * When exact match: original content. When fuzzy match: normalized content.\n\t */\n\tcontentForReplacement: string;\n}\n\nexport interface Edit {\n\toldText: string;\n\tnewText: string;\n\t/**\n\t * When true, replace every occurrence of oldText instead of requiring it to\n\t * be unique. Default (false/undefined) keeps the uniqueness guardrail.\n\t */\n\treplaceAll?: boolean;\n}\n\ninterface MatchedEdit {\n\teditIndex: number;\n\tmatchIndex: number;\n\tmatchLength: number;\n\tnewText: string;\n}\n\nexport interface AppliedEditsResult {\n\tbaseContent: string;\n\tnewContent: string;\n}\n\n/**\n * Find oldText in content, trying exact match first, then fuzzy match.\n * When fuzzy matching is used, the returned contentForReplacement is the\n * fuzzy-normalized version of the content (trailing whitespace stripped,\n * Unicode quotes/dashes normalized to ASCII).\n */\nexport function fuzzyFindText(content: string, oldText: string): FuzzyMatchResult {\n\t// Try exact match first\n\tconst exactIndex = content.indexOf(oldText);\n\tif (exactIndex !== -1) {\n\t\treturn {\n\t\t\tfound: true,\n\t\t\tindex: exactIndex,\n\t\t\tmatchLength: oldText.length,\n\t\t\tusedFuzzyMatch: false,\n\t\t\tcontentForReplacement: content,\n\t\t};\n\t}\n\n\t// Try fuzzy match - work entirely in normalized space\n\tconst fuzzyContent = normalizeForFuzzyMatch(content);\n\tconst fuzzyOldText = normalizeForFuzzyMatch(oldText);\n\tconst fuzzyIndex = fuzzyContent.indexOf(fuzzyOldText);\n\n\tif (fuzzyIndex === -1) {\n\t\treturn {\n\t\t\tfound: false,\n\t\t\tindex: -1,\n\t\t\tmatchLength: 0,\n\t\t\tusedFuzzyMatch: false,\n\t\t\tcontentForReplacement: content,\n\t\t};\n\t}\n\n\t// When fuzzy matching, we work in the normalized space for replacement.\n\t// This means the output will have normalized whitespace/quotes/dashes,\n\t// which is acceptable since we're fixing minor formatting differences anyway.\n\treturn {\n\t\tfound: true,\n\t\tindex: fuzzyIndex,\n\t\tmatchLength: fuzzyOldText.length,\n\t\tusedFuzzyMatch: true,\n\t\tcontentForReplacement: fuzzyContent,\n\t};\n}\n\n/** Strip UTF-8 BOM if present, return both the BOM (if any) and the text without it */\nexport function stripBom(content: string): { bom: string; text: string } {\n\treturn content.startsWith(\"\\uFEFF\") ? { bom: \"\\uFEFF\", text: content.slice(1) } : { bom: \"\", text: content };\n}\n\n/**\n * Collect the start index of every non-overlapping occurrence of needle in\n * haystack. The needle must already be in the same space as haystack (raw for\n * exact matches, fuzzy-normalized when haystack is fuzzy-normalized).\n */\nfunction collectMatchIndices(haystack: string, needle: string): number[] {\n\tconst indices: number[] = [];\n\tif (needle.length === 0) return indices;\n\tlet from = 0;\n\twhile (true) {\n\t\tconst idx = haystack.indexOf(needle, from);\n\t\tif (idx === -1) break;\n\t\tindices.push(idx);\n\t\tfrom = idx + needle.length; // non-overlapping\n\t}\n\treturn indices;\n}\n\n/**\n * Per-line normalization for indentation-tolerant block matching. Applies the\n * same Unicode/space normalization as fuzzy matching, then strips all leading\n * and trailing whitespace so indentation differences are ignored entirely.\n */\nfunction blockNormalizeLine(line: string): string {\n\treturn normalizeForFuzzyMatch(line).trim();\n}\n\ninterface LineBlockMatch {\n\tmatchIndex: number;\n\tmatchLength: number;\n}\n\n/**\n * Indentation-tolerant fallback matcher. Compares oldText against content line\n * by line, ignoring each line's leading/trailing whitespace (and Unicode\n * formatting). Returns character spans in `content` for every block whose\n * trimmed lines equal the trimmed oldText lines. Replacement still happens in\n * the original content space, so surrounding formatting is preserved.\n */\nfunction findLineBlockMatches(content: string, oldText: string): LineBlockMatch[] {\n\tconst hadTrailingNewline = oldText.endsWith(\"\\n\");\n\tconst oldLines = oldText.split(\"\\n\");\n\tif (hadTrailingNewline) oldLines.pop();\n\tif (oldLines.length === 0) return [];\n\tconst trimmedOld = oldLines.map(blockNormalizeLine);\n\n\tconst contentLines = content.split(\"\\n\");\n\tconst k = trimmedOld.length;\n\tif (k > contentLines.length) return [];\n\n\t// Char offset of each line start within content.\n\tconst offsets = new Array<number>(contentLines.length);\n\tlet acc = 0;\n\tfor (let i = 0; i < contentLines.length; i++) {\n\t\toffsets[i] = acc;\n\t\tacc += contentLines[i].length + 1; // + newline\n\t}\n\n\tconst matches: LineBlockMatch[] = [];\n\tfor (let i = 0; i + k <= contentLines.length; i++) {\n\t\tlet ok = true;\n\t\tfor (let j = 0; j < k; j++) {\n\t\t\tif (blockNormalizeLine(contentLines[i + j]) !== trimmedOld[j]) {\n\t\t\t\tok = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!ok) continue;\n\t\tconst matchIndex = offsets[i];\n\t\tlet matchLength = 0;\n\t\tfor (let j = 0; j < k; j++) matchLength += contentLines[i + j].length + (j < k - 1 ? 1 : 0);\n\t\t// Include the trailing newline when oldText carried one and a line follows the block.\n\t\tif (hadTrailingNewline && i + k < contentLines.length) matchLength += 1;\n\t\tmatches.push({ matchIndex, matchLength });\n\t}\n\treturn matches;\n}\n\nfunction getNotFoundError(path: string, editIndex: number, totalEdits: number): Error {\n\tif (totalEdits === 1) {\n\t\treturn new Error(\n\t\t\t`Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n\t\t);\n\t}\n\treturn new Error(\n\t\t`Could not find edits[${editIndex}] in ${path}. The oldText must match exactly including all whitespace and newlines.`,\n\t);\n}\n\nfunction getDuplicateError(path: string, editIndex: number, totalEdits: number, occurrences: number): Error {\n\tif (totalEdits === 1) {\n\t\treturn new Error(\n\t\t\t`Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n\t\t);\n\t}\n\treturn new Error(\n\t\t`Found ${occurrences} occurrences of edits[${editIndex}] in ${path}. Each oldText must be unique. Please provide more context to make it unique.`,\n\t);\n}\n\nfunction getEmptyOldTextError(path: string, editIndex: number, totalEdits: number): Error {\n\tif (totalEdits === 1) {\n\t\treturn new Error(`oldText must not be empty in ${path}.`);\n\t}\n\treturn new Error(`edits[${editIndex}].oldText must not be empty in ${path}.`);\n}\n\nfunction getNoChangeError(path: string, totalEdits: number): Error {\n\tif (totalEdits === 1) {\n\t\treturn new Error(\n\t\t\t`No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n\t\t);\n\t}\n\treturn new Error(`No changes made to ${path}. The replacements produced identical content.`);\n}\n\n/**\n * Apply one or more exact-text replacements to LF-normalized content.\n *\n * All edits are matched against the same original content. Replacements are\n * then applied in reverse order so offsets remain stable. If any edit needs\n * fuzzy matching, the operation runs in fuzzy-normalized content space to\n * preserve current single-edit behavior.\n */\nexport function applyEditsToNormalizedContent(\n\tnormalizedContent: string,\n\tedits: Edit[],\n\tpath: string,\n): AppliedEditsResult {\n\tconst normalizedEdits = edits.map((edit) => ({\n\t\toldText: normalizeToLF(edit.oldText),\n\t\tnewText: normalizeToLF(edit.newText),\n\t\treplaceAll: edit.replaceAll === true,\n\t}));\n\n\tfor (let i = 0; i < normalizedEdits.length; i++) {\n\t\tif (normalizedEdits[i].oldText.length === 0) {\n\t\t\tthrow getEmptyOldTextError(path, i, normalizedEdits.length);\n\t\t}\n\t}\n\n\tconst initialMatches = normalizedEdits.map((edit) => fuzzyFindText(normalizedContent, edit.oldText));\n\tconst baseContent = initialMatches.some((match) => match.usedFuzzyMatch)\n\t\t? normalizeForFuzzyMatch(normalizedContent)\n\t\t: normalizedContent;\n\n\tconst matchedEdits: MatchedEdit[] = [];\n\tfor (let i = 0; i < normalizedEdits.length; i++) {\n\t\tconst edit = normalizedEdits[i];\n\t\tconst matchResult = fuzzyFindText(baseContent, edit.oldText);\n\n\t\t// Resolve the match spans for this edit, all in baseContent space.\n\t\t// Tier 1/2: exact then fuzzy (fuzzyFindText). Tier 3: indentation-tolerant\n\t\t// line-block fallback, only when both fail — the common cause of edit\n\t\t// failures is leading-whitespace drift that survives fuzzy normalization.\n\t\tlet spans: LineBlockMatch[];\n\t\tif (matchResult.found) {\n\t\t\tconst needle = matchResult.usedFuzzyMatch ? normalizeForFuzzyMatch(edit.oldText) : edit.oldText;\n\t\t\tspans = collectMatchIndices(baseContent, needle).map((matchIndex) => ({\n\t\t\t\tmatchIndex,\n\t\t\t\tmatchLength: matchResult.matchLength,\n\t\t\t}));\n\t\t} else {\n\t\t\tspans = findLineBlockMatches(baseContent, edit.oldText);\n\t\t\tif (spans.length === 0) {\n\t\t\t\tthrow getNotFoundError(path, i, normalizedEdits.length);\n\t\t\t}\n\t\t}\n\n\t\tif (edit.replaceAll) {\n\t\t\t// Replace every occurrence so the shared reverse-order applier rewrites them all.\n\t\t\tfor (const span of spans) {\n\t\t\t\tmatchedEdits.push({\n\t\t\t\t\teditIndex: i,\n\t\t\t\t\tmatchIndex: span.matchIndex,\n\t\t\t\t\tmatchLength: span.matchLength,\n\t\t\t\t\tnewText: edit.newText,\n\t\t\t\t});\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (spans.length > 1) {\n\t\t\tthrow getDuplicateError(path, i, normalizedEdits.length, spans.length);\n\t\t}\n\n\t\tmatchedEdits.push({\n\t\t\teditIndex: i,\n\t\t\tmatchIndex: spans[0].matchIndex,\n\t\t\tmatchLength: spans[0].matchLength,\n\t\t\tnewText: edit.newText,\n\t\t});\n\t}\n\n\tmatchedEdits.sort((a, b) => a.matchIndex - b.matchIndex);\n\tfor (let i = 1; i < matchedEdits.length; i++) {\n\t\tconst previous = matchedEdits[i - 1];\n\t\tconst current = matchedEdits[i];\n\t\tif (previous.matchIndex + previous.matchLength > current.matchIndex) {\n\t\t\tthrow new Error(\n\t\t\t\t`edits[${previous.editIndex}] and edits[${current.editIndex}] overlap in ${path}. Merge them into one edit or target disjoint regions.`,\n\t\t\t);\n\t\t}\n\t}\n\n\tlet newContent = baseContent;\n\tfor (let i = matchedEdits.length - 1; i >= 0; i--) {\n\t\tconst edit = matchedEdits[i];\n\t\tnewContent =\n\t\t\tnewContent.substring(0, edit.matchIndex) +\n\t\t\tedit.newText +\n\t\t\tnewContent.substring(edit.matchIndex + edit.matchLength);\n\t}\n\n\tif (baseContent === newContent) {\n\t\tthrow getNoChangeError(path, normalizedEdits.length);\n\t}\n\n\treturn { baseContent, newContent };\n}\n\n/**\n * Generate a unified diff string with line numbers and context.\n * Returns both the diff string and the first changed line number (in the new file).\n */\nexport function generateDiffString(\n\toldContent: string,\n\tnewContent: string,\n\tcontextLines = 4,\n): { diff: string; firstChangedLine: number | undefined } {\n\tconst parts = Diff.diffLines(oldContent, newContent);\n\tconst output: string[] = [];\n\n\tconst oldLines = oldContent.split(\"\\n\");\n\tconst newLines = newContent.split(\"\\n\");\n\tconst maxLineNum = Math.max(oldLines.length, newLines.length);\n\tconst lineNumWidth = String(maxLineNum).length;\n\n\tlet oldLineNum = 1;\n\tlet newLineNum = 1;\n\tlet lastWasChange = false;\n\tlet firstChangedLine: number | undefined;\n\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i];\n\t\tconst raw = part.value.split(\"\\n\");\n\t\tif (raw[raw.length - 1] === \"\") {\n\t\t\traw.pop();\n\t\t}\n\n\t\tif (part.added || part.removed) {\n\t\t\t// Capture the first changed line (in the new file)\n\t\t\tif (firstChangedLine === undefined) {\n\t\t\t\tfirstChangedLine = newLineNum;\n\t\t\t}\n\n\t\t\t// Show the change\n\t\t\tfor (const line of raw) {\n\t\t\t\tif (part.added) {\n\t\t\t\t\tconst lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`+${lineNum} ${line}`);\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t} else {\n\t\t\t\t\t// removed\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`-${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastWasChange = true;\n\t\t} else {\n\t\t\t// Context lines - only show a few before/after changes\n\t\t\tconst nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\t\t\tconst hasLeadingChange = lastWasChange;\n\t\t\tconst hasTrailingChange = nextPartIsChange;\n\n\t\t\tif (hasLeadingChange && hasTrailingChange) {\n\t\t\t\tif (raw.length <= contextLines * 2) {\n\t\t\t\t\tfor (const line of raw) {\n\t\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\t\toldLineNum++;\n\t\t\t\t\t\tnewLineNum++;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst leadingLines = raw.slice(0, contextLines);\n\t\t\t\t\tconst trailingLines = raw.slice(raw.length - contextLines);\n\t\t\t\t\tconst skippedLines = raw.length - leadingLines.length - trailingLines.length;\n\n\t\t\t\t\tfor (const line of leadingLines) {\n\t\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\t\toldLineNum++;\n\t\t\t\t\t\tnewLineNum++;\n\t\t\t\t\t}\n\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t\toldLineNum += skippedLines;\n\t\t\t\t\tnewLineNum += skippedLines;\n\n\t\t\t\t\tfor (const line of trailingLines) {\n\t\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\t\toldLineNum++;\n\t\t\t\t\t\tnewLineNum++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (hasLeadingChange) {\n\t\t\t\tconst shownLines = raw.slice(0, contextLines);\n\t\t\t\tconst skippedLines = raw.length - shownLines.length;\n\n\t\t\t\tfor (const line of shownLines) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\n\t\t\t\tif (skippedLines > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t\toldLineNum += skippedLines;\n\t\t\t\t\tnewLineNum += skippedLines;\n\t\t\t\t}\n\t\t\t} else if (hasTrailingChange) {\n\t\t\t\tconst skippedLines = Math.max(0, raw.length - contextLines);\n\t\t\t\tif (skippedLines > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t\toldLineNum += skippedLines;\n\t\t\t\t\tnewLineNum += skippedLines;\n\t\t\t\t}\n\n\t\t\t\tfor (const line of raw.slice(skippedLines)) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Skip these context lines entirely\n\t\t\t\toldLineNum += raw.length;\n\t\t\t\tnewLineNum += raw.length;\n\t\t\t}\n\n\t\t\tlastWasChange = false;\n\t\t}\n\t}\n\n\treturn { diff: output.join(\"\\n\"), firstChangedLine };\n}\n\nexport interface EditDiffResult {\n\tdiff: string;\n\tfirstChangedLine: number | undefined;\n}\n\nexport interface EditDiffError {\n\terror: string;\n}\n\n/**\n * Compute the diff for one or more edit operations without applying them.\n * Used for preview rendering in the TUI before the tool executes.\n */\nexport async function computeEditsDiff(\n\tpath: string,\n\tedits: Edit[],\n\tcwd: string,\n): Promise<EditDiffResult | EditDiffError> {\n\tconst absolutePath = resolveToCwd(path, cwd);\n\n\ttry {\n\t\t// Check if file exists and is readable\n\t\ttry {\n\t\t\tawait access(absolutePath, constants.R_OK);\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error && \"code\" in error ? `Error code: ${error.code}` : String(error);\n\t\t\treturn { error: `Could not edit file: ${path}. ${errorMessage}.` };\n\t\t}\n\n\t\t// Read the file\n\t\tconst rawContent = await readFile(absolutePath, \"utf-8\");\n\n\t\t// Strip BOM before matching (LLM won't include invisible BOM in oldText)\n\t\tconst { text: content } = stripBom(rawContent);\n\t\tconst normalizedContent = normalizeToLF(content);\n\t\tconst { baseContent, newContent } = applyEditsToNormalizedContent(normalizedContent, edits, path);\n\n\t\t// Generate the diff\n\t\treturn generateDiffString(baseContent, newContent);\n\t} catch (err) {\n\t\treturn { error: err instanceof Error ? err.message : String(err) };\n\t}\n}\n\n/**\n * Compute the diff for a single edit operation without applying it.\n * Kept as a convenience wrapper for single-edit callers.\n */\nexport async function computeEditDiff(\n\tpath: string,\n\toldText: string,\n\tnewText: string,\n\tcwd: string,\n): Promise<EditDiffResult | EditDiffError> {\n\treturn computeEditsDiff(path, [{ oldText, newText }], cwd);\n}\n"]}
1
+ {"version":3,"file":"edit-diff.d.ts","sourceRoot":"","sources":["../../../src/core/tools/edit-diff.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM/D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAE9E;AA6ED,MAAM,WAAW,IAAI;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AASD,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACnB;AAgDD,uFAAuF;AACvF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAEvE;AAsHD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC5C,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,IAAI,EAAE,EACb,IAAI,EAAE,MAAM,GACV,kBAAkB,CA2FpB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CACjC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,YAAY,SAAI,GACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAuHxD;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACrC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,IAAI,EAAE,EACb,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,cAAc,GAAG,aAAa,CAAC,CAyBzC","sourcesContent":["/**\n * Shared diff computation utilities for the edit tool.\n * Used by both edit.ts (for execution) and tool-execution.ts (for preview rendering).\n */\n\nimport * as Diff from \"diff\";\nimport { constants } from \"fs\";\nimport { access, readFile } from \"fs/promises\";\nimport { resolveToCwd } from \"./path-utils.js\";\n\n/** Cache for normalized text to avoid redundant processing. Max 100 entries. */\nconst normalizeCache = new Map<string, string>();\nconst MAX_CACHE_SIZE = 100;\n\nexport function detectLineEnding(content: string): \"\\r\\n\" | \"\\n\" {\n\tconst crlfIdx = content.indexOf(\"\\r\\n\");\n\tconst lfIdx = content.indexOf(\"\\n\");\n\tif (lfIdx === -1) return \"\\n\";\n\tif (crlfIdx === -1) return \"\\n\";\n\treturn crlfIdx < lfIdx ? \"\\r\\n\" : \"\\n\";\n}\n\nexport function normalizeToLF(text: string): string {\n\treturn text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n}\n\nexport function restoreLineEndings(text: string, ending: \"\\r\\n\" | \"\\n\"): string {\n\treturn ending === \"\\r\\n\" ? text.replace(/\\n/g, \"\\r\\n\") : text;\n}\n\n/**\n * Normalize text for fuzzy matching. Applies progressive transformations:\n * - Normalize line endings to LF\n * - Strip trailing whitespace from each line\n * - Normalize tabs to spaces (2 spaces per tab)\n * - Collapse multiple spaces to single space\n * - Normalize smart quotes to ASCII equivalents\n * - Normalize Unicode dashes/hyphens to ASCII hyphen\n * - Normalize special Unicode spaces to regular space\n */\nfunction normalizeForFuzzyMatch(text: string): string {\n\t// Check cache first\n\tconst cached = normalizeCache.get(text);\n\tif (cached !== undefined) return cached;\n\n\tconst normalized = text\n\t\t.normalize(\"NFKC\")\n\t\t// Normalize line endings to LF\n\t\t.replace(/\\r\\n/g, \"\\n\")\n\t\t.replace(/\\r/g, \"\\n\")\n\t\t// Strip trailing whitespace per line\n\t\t.split(\"\\n\")\n\t\t.map((line) => {\n\t\t\t// Normalize tabs to 2 spaces\n\t\t\tlet normalized = line.replace(/\\t/g, \" \");\n\t\t\t// Collapse multiple spaces to single space (but preserve leading indentation pattern)\n\t\t\t// Only collapse spaces that are NOT at the start of the line (indentation)\n\t\t\tconst leadingSpaces = normalized.match(/^(\\s*)/)?.[1] ?? \"\";\n\t\t\tconst rest = normalized.slice(leadingSpaces.length);\n\t\t\tnormalized = leadingSpaces + rest.replace(/ {2,}/g, \" \");\n\t\t\treturn normalized.trimEnd();\n\t\t})\n\t\t.join(\"\\n\")\n\t\t// Smart single quotes → '\n\t\t.replace(/[\\u2018\\u2019\\u201A\\u201B]/g, \"'\")\n\t\t// Smart double quotes → \"\n\t\t.replace(/[\\u201C\\u201D\\u201E\\u201F]/g, '\"')\n\t\t// Various dashes/hyphens → -\n\t\t// U+2010 hyphen, U+2011 non-breaking hyphen, U+2012 figure dash,\n\t\t// U+2013 en-dash, U+2014 em-dash, U+2015 horizontal bar, U+2212 minus\n\t\t.replace(/[\\u2010\\u2011\\u2012\\u2013\\u2014\\u2015\\u2212]/g, \"-\")\n\t\t// Special spaces → regular space\n\t\t// U+00A0 NBSP, U+2002-U+200A various spaces, U+202F narrow NBSP,\n\t\t// U+205F medium math space, U+3000 ideographic space\n\t\t.replace(/[\\u00A0\\u2002-\\u200A\\u202F\\u205F\\u3000]/g, \" \");\n\n\t// Cache the result (with size limit)\n\tif (normalizeCache.size >= MAX_CACHE_SIZE) {\n\t\t// Remove oldest entry (first key)\n\t\tconst firstKey = normalizeCache.keys().next().value;\n\t\tif (firstKey !== undefined) {\n\t\t\tnormalizeCache.delete(firstKey);\n\t\t}\n\t}\n\tnormalizeCache.set(text, normalized);\n\n\treturn normalized;\n}\n\ninterface FuzzyMatchResult {\n\t/** Whether a match was found */\n\tfound: boolean;\n\t/** The index where the match starts (in the content that should be used for replacement) */\n\tindex: number;\n\t/** Length of the matched text */\n\tmatchLength: number;\n\t/** Whether fuzzy matching was used (false = exact match) */\n\tusedFuzzyMatch: boolean;\n\t/**\n\t * The content to use for replacement operations.\n\t * When exact match: original content. When fuzzy match: normalized content.\n\t */\n\tcontentForReplacement: string;\n}\n\nexport interface Edit {\n\toldText: string;\n\tnewText: string;\n\t/**\n\t * When true, replace every occurrence of oldText instead of requiring it to\n\t * be unique. Default (false/undefined) keeps the uniqueness guardrail.\n\t */\n\treplaceAll?: boolean;\n}\n\ninterface MatchedEdit {\n\teditIndex: number;\n\tmatchIndex: number;\n\tmatchLength: number;\n\tnewText: string;\n}\n\nexport interface AppliedEditsResult {\n\tbaseContent: string;\n\tnewContent: string;\n}\n\n/**\n * Find oldText in content, trying exact match first, then fuzzy match.\n * When fuzzy matching is used, the returned contentForReplacement is the\n * fuzzy-normalized version of the content (trailing whitespace stripped,\n * Unicode quotes/dashes normalized to ASCII).\n */\nfunction fuzzyFindText(content: string, oldText: string): FuzzyMatchResult {\n\t// Try exact match first\n\tconst exactIndex = content.indexOf(oldText);\n\tif (exactIndex !== -1) {\n\t\treturn {\n\t\t\tfound: true,\n\t\t\tindex: exactIndex,\n\t\t\tmatchLength: oldText.length,\n\t\t\tusedFuzzyMatch: false,\n\t\t\tcontentForReplacement: content,\n\t\t};\n\t}\n\n\t// Try fuzzy match - work entirely in normalized space\n\tconst fuzzyContent = normalizeForFuzzyMatch(content);\n\tconst fuzzyOldText = normalizeForFuzzyMatch(oldText);\n\tconst fuzzyIndex = fuzzyContent.indexOf(fuzzyOldText);\n\n\tif (fuzzyIndex === -1) {\n\t\treturn {\n\t\t\tfound: false,\n\t\t\tindex: -1,\n\t\t\tmatchLength: 0,\n\t\t\tusedFuzzyMatch: false,\n\t\t\tcontentForReplacement: content,\n\t\t};\n\t}\n\n\t// When fuzzy matching, we work in the normalized space for replacement.\n\t// This means the output will have normalized whitespace/quotes/dashes,\n\t// which is acceptable since we're fixing minor formatting differences anyway.\n\treturn {\n\t\tfound: true,\n\t\tindex: fuzzyIndex,\n\t\tmatchLength: fuzzyOldText.length,\n\t\tusedFuzzyMatch: true,\n\t\tcontentForReplacement: fuzzyContent,\n\t};\n}\n\n/** Strip UTF-8 BOM if present, return both the BOM (if any) and the text without it */\nexport function stripBom(content: string): { bom: string; text: string } {\n\treturn content.startsWith(\"\\uFEFF\") ? { bom: \"\\uFEFF\", text: content.slice(1) } : { bom: \"\", text: content };\n}\n\n/**\n * Collect the start index of every non-overlapping occurrence of needle in\n * haystack. The needle must already be in the same space as haystack (raw for\n * exact matches, fuzzy-normalized when haystack is fuzzy-normalized).\n */\nfunction collectMatchIndices(haystack: string, needle: string): number[] {\n\tconst indices: number[] = [];\n\tif (needle.length === 0) return indices;\n\tlet from = 0;\n\twhile (true) {\n\t\tconst idx = haystack.indexOf(needle, from);\n\t\tif (idx === -1) break;\n\t\tindices.push(idx);\n\t\tfrom = idx + needle.length; // non-overlapping\n\t}\n\treturn indices;\n}\n\n/**\n * Per-line normalization for indentation-tolerant block matching. Applies the\n * same Unicode/space normalization as fuzzy matching, then strips all leading\n * and trailing whitespace so indentation differences are ignored entirely.\n */\nfunction blockNormalizeLine(line: string): string {\n\treturn normalizeForFuzzyMatch(line).trim();\n}\n\ninterface LineBlockMatch {\n\tmatchIndex: number;\n\tmatchLength: number;\n}\n\n/**\n * Indentation-tolerant fallback matcher. Compares oldText against content line\n * by line, ignoring each line's leading/trailing whitespace (and Unicode\n * formatting). Returns character spans in `content` for every block whose\n * trimmed lines equal the trimmed oldText lines. Replacement still happens in\n * the original content space, so surrounding formatting is preserved.\n */\nfunction findLineBlockMatches(content: string, oldText: string): LineBlockMatch[] {\n\tconst hadTrailingNewline = oldText.endsWith(\"\\n\");\n\tconst oldLines = oldText.split(\"\\n\");\n\tif (hadTrailingNewline) oldLines.pop();\n\tif (oldLines.length === 0) return [];\n\tconst trimmedOld = oldLines.map(blockNormalizeLine);\n\n\tconst contentLines = content.split(\"\\n\");\n\tconst k = trimmedOld.length;\n\tif (k > contentLines.length) return [];\n\n\t// Char offset of each line start within content.\n\tconst offsets = new Array<number>(contentLines.length);\n\tlet acc = 0;\n\tfor (let i = 0; i < contentLines.length; i++) {\n\t\toffsets[i] = acc;\n\t\tacc += contentLines[i].length + 1; // + newline\n\t}\n\n\tconst matches: LineBlockMatch[] = [];\n\tfor (let i = 0; i + k <= contentLines.length; i++) {\n\t\tlet ok = true;\n\t\tfor (let j = 0; j < k; j++) {\n\t\t\tif (blockNormalizeLine(contentLines[i + j]) !== trimmedOld[j]) {\n\t\t\t\tok = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!ok) continue;\n\t\tconst matchIndex = offsets[i];\n\t\tlet matchLength = 0;\n\t\tfor (let j = 0; j < k; j++) matchLength += contentLines[i + j].length + (j < k - 1 ? 1 : 0);\n\t\t// Include the trailing newline when oldText carried one and a line follows the block.\n\t\tif (hadTrailingNewline && i + k < contentLines.length) matchLength += 1;\n\t\tmatches.push({ matchIndex, matchLength });\n\t}\n\treturn matches;\n}\n\nfunction getNotFoundError(path: string, editIndex: number, totalEdits: number): Error {\n\tif (totalEdits === 1) {\n\t\treturn new Error(\n\t\t\t`Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n\t\t);\n\t}\n\treturn new Error(\n\t\t`Could not find edits[${editIndex}] in ${path}. The oldText must match exactly including all whitespace and newlines.`,\n\t);\n}\n\nfunction getDuplicateError(path: string, editIndex: number, totalEdits: number, occurrences: number): Error {\n\tif (totalEdits === 1) {\n\t\treturn new Error(\n\t\t\t`Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n\t\t);\n\t}\n\treturn new Error(\n\t\t`Found ${occurrences} occurrences of edits[${editIndex}] in ${path}. Each oldText must be unique. Please provide more context to make it unique.`,\n\t);\n}\n\nfunction getEmptyOldTextError(path: string, editIndex: number, totalEdits: number): Error {\n\tif (totalEdits === 1) {\n\t\treturn new Error(`oldText must not be empty in ${path}.`);\n\t}\n\treturn new Error(`edits[${editIndex}].oldText must not be empty in ${path}.`);\n}\n\nfunction getNoChangeError(path: string, totalEdits: number): Error {\n\tif (totalEdits === 1) {\n\t\treturn new Error(\n\t\t\t`No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n\t\t);\n\t}\n\treturn new Error(`No changes made to ${path}. The replacements produced identical content.`);\n}\n\n/**\n * Apply one or more exact-text replacements to LF-normalized content.\n *\n * All edits are matched against the same original content. Replacements are\n * then applied in reverse order so offsets remain stable. If any edit needs\n * fuzzy matching, the operation runs in fuzzy-normalized content space to\n * preserve current single-edit behavior.\n */\nexport function applyEditsToNormalizedContent(\n\tnormalizedContent: string,\n\tedits: Edit[],\n\tpath: string,\n): AppliedEditsResult {\n\tconst normalizedEdits = edits.map((edit) => ({\n\t\toldText: normalizeToLF(edit.oldText),\n\t\tnewText: normalizeToLF(edit.newText),\n\t\treplaceAll: edit.replaceAll === true,\n\t}));\n\n\tfor (let i = 0; i < normalizedEdits.length; i++) {\n\t\tif (normalizedEdits[i].oldText.length === 0) {\n\t\t\tthrow getEmptyOldTextError(path, i, normalizedEdits.length);\n\t\t}\n\t}\n\n\tconst initialMatches = normalizedEdits.map((edit) => fuzzyFindText(normalizedContent, edit.oldText));\n\tconst baseContent = initialMatches.some((match) => match.usedFuzzyMatch)\n\t\t? normalizeForFuzzyMatch(normalizedContent)\n\t\t: normalizedContent;\n\n\tconst matchedEdits: MatchedEdit[] = [];\n\tfor (let i = 0; i < normalizedEdits.length; i++) {\n\t\tconst edit = normalizedEdits[i];\n\t\tconst matchResult = fuzzyFindText(baseContent, edit.oldText);\n\n\t\t// Resolve the match spans for this edit, all in baseContent space.\n\t\t// Tier 1/2: exact then fuzzy (fuzzyFindText). Tier 3: indentation-tolerant\n\t\t// line-block fallback, only when both fail — the common cause of edit\n\t\t// failures is leading-whitespace drift that survives fuzzy normalization.\n\t\tlet spans: LineBlockMatch[];\n\t\tif (matchResult.found) {\n\t\t\tconst needle = matchResult.usedFuzzyMatch ? normalizeForFuzzyMatch(edit.oldText) : edit.oldText;\n\t\t\tspans = collectMatchIndices(baseContent, needle).map((matchIndex) => ({\n\t\t\t\tmatchIndex,\n\t\t\t\tmatchLength: matchResult.matchLength,\n\t\t\t}));\n\t\t} else {\n\t\t\tspans = findLineBlockMatches(baseContent, edit.oldText);\n\t\t\tif (spans.length === 0) {\n\t\t\t\tthrow getNotFoundError(path, i, normalizedEdits.length);\n\t\t\t}\n\t\t}\n\n\t\tif (edit.replaceAll) {\n\t\t\t// Replace every occurrence so the shared reverse-order applier rewrites them all.\n\t\t\tfor (const span of spans) {\n\t\t\t\tmatchedEdits.push({\n\t\t\t\t\teditIndex: i,\n\t\t\t\t\tmatchIndex: span.matchIndex,\n\t\t\t\t\tmatchLength: span.matchLength,\n\t\t\t\t\tnewText: edit.newText,\n\t\t\t\t});\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (spans.length > 1) {\n\t\t\tthrow getDuplicateError(path, i, normalizedEdits.length, spans.length);\n\t\t}\n\n\t\tmatchedEdits.push({\n\t\t\teditIndex: i,\n\t\t\tmatchIndex: spans[0].matchIndex,\n\t\t\tmatchLength: spans[0].matchLength,\n\t\t\tnewText: edit.newText,\n\t\t});\n\t}\n\n\tmatchedEdits.sort((a, b) => a.matchIndex - b.matchIndex);\n\tfor (let i = 1; i < matchedEdits.length; i++) {\n\t\tconst previous = matchedEdits[i - 1];\n\t\tconst current = matchedEdits[i];\n\t\tif (previous.matchIndex + previous.matchLength > current.matchIndex) {\n\t\t\tthrow new Error(\n\t\t\t\t`edits[${previous.editIndex}] and edits[${current.editIndex}] overlap in ${path}. Merge them into one edit or target disjoint regions.`,\n\t\t\t);\n\t\t}\n\t}\n\n\tlet newContent = baseContent;\n\tfor (let i = matchedEdits.length - 1; i >= 0; i--) {\n\t\tconst edit = matchedEdits[i];\n\t\tnewContent =\n\t\t\tnewContent.substring(0, edit.matchIndex) +\n\t\t\tedit.newText +\n\t\t\tnewContent.substring(edit.matchIndex + edit.matchLength);\n\t}\n\n\tif (baseContent === newContent) {\n\t\tthrow getNoChangeError(path, normalizedEdits.length);\n\t}\n\n\treturn { baseContent, newContent };\n}\n\n/**\n * Generate a unified diff string with line numbers and context.\n * Returns both the diff string and the first changed line number (in the new file).\n */\nexport function generateDiffString(\n\toldContent: string,\n\tnewContent: string,\n\tcontextLines = 4,\n): { diff: string; firstChangedLine: number | undefined } {\n\tconst parts = Diff.diffLines(oldContent, newContent);\n\tconst output: string[] = [];\n\n\tconst oldLines = oldContent.split(\"\\n\");\n\tconst newLines = newContent.split(\"\\n\");\n\tconst maxLineNum = Math.max(oldLines.length, newLines.length);\n\tconst lineNumWidth = String(maxLineNum).length;\n\n\tlet oldLineNum = 1;\n\tlet newLineNum = 1;\n\tlet lastWasChange = false;\n\tlet firstChangedLine: number | undefined;\n\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i];\n\t\tconst raw = part.value.split(\"\\n\");\n\t\tif (raw[raw.length - 1] === \"\") {\n\t\t\traw.pop();\n\t\t}\n\n\t\tif (part.added || part.removed) {\n\t\t\t// Capture the first changed line (in the new file)\n\t\t\tif (firstChangedLine === undefined) {\n\t\t\t\tfirstChangedLine = newLineNum;\n\t\t\t}\n\n\t\t\t// Show the change\n\t\t\tfor (const line of raw) {\n\t\t\t\tif (part.added) {\n\t\t\t\t\tconst lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`+${lineNum} ${line}`);\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t} else {\n\t\t\t\t\t// removed\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`-${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastWasChange = true;\n\t\t} else {\n\t\t\t// Context lines - only show a few before/after changes\n\t\t\tconst nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\t\t\tconst hasLeadingChange = lastWasChange;\n\t\t\tconst hasTrailingChange = nextPartIsChange;\n\n\t\t\tif (hasLeadingChange && hasTrailingChange) {\n\t\t\t\tif (raw.length <= contextLines * 2) {\n\t\t\t\t\tfor (const line of raw) {\n\t\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\t\toldLineNum++;\n\t\t\t\t\t\tnewLineNum++;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst leadingLines = raw.slice(0, contextLines);\n\t\t\t\t\tconst trailingLines = raw.slice(raw.length - contextLines);\n\t\t\t\t\tconst skippedLines = raw.length - leadingLines.length - trailingLines.length;\n\n\t\t\t\t\tfor (const line of leadingLines) {\n\t\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\t\toldLineNum++;\n\t\t\t\t\t\tnewLineNum++;\n\t\t\t\t\t}\n\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t\toldLineNum += skippedLines;\n\t\t\t\t\tnewLineNum += skippedLines;\n\n\t\t\t\t\tfor (const line of trailingLines) {\n\t\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\t\toldLineNum++;\n\t\t\t\t\t\tnewLineNum++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (hasLeadingChange) {\n\t\t\t\tconst shownLines = raw.slice(0, contextLines);\n\t\t\t\tconst skippedLines = raw.length - shownLines.length;\n\n\t\t\t\tfor (const line of shownLines) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\n\t\t\t\tif (skippedLines > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t\toldLineNum += skippedLines;\n\t\t\t\t\tnewLineNum += skippedLines;\n\t\t\t\t}\n\t\t\t} else if (hasTrailingChange) {\n\t\t\t\tconst skippedLines = Math.max(0, raw.length - contextLines);\n\t\t\t\tif (skippedLines > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t\toldLineNum += skippedLines;\n\t\t\t\t\tnewLineNum += skippedLines;\n\t\t\t\t}\n\n\t\t\t\tfor (const line of raw.slice(skippedLines)) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Skip these context lines entirely\n\t\t\t\toldLineNum += raw.length;\n\t\t\t\tnewLineNum += raw.length;\n\t\t\t}\n\n\t\t\tlastWasChange = false;\n\t\t}\n\t}\n\n\treturn { diff: output.join(\"\\n\"), firstChangedLine };\n}\n\nexport interface EditDiffResult {\n\tdiff: string;\n\tfirstChangedLine: number | undefined;\n}\n\nexport interface EditDiffError {\n\terror: string;\n}\n\n/**\n * Compute the diff for one or more edit operations without applying them.\n * Used for preview rendering in the TUI before the tool executes.\n */\nexport async function computeEditsDiff(\n\tpath: string,\n\tedits: Edit[],\n\tcwd: string,\n): Promise<EditDiffResult | EditDiffError> {\n\tconst absolutePath = resolveToCwd(path, cwd);\n\n\ttry {\n\t\t// Check if file exists and is readable\n\t\ttry {\n\t\t\tawait access(absolutePath, constants.R_OK);\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error && \"code\" in error ? `Error code: ${error.code}` : String(error);\n\t\t\treturn { error: `Could not edit file: ${path}. ${errorMessage}.` };\n\t\t}\n\n\t\t// Read the file\n\t\tconst rawContent = await readFile(absolutePath, \"utf-8\");\n\n\t\t// Strip BOM before matching (LLM won't include invisible BOM in oldText)\n\t\tconst { text: content } = stripBom(rawContent);\n\t\tconst normalizedContent = normalizeToLF(content);\n\t\tconst { baseContent, newContent } = applyEditsToNormalizedContent(normalizedContent, edits, path);\n\n\t\t// Generate the diff\n\t\treturn generateDiffString(baseContent, newContent);\n\t} catch (err) {\n\t\treturn { error: err instanceof Error ? err.message : String(err) };\n\t}\n}\n"]}
@@ -34,7 +34,7 @@ export function restoreLineEndings(text, ending) {
34
34
  * - Normalize Unicode dashes/hyphens to ASCII hyphen
35
35
  * - Normalize special Unicode spaces to regular space
36
36
  */
37
- export function normalizeForFuzzyMatch(text) {
37
+ function normalizeForFuzzyMatch(text) {
38
38
  // Check cache first
39
39
  const cached = normalizeCache.get(text);
40
40
  if (cached !== undefined)
@@ -86,7 +86,7 @@ export function normalizeForFuzzyMatch(text) {
86
86
  * fuzzy-normalized version of the content (trailing whitespace stripped,
87
87
  * Unicode quotes/dashes normalized to ASCII).
88
88
  */
89
- export function fuzzyFindText(content, oldText) {
89
+ function fuzzyFindText(content, oldText) {
90
90
  // Try exact match first
91
91
  const exactIndex = content.indexOf(oldText);
92
92
  if (exactIndex !== -1) {
@@ -457,11 +457,4 @@ export async function computeEditsDiff(path, edits, cwd) {
457
457
  return { error: err instanceof Error ? err.message : String(err) };
458
458
  }
459
459
  }
460
- /**
461
- * Compute the diff for a single edit operation without applying it.
462
- * Kept as a convenience wrapper for single-edit callers.
463
- */
464
- export async function computeEditDiff(path, oldText, newText, cwd) {
465
- return computeEditsDiff(path, [{ oldText, newText }], cwd);
466
- }
467
460
  //# sourceMappingURL=edit-diff.js.map