@nghyane/arcane 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (738) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +12 -0
  3. package/examples/README.md +21 -0
  4. package/examples/custom-tools/README.md +109 -0
  5. package/examples/custom-tools/hello/index.ts +20 -0
  6. package/examples/custom-tools/todo/index.ts +206 -0
  7. package/examples/extensions/README.md +143 -0
  8. package/examples/extensions/api-demo.ts +89 -0
  9. package/examples/extensions/chalk-logger.ts +25 -0
  10. package/examples/extensions/hello.ts +32 -0
  11. package/examples/extensions/pirate.ts +43 -0
  12. package/examples/extensions/plan-mode.ts +550 -0
  13. package/examples/extensions/reload-runtime.ts +37 -0
  14. package/examples/extensions/todo.ts +296 -0
  15. package/examples/extensions/tools.ts +144 -0
  16. package/examples/extensions/with-deps/index.ts +35 -0
  17. package/examples/extensions/with-deps/package-lock.json +31 -0
  18. package/examples/extensions/with-deps/package.json +16 -0
  19. package/examples/hooks/README.md +56 -0
  20. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  21. package/examples/hooks/confirm-destructive.ts +58 -0
  22. package/examples/hooks/custom-compaction.ts +116 -0
  23. package/examples/hooks/dirty-repo-guard.ts +51 -0
  24. package/examples/hooks/file-trigger.ts +40 -0
  25. package/examples/hooks/git-checkpoint.ts +52 -0
  26. package/examples/hooks/handoff.ts +150 -0
  27. package/examples/hooks/permission-gate.ts +33 -0
  28. package/examples/hooks/protected-paths.ts +29 -0
  29. package/examples/hooks/qna.ts +119 -0
  30. package/examples/hooks/status-line.ts +39 -0
  31. package/examples/sdk/01-minimal.ts +21 -0
  32. package/examples/sdk/02-custom-model.ts +49 -0
  33. package/examples/sdk/03-custom-prompt.ts +43 -0
  34. package/examples/sdk/04-skills.ts +43 -0
  35. package/examples/sdk/06-extensions.ts +80 -0
  36. package/examples/sdk/06-hooks.ts +61 -0
  37. package/examples/sdk/07-context-files.ts +35 -0
  38. package/examples/sdk/08-prompt-templates.ts +36 -0
  39. package/examples/sdk/08-slash-commands.ts +41 -0
  40. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  41. package/examples/sdk/11-sessions.ts +47 -0
  42. package/examples/sdk/README.md +150 -0
  43. package/package.json +464 -0
  44. package/scripts/format-prompts.ts +184 -0
  45. package/scripts/generate-docs-index.ts +40 -0
  46. package/scripts/generate-template.ts +32 -0
  47. package/src/bun-imports.d.ts +22 -0
  48. package/src/capability/context-file.ts +39 -0
  49. package/src/capability/extension-module.ts +33 -0
  50. package/src/capability/extension.ts +47 -0
  51. package/src/capability/fs.ts +89 -0
  52. package/src/capability/hook.ts +39 -0
  53. package/src/capability/index.ts +432 -0
  54. package/src/capability/instruction.ts +36 -0
  55. package/src/capability/mcp.ts +60 -0
  56. package/src/capability/prompt.ts +34 -0
  57. package/src/capability/rule.ts +223 -0
  58. package/src/capability/settings.ts +34 -0
  59. package/src/capability/skill.ts +48 -0
  60. package/src/capability/slash-command.ts +39 -0
  61. package/src/capability/ssh.ts +41 -0
  62. package/src/capability/system-prompt.ts +34 -0
  63. package/src/capability/tool.ts +37 -0
  64. package/src/capability/types.ts +156 -0
  65. package/src/cli/args.ts +259 -0
  66. package/src/cli/config-cli.ts +357 -0
  67. package/src/cli/file-processor.ts +124 -0
  68. package/src/cli/grep-cli.ts +152 -0
  69. package/src/cli/jupyter-cli.ts +106 -0
  70. package/src/cli/list-models.ts +103 -0
  71. package/src/cli/plugin-cli.ts +661 -0
  72. package/src/cli/session-picker.ts +42 -0
  73. package/src/cli/setup-cli.ts +376 -0
  74. package/src/cli/shell-cli.ts +174 -0
  75. package/src/cli/ssh-cli.ts +179 -0
  76. package/src/cli/stats-cli.ts +197 -0
  77. package/src/cli/update-cli.ts +286 -0
  78. package/src/cli/web-search-cli.ts +143 -0
  79. package/src/cli.ts +65 -0
  80. package/src/commands/commit.ts +36 -0
  81. package/src/commands/config.ts +51 -0
  82. package/src/commands/grep.ts +41 -0
  83. package/src/commands/jupyter.ts +32 -0
  84. package/src/commands/launch.ts +139 -0
  85. package/src/commands/plugin.ts +70 -0
  86. package/src/commands/setup.ts +42 -0
  87. package/src/commands/shell.ts +29 -0
  88. package/src/commands/ssh.ts +60 -0
  89. package/src/commands/stats.ts +29 -0
  90. package/src/commands/update.ts +21 -0
  91. package/src/commands/web-search.ts +42 -0
  92. package/src/commit/agentic/agent.ts +311 -0
  93. package/src/commit/agentic/fallback.ts +96 -0
  94. package/src/commit/agentic/index.ts +359 -0
  95. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  96. package/src/commit/agentic/prompts/session-user.md +25 -0
  97. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  98. package/src/commit/agentic/prompts/system.md +38 -0
  99. package/src/commit/agentic/state.ts +69 -0
  100. package/src/commit/agentic/tools/analyze-file.ts +118 -0
  101. package/src/commit/agentic/tools/git-file-diff.ts +194 -0
  102. package/src/commit/agentic/tools/git-hunk.ts +50 -0
  103. package/src/commit/agentic/tools/git-overview.ts +84 -0
  104. package/src/commit/agentic/tools/index.ts +56 -0
  105. package/src/commit/agentic/tools/propose-changelog.ts +128 -0
  106. package/src/commit/agentic/tools/propose-commit.ts +154 -0
  107. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  108. package/src/commit/agentic/tools/split-commit.ts +280 -0
  109. package/src/commit/agentic/topo-sort.ts +44 -0
  110. package/src/commit/agentic/trivial.ts +51 -0
  111. package/src/commit/agentic/validation.ts +200 -0
  112. package/src/commit/analysis/conventional.ts +165 -0
  113. package/src/commit/analysis/index.ts +4 -0
  114. package/src/commit/analysis/scope.ts +242 -0
  115. package/src/commit/analysis/summary.ts +112 -0
  116. package/src/commit/analysis/validation.ts +66 -0
  117. package/src/commit/changelog/detect.ts +37 -0
  118. package/src/commit/changelog/generate.ts +110 -0
  119. package/src/commit/changelog/index.ts +234 -0
  120. package/src/commit/changelog/parse.ts +44 -0
  121. package/src/commit/cli.ts +93 -0
  122. package/src/commit/git/diff.ts +148 -0
  123. package/src/commit/git/errors.ts +9 -0
  124. package/src/commit/git/index.ts +211 -0
  125. package/src/commit/git/operations.ts +54 -0
  126. package/src/commit/index.ts +5 -0
  127. package/src/commit/map-reduce/index.ts +64 -0
  128. package/src/commit/map-reduce/map-phase.ts +178 -0
  129. package/src/commit/map-reduce/reduce-phase.ts +145 -0
  130. package/src/commit/map-reduce/utils.ts +9 -0
  131. package/src/commit/message.ts +11 -0
  132. package/src/commit/model-selection.ts +69 -0
  133. package/src/commit/pipeline.ts +243 -0
  134. package/src/commit/prompts/analysis-system.md +148 -0
  135. package/src/commit/prompts/analysis-user.md +38 -0
  136. package/src/commit/prompts/changelog-system.md +50 -0
  137. package/src/commit/prompts/changelog-user.md +18 -0
  138. package/src/commit/prompts/file-observer-system.md +24 -0
  139. package/src/commit/prompts/file-observer-user.md +8 -0
  140. package/src/commit/prompts/reduce-system.md +50 -0
  141. package/src/commit/prompts/reduce-user.md +17 -0
  142. package/src/commit/prompts/summary-retry.md +3 -0
  143. package/src/commit/prompts/summary-system.md +38 -0
  144. package/src/commit/prompts/summary-user.md +13 -0
  145. package/src/commit/prompts/types-description.md +2 -0
  146. package/src/commit/types.ts +109 -0
  147. package/src/commit/utils/exclusions.ts +42 -0
  148. package/src/config/file-lock.ts +121 -0
  149. package/src/config/keybindings.ts +280 -0
  150. package/src/config/model-registry.ts +1140 -0
  151. package/src/config/model-resolver.ts +812 -0
  152. package/src/config/prompt-templates.ts +526 -0
  153. package/src/config/resolve-config-value.ts +92 -0
  154. package/src/config/settings-schema.ts +1236 -0
  155. package/src/config/settings.ts +706 -0
  156. package/src/config.ts +414 -0
  157. package/src/cursor.ts +239 -0
  158. package/src/debug/index.ts +431 -0
  159. package/src/debug/log-formatting.ts +60 -0
  160. package/src/debug/log-viewer.ts +903 -0
  161. package/src/debug/profiler.ts +158 -0
  162. package/src/debug/report-bundle.ts +366 -0
  163. package/src/debug/system-info.ts +112 -0
  164. package/src/discovery/agents-md.ts +68 -0
  165. package/src/discovery/agents.ts +199 -0
  166. package/src/discovery/builtin.ts +815 -0
  167. package/src/discovery/claude-plugins.ts +205 -0
  168. package/src/discovery/claude.ts +506 -0
  169. package/src/discovery/cline.ts +83 -0
  170. package/src/discovery/codex.ts +532 -0
  171. package/src/discovery/cursor.ts +218 -0
  172. package/src/discovery/gemini.ts +395 -0
  173. package/src/discovery/github.ts +117 -0
  174. package/src/discovery/helpers.ts +698 -0
  175. package/src/discovery/index.ts +89 -0
  176. package/src/discovery/mcp-json.ts +156 -0
  177. package/src/discovery/opencode.ts +394 -0
  178. package/src/discovery/ssh.ts +160 -0
  179. package/src/discovery/vscode.ts +103 -0
  180. package/src/discovery/windsurf.ts +145 -0
  181. package/src/exa/company.ts +57 -0
  182. package/src/exa/index.ts +62 -0
  183. package/src/exa/linkedin.ts +57 -0
  184. package/src/exa/mcp-client.ts +289 -0
  185. package/src/exa/render.ts +244 -0
  186. package/src/exa/researcher.ts +89 -0
  187. package/src/exa/search.ts +330 -0
  188. package/src/exa/types.ts +166 -0
  189. package/src/exa/websets.ts +247 -0
  190. package/src/exec/bash-executor.ts +184 -0
  191. package/src/exec/exec.ts +53 -0
  192. package/src/export/custom-share.ts +65 -0
  193. package/src/export/html/index.ts +162 -0
  194. package/src/export/html/template.css +889 -0
  195. package/src/export/html/template.generated.ts +2 -0
  196. package/src/export/html/template.html +45 -0
  197. package/src/export/html/template.js +1329 -0
  198. package/src/export/html/template.macro.ts +24 -0
  199. package/src/export/html/vendor/highlight.min.js +1213 -0
  200. package/src/export/html/vendor/marked.min.js +6 -0
  201. package/src/export/ttsr.ts +434 -0
  202. package/src/extensibility/custom-commands/bundled/review/index.ts +433 -0
  203. package/src/extensibility/custom-commands/index.ts +15 -0
  204. package/src/extensibility/custom-commands/loader.ts +231 -0
  205. package/src/extensibility/custom-commands/types.ts +111 -0
  206. package/src/extensibility/custom-tools/index.ts +22 -0
  207. package/src/extensibility/custom-tools/loader.ts +235 -0
  208. package/src/extensibility/custom-tools/types.ts +226 -0
  209. package/src/extensibility/custom-tools/wrapper.ts +45 -0
  210. package/src/extensibility/extensions/index.ts +136 -0
  211. package/src/extensibility/extensions/loader.ts +520 -0
  212. package/src/extensibility/extensions/runner.ts +774 -0
  213. package/src/extensibility/extensions/types.ts +1293 -0
  214. package/src/extensibility/extensions/wrapper.ts +188 -0
  215. package/src/extensibility/hooks/index.ts +16 -0
  216. package/src/extensibility/hooks/loader.ts +273 -0
  217. package/src/extensibility/hooks/runner.ts +441 -0
  218. package/src/extensibility/hooks/tool-wrapper.ts +106 -0
  219. package/src/extensibility/hooks/types.ts +817 -0
  220. package/src/extensibility/plugins/doctor.ts +65 -0
  221. package/src/extensibility/plugins/git-url.ts +281 -0
  222. package/src/extensibility/plugins/index.ts +33 -0
  223. package/src/extensibility/plugins/installer.ts +192 -0
  224. package/src/extensibility/plugins/loader.ts +338 -0
  225. package/src/extensibility/plugins/manager.ts +716 -0
  226. package/src/extensibility/plugins/parser.ts +105 -0
  227. package/src/extensibility/plugins/types.ts +190 -0
  228. package/src/extensibility/skills.ts +385 -0
  229. package/src/extensibility/slash-commands.ts +287 -0
  230. package/src/extensibility/tool-proxy.ts +25 -0
  231. package/src/index.ts +275 -0
  232. package/src/internal-urls/agent-protocol.ts +136 -0
  233. package/src/internal-urls/artifact-protocol.ts +97 -0
  234. package/src/internal-urls/docs-index.generated.ts +54 -0
  235. package/src/internal-urls/docs-protocol.ts +84 -0
  236. package/src/internal-urls/index.ts +31 -0
  237. package/src/internal-urls/json-query.ts +126 -0
  238. package/src/internal-urls/memory-protocol.ts +133 -0
  239. package/src/internal-urls/router.ts +70 -0
  240. package/src/internal-urls/rule-protocol.ts +55 -0
  241. package/src/internal-urls/skill-protocol.ts +111 -0
  242. package/src/internal-urls/types.ts +52 -0
  243. package/src/ipy/executor.ts +556 -0
  244. package/src/ipy/gateway-coordinator.ts +426 -0
  245. package/src/ipy/kernel.ts +892 -0
  246. package/src/ipy/modules.ts +109 -0
  247. package/src/ipy/prelude.py +831 -0
  248. package/src/ipy/prelude.ts +3 -0
  249. package/src/ipy/runtime.ts +222 -0
  250. package/src/lsp/client.ts +867 -0
  251. package/src/lsp/clients/biome-client.ts +202 -0
  252. package/src/lsp/clients/index.ts +50 -0
  253. package/src/lsp/clients/lsp-linter-client.ts +93 -0
  254. package/src/lsp/clients/swiftlint-client.ts +120 -0
  255. package/src/lsp/config.ts +397 -0
  256. package/src/lsp/defaults.json +464 -0
  257. package/src/lsp/edits.ts +109 -0
  258. package/src/lsp/index.ts +1268 -0
  259. package/src/lsp/lspmux.ts +250 -0
  260. package/src/lsp/render.ts +689 -0
  261. package/src/lsp/types.ts +414 -0
  262. package/src/lsp/utils.ts +549 -0
  263. package/src/main.ts +773 -0
  264. package/src/mcp/client.ts +239 -0
  265. package/src/mcp/config-writer.ts +215 -0
  266. package/src/mcp/config.ts +363 -0
  267. package/src/mcp/index.ts +55 -0
  268. package/src/mcp/json-rpc.ts +84 -0
  269. package/src/mcp/loader.ts +124 -0
  270. package/src/mcp/manager.ts +490 -0
  271. package/src/mcp/oauth-discovery.ts +274 -0
  272. package/src/mcp/oauth-flow.ts +229 -0
  273. package/src/mcp/render.ts +123 -0
  274. package/src/mcp/tool-bridge.ts +372 -0
  275. package/src/mcp/tool-cache.ts +121 -0
  276. package/src/mcp/transports/http.ts +332 -0
  277. package/src/mcp/transports/index.ts +6 -0
  278. package/src/mcp/transports/stdio.ts +281 -0
  279. package/src/mcp/types.ts +248 -0
  280. package/src/memories/index.ts +1099 -0
  281. package/src/memories/storage.ts +563 -0
  282. package/src/modes/components/agent-dashboard.ts +1130 -0
  283. package/src/modes/components/assistant-message.ts +144 -0
  284. package/src/modes/components/bash-execution.ts +218 -0
  285. package/src/modes/components/bordered-loader.ts +41 -0
  286. package/src/modes/components/branch-summary-message.ts +45 -0
  287. package/src/modes/components/codemode-group.ts +369 -0
  288. package/src/modes/components/compaction-summary-message.ts +51 -0
  289. package/src/modes/components/countdown-timer.ts +46 -0
  290. package/src/modes/components/custom-editor.ts +181 -0
  291. package/src/modes/components/custom-message.ts +91 -0
  292. package/src/modes/components/diff.ts +186 -0
  293. package/src/modes/components/dynamic-border.ts +25 -0
  294. package/src/modes/components/extensions/extension-dashboard.ts +325 -0
  295. package/src/modes/components/extensions/extension-list.ts +484 -0
  296. package/src/modes/components/extensions/index.ts +9 -0
  297. package/src/modes/components/extensions/inspector-panel.ts +321 -0
  298. package/src/modes/components/extensions/state-manager.ts +586 -0
  299. package/src/modes/components/extensions/types.ts +191 -0
  300. package/src/modes/components/footer.ts +315 -0
  301. package/src/modes/components/history-search.ts +157 -0
  302. package/src/modes/components/hook-editor.ts +101 -0
  303. package/src/modes/components/hook-input.ts +72 -0
  304. package/src/modes/components/hook-message.ts +100 -0
  305. package/src/modes/components/hook-selector.ts +155 -0
  306. package/src/modes/components/index.ts +41 -0
  307. package/src/modes/components/keybinding-hints.ts +65 -0
  308. package/src/modes/components/login-dialog.ts +164 -0
  309. package/src/modes/components/mcp-add-wizard.ts +1295 -0
  310. package/src/modes/components/model-selector.ts +625 -0
  311. package/src/modes/components/oauth-selector.ts +210 -0
  312. package/src/modes/components/plugin-settings.ts +477 -0
  313. package/src/modes/components/python-execution.ts +196 -0
  314. package/src/modes/components/queue-mode-selector.ts +56 -0
  315. package/src/modes/components/read-tool-group.ts +119 -0
  316. package/src/modes/components/session-selector.ts +242 -0
  317. package/src/modes/components/settings-defs.ts +340 -0
  318. package/src/modes/components/settings-selector.ts +529 -0
  319. package/src/modes/components/show-images-selector.ts +45 -0
  320. package/src/modes/components/skill-message.ts +90 -0
  321. package/src/modes/components/status-line/index.ts +4 -0
  322. package/src/modes/components/status-line/presets.ts +94 -0
  323. package/src/modes/components/status-line/segments.ts +352 -0
  324. package/src/modes/components/status-line/separators.ts +55 -0
  325. package/src/modes/components/status-line/types.ts +75 -0
  326. package/src/modes/components/status-line-segment-editor.ts +354 -0
  327. package/src/modes/components/status-line.ts +421 -0
  328. package/src/modes/components/theme-selector.ts +63 -0
  329. package/src/modes/components/thinking-selector.ts +64 -0
  330. package/src/modes/components/todo-display.ts +115 -0
  331. package/src/modes/components/todo-reminder.ts +40 -0
  332. package/src/modes/components/tool-execution.ts +703 -0
  333. package/src/modes/components/tree-selector.ts +904 -0
  334. package/src/modes/components/ttsr-notification.ts +80 -0
  335. package/src/modes/components/user-message-selector.ts +146 -0
  336. package/src/modes/components/user-message.ts +22 -0
  337. package/src/modes/components/visual-truncate.ts +63 -0
  338. package/src/modes/components/welcome.ts +247 -0
  339. package/src/modes/controllers/command-controller.ts +1120 -0
  340. package/src/modes/controllers/event-controller.ts +479 -0
  341. package/src/modes/controllers/extension-ui-controller.ts +778 -0
  342. package/src/modes/controllers/input-controller.ts +671 -0
  343. package/src/modes/controllers/mcp-command-controller.ts +1315 -0
  344. package/src/modes/controllers/selector-controller.ts +712 -0
  345. package/src/modes/controllers/ssh-command-controller.ts +452 -0
  346. package/src/modes/index.ts +15 -0
  347. package/src/modes/interactive-mode.ts +1027 -0
  348. package/src/modes/print-mode.ts +191 -0
  349. package/src/modes/rpc/rpc-client.ts +583 -0
  350. package/src/modes/rpc/rpc-mode.ts +700 -0
  351. package/src/modes/rpc/rpc-types.ts +236 -0
  352. package/src/modes/theme/dark.json +95 -0
  353. package/src/modes/theme/defaults/alabaster.json +93 -0
  354. package/src/modes/theme/defaults/amethyst.json +96 -0
  355. package/src/modes/theme/defaults/anthracite.json +93 -0
  356. package/src/modes/theme/defaults/basalt.json +91 -0
  357. package/src/modes/theme/defaults/birch.json +95 -0
  358. package/src/modes/theme/defaults/dark-abyss.json +91 -0
  359. package/src/modes/theme/defaults/dark-arctic.json +104 -0
  360. package/src/modes/theme/defaults/dark-aurora.json +95 -0
  361. package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
  362. package/src/modes/theme/defaults/dark-cavern.json +91 -0
  363. package/src/modes/theme/defaults/dark-copper.json +95 -0
  364. package/src/modes/theme/defaults/dark-cosmos.json +90 -0
  365. package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
  366. package/src/modes/theme/defaults/dark-dracula.json +98 -0
  367. package/src/modes/theme/defaults/dark-eclipse.json +91 -0
  368. package/src/modes/theme/defaults/dark-ember.json +95 -0
  369. package/src/modes/theme/defaults/dark-equinox.json +90 -0
  370. package/src/modes/theme/defaults/dark-forest.json +96 -0
  371. package/src/modes/theme/defaults/dark-github.json +105 -0
  372. package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
  373. package/src/modes/theme/defaults/dark-lavender.json +95 -0
  374. package/src/modes/theme/defaults/dark-lunar.json +89 -0
  375. package/src/modes/theme/defaults/dark-midnight.json +95 -0
  376. package/src/modes/theme/defaults/dark-monochrome.json +94 -0
  377. package/src/modes/theme/defaults/dark-monokai.json +98 -0
  378. package/src/modes/theme/defaults/dark-nebula.json +90 -0
  379. package/src/modes/theme/defaults/dark-nord.json +97 -0
  380. package/src/modes/theme/defaults/dark-ocean.json +101 -0
  381. package/src/modes/theme/defaults/dark-one.json +100 -0
  382. package/src/modes/theme/defaults/dark-rainforest.json +91 -0
  383. package/src/modes/theme/defaults/dark-reef.json +91 -0
  384. package/src/modes/theme/defaults/dark-retro.json +92 -0
  385. package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
  386. package/src/modes/theme/defaults/dark-sakura.json +95 -0
  387. package/src/modes/theme/defaults/dark-slate.json +95 -0
  388. package/src/modes/theme/defaults/dark-solarized.json +97 -0
  389. package/src/modes/theme/defaults/dark-solstice.json +90 -0
  390. package/src/modes/theme/defaults/dark-starfall.json +91 -0
  391. package/src/modes/theme/defaults/dark-sunset.json +99 -0
  392. package/src/modes/theme/defaults/dark-swamp.json +90 -0
  393. package/src/modes/theme/defaults/dark-synthwave.json +103 -0
  394. package/src/modes/theme/defaults/dark-taiga.json +91 -0
  395. package/src/modes/theme/defaults/dark-terminal.json +95 -0
  396. package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
  397. package/src/modes/theme/defaults/dark-tundra.json +91 -0
  398. package/src/modes/theme/defaults/dark-twilight.json +91 -0
  399. package/src/modes/theme/defaults/dark-volcanic.json +91 -0
  400. package/src/modes/theme/defaults/graphite.json +92 -0
  401. package/src/modes/theme/defaults/index.ts +195 -0
  402. package/src/modes/theme/defaults/light-arctic.json +107 -0
  403. package/src/modes/theme/defaults/light-aurora-day.json +91 -0
  404. package/src/modes/theme/defaults/light-canyon.json +91 -0
  405. package/src/modes/theme/defaults/light-catppuccin.json +106 -0
  406. package/src/modes/theme/defaults/light-cirrus.json +90 -0
  407. package/src/modes/theme/defaults/light-coral.json +95 -0
  408. package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
  409. package/src/modes/theme/defaults/light-dawn.json +90 -0
  410. package/src/modes/theme/defaults/light-dunes.json +91 -0
  411. package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
  412. package/src/modes/theme/defaults/light-forest.json +100 -0
  413. package/src/modes/theme/defaults/light-frost.json +95 -0
  414. package/src/modes/theme/defaults/light-github.json +115 -0
  415. package/src/modes/theme/defaults/light-glacier.json +91 -0
  416. package/src/modes/theme/defaults/light-gruvbox.json +108 -0
  417. package/src/modes/theme/defaults/light-haze.json +90 -0
  418. package/src/modes/theme/defaults/light-honeycomb.json +95 -0
  419. package/src/modes/theme/defaults/light-lagoon.json +91 -0
  420. package/src/modes/theme/defaults/light-lavender.json +95 -0
  421. package/src/modes/theme/defaults/light-meadow.json +91 -0
  422. package/src/modes/theme/defaults/light-mint.json +95 -0
  423. package/src/modes/theme/defaults/light-monochrome.json +101 -0
  424. package/src/modes/theme/defaults/light-ocean.json +99 -0
  425. package/src/modes/theme/defaults/light-one.json +99 -0
  426. package/src/modes/theme/defaults/light-opal.json +91 -0
  427. package/src/modes/theme/defaults/light-orchard.json +91 -0
  428. package/src/modes/theme/defaults/light-paper.json +95 -0
  429. package/src/modes/theme/defaults/light-prism.json +90 -0
  430. package/src/modes/theme/defaults/light-retro.json +98 -0
  431. package/src/modes/theme/defaults/light-sand.json +95 -0
  432. package/src/modes/theme/defaults/light-savanna.json +91 -0
  433. package/src/modes/theme/defaults/light-solarized.json +102 -0
  434. package/src/modes/theme/defaults/light-soleil.json +90 -0
  435. package/src/modes/theme/defaults/light-sunset.json +99 -0
  436. package/src/modes/theme/defaults/light-synthwave.json +98 -0
  437. package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
  438. package/src/modes/theme/defaults/light-wetland.json +91 -0
  439. package/src/modes/theme/defaults/light-zenith.json +89 -0
  440. package/src/modes/theme/defaults/limestone.json +94 -0
  441. package/src/modes/theme/defaults/mahogany.json +97 -0
  442. package/src/modes/theme/defaults/marble.json +93 -0
  443. package/src/modes/theme/defaults/obsidian.json +91 -0
  444. package/src/modes/theme/defaults/onyx.json +91 -0
  445. package/src/modes/theme/defaults/pearl.json +93 -0
  446. package/src/modes/theme/defaults/porcelain.json +91 -0
  447. package/src/modes/theme/defaults/quartz.json +96 -0
  448. package/src/modes/theme/defaults/sandstone.json +95 -0
  449. package/src/modes/theme/defaults/titanium.json +90 -0
  450. package/src/modes/theme/light.json +93 -0
  451. package/src/modes/theme/mermaid-cache.ts +111 -0
  452. package/src/modes/theme/theme-schema.json +429 -0
  453. package/src/modes/theme/theme.ts +2333 -0
  454. package/src/modes/types.ts +216 -0
  455. package/src/modes/utils/ui-helpers.ts +529 -0
  456. package/src/patch/applicator.ts +1482 -0
  457. package/src/patch/diff.ts +425 -0
  458. package/src/patch/fuzzy.ts +784 -0
  459. package/src/patch/hashline.ts +972 -0
  460. package/src/patch/index.ts +964 -0
  461. package/src/patch/normalize.ts +397 -0
  462. package/src/patch/normative.ts +72 -0
  463. package/src/patch/parser.ts +532 -0
  464. package/src/patch/shared.ts +400 -0
  465. package/src/patch/types.ts +292 -0
  466. package/src/priority.json +35 -0
  467. package/src/prompts/agents/explore.md +48 -0
  468. package/src/prompts/agents/frontmatter.md +9 -0
  469. package/src/prompts/agents/init.md +36 -0
  470. package/src/prompts/agents/librarian.md +53 -0
  471. package/src/prompts/agents/oracle.md +51 -0
  472. package/src/prompts/agents/reviewer.md +70 -0
  473. package/src/prompts/agents/task.md +14 -0
  474. package/src/prompts/compaction/branch-summary-context.md +5 -0
  475. package/src/prompts/compaction/branch-summary-preamble.md +2 -0
  476. package/src/prompts/compaction/branch-summary.md +30 -0
  477. package/src/prompts/compaction/compaction-short-summary.md +9 -0
  478. package/src/prompts/compaction/compaction-summary-context.md +5 -0
  479. package/src/prompts/compaction/compaction-summary.md +38 -0
  480. package/src/prompts/compaction/compaction-turn-prefix.md +17 -0
  481. package/src/prompts/compaction/compaction-update-summary.md +45 -0
  482. package/src/prompts/memories/consolidation.md +30 -0
  483. package/src/prompts/memories/read_path.md +11 -0
  484. package/src/prompts/memories/stage_one_input.md +6 -0
  485. package/src/prompts/memories/stage_one_system.md +21 -0
  486. package/src/prompts/review-request.md +64 -0
  487. package/src/prompts/system/agent-creation-architect.md +65 -0
  488. package/src/prompts/system/agent-creation-user.md +6 -0
  489. package/src/prompts/system/custom-system-prompt.md +68 -0
  490. package/src/prompts/system/file-operations.md +10 -0
  491. package/src/prompts/system/subagent-submit-reminder.md +11 -0
  492. package/src/prompts/system/subagent-system-prompt.md +31 -0
  493. package/src/prompts/system/subagent-user-prompt.md +8 -0
  494. package/src/prompts/system/summarization-system.md +3 -0
  495. package/src/prompts/system/system-prompt.md +300 -0
  496. package/src/prompts/system/title-system.md +2 -0
  497. package/src/prompts/system/ttsr-interrupt.md +7 -0
  498. package/src/prompts/system/web-search.md +28 -0
  499. package/src/prompts/tools/ask.md +44 -0
  500. package/src/prompts/tools/bash.md +24 -0
  501. package/src/prompts/tools/browser.md +33 -0
  502. package/src/prompts/tools/calculator.md +12 -0
  503. package/src/prompts/tools/explore.md +29 -0
  504. package/src/prompts/tools/fetch.md +16 -0
  505. package/src/prompts/tools/find.md +18 -0
  506. package/src/prompts/tools/gemini-image.md +23 -0
  507. package/src/prompts/tools/grep.md +28 -0
  508. package/src/prompts/tools/hashline.md +232 -0
  509. package/src/prompts/tools/librarian.md +24 -0
  510. package/src/prompts/tools/lsp.md +28 -0
  511. package/src/prompts/tools/oracle.md +26 -0
  512. package/src/prompts/tools/patch.md +74 -0
  513. package/src/prompts/tools/python.md +66 -0
  514. package/src/prompts/tools/read.md +36 -0
  515. package/src/prompts/tools/replace.md +38 -0
  516. package/src/prompts/tools/reviewer.md +41 -0
  517. package/src/prompts/tools/ssh.md +51 -0
  518. package/src/prompts/tools/task-summary.md +28 -0
  519. package/src/prompts/tools/task.md +275 -0
  520. package/src/prompts/tools/todo-write.md +65 -0
  521. package/src/prompts/tools/undo-edit.md +7 -0
  522. package/src/prompts/tools/web-search.md +19 -0
  523. package/src/prompts/tools/write.md +18 -0
  524. package/src/sdk.ts +1287 -0
  525. package/src/secrets/index.ts +116 -0
  526. package/src/secrets/obfuscator.ts +269 -0
  527. package/src/secrets/regex.ts +21 -0
  528. package/src/session/agent-session.ts +4669 -0
  529. package/src/session/agent-storage.ts +621 -0
  530. package/src/session/artifacts.ts +132 -0
  531. package/src/session/auth-storage.ts +1433 -0
  532. package/src/session/blob-store.ts +103 -0
  533. package/src/session/compaction/branch-summarization.ts +315 -0
  534. package/src/session/compaction/compaction.ts +864 -0
  535. package/src/session/compaction/index.ts +7 -0
  536. package/src/session/compaction/pruning.ts +91 -0
  537. package/src/session/compaction/utils.ts +171 -0
  538. package/src/session/history-storage.ts +170 -0
  539. package/src/session/messages.ts +317 -0
  540. package/src/session/session-manager.ts +2276 -0
  541. package/src/session/session-storage.ts +342 -0
  542. package/src/session/streaming-output.ts +565 -0
  543. package/src/slash-commands/builtin-registry.ts +439 -0
  544. package/src/ssh/config-writer.ts +183 -0
  545. package/src/ssh/connection-manager.ts +444 -0
  546. package/src/ssh/ssh-executor.ts +127 -0
  547. package/src/ssh/sshfs-mount.ts +135 -0
  548. package/src/stt/downloader.ts +71 -0
  549. package/src/stt/index.ts +3 -0
  550. package/src/stt/recorder.ts +351 -0
  551. package/src/stt/setup.ts +52 -0
  552. package/src/stt/stt-controller.ts +160 -0
  553. package/src/stt/transcribe.py +70 -0
  554. package/src/stt/transcriber.ts +91 -0
  555. package/src/system-prompt.ts +685 -0
  556. package/src/task/agents.ts +155 -0
  557. package/src/task/batch.ts +102 -0
  558. package/src/task/commands.ts +134 -0
  559. package/src/task/discovery.ts +126 -0
  560. package/src/task/executor.ts +908 -0
  561. package/src/task/index.ts +223 -0
  562. package/src/task/output-manager.ts +107 -0
  563. package/src/task/parallel.ts +84 -0
  564. package/src/task/render.ts +326 -0
  565. package/src/task/subprocess-tool-registry.ts +88 -0
  566. package/src/task/template.ts +32 -0
  567. package/src/task/types.ts +144 -0
  568. package/src/tools/ask.ts +523 -0
  569. package/src/tools/bash-interactive.ts +419 -0
  570. package/src/tools/bash-interceptor.ts +105 -0
  571. package/src/tools/bash-normalize.ts +107 -0
  572. package/src/tools/bash-skill-urls.ts +177 -0
  573. package/src/tools/bash.ts +347 -0
  574. package/src/tools/browser.ts +1374 -0
  575. package/src/tools/calculator.ts +537 -0
  576. package/src/tools/context.ts +39 -0
  577. package/src/tools/explore.ts +23 -0
  578. package/src/tools/fetch.ts +1091 -0
  579. package/src/tools/find.ts +540 -0
  580. package/src/tools/fs-cache-invalidation.ts +28 -0
  581. package/src/tools/gemini-image.ts +907 -0
  582. package/src/tools/grep.ts +489 -0
  583. package/src/tools/index.ts +337 -0
  584. package/src/tools/json-tree.ts +231 -0
  585. package/src/tools/jtd-to-json-schema.ts +247 -0
  586. package/src/tools/jtd-to-typescript.ts +198 -0
  587. package/src/tools/librarian.ts +33 -0
  588. package/src/tools/list-limit.ts +40 -0
  589. package/src/tools/notebook.ts +287 -0
  590. package/src/tools/oracle.ts +40 -0
  591. package/src/tools/output-meta.ts +459 -0
  592. package/src/tools/output-utils.ts +63 -0
  593. package/src/tools/path-utils.ts +116 -0
  594. package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
  595. package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
  596. package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
  597. package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
  598. package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
  599. package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
  600. package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
  601. package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
  602. package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
  603. package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
  604. package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
  605. package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
  606. package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
  607. package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
  608. package/src/tools/python.ts +1118 -0
  609. package/src/tools/read.ts +1193 -0
  610. package/src/tools/render-utils.ts +680 -0
  611. package/src/tools/renderers.ts +60 -0
  612. package/src/tools/reviewer-tool.ts +41 -0
  613. package/src/tools/ssh.ts +326 -0
  614. package/src/tools/subagent-tool.ts +169 -0
  615. package/src/tools/submit-result.ts +152 -0
  616. package/src/tools/todo-write.ts +255 -0
  617. package/src/tools/tool-errors.ts +92 -0
  618. package/src/tools/tool-result.ts +86 -0
  619. package/src/tools/undo-edit.ts +145 -0
  620. package/src/tools/undo-history.ts +22 -0
  621. package/src/tools/write.ts +274 -0
  622. package/src/tui/code-cell.ts +108 -0
  623. package/src/tui/file-list.ts +47 -0
  624. package/src/tui/index.ts +11 -0
  625. package/src/tui/output-block.ts +144 -0
  626. package/src/tui/status-line.ts +39 -0
  627. package/src/tui/tree-list.ts +53 -0
  628. package/src/tui/types.ts +16 -0
  629. package/src/tui/utils.ts +116 -0
  630. package/src/utils/changelog.ts +98 -0
  631. package/src/utils/event-bus.ts +33 -0
  632. package/src/utils/external-editor.ts +59 -0
  633. package/src/utils/file-display-mode.ts +36 -0
  634. package/src/utils/file-mentions.ts +384 -0
  635. package/src/utils/frontmatter.ts +101 -0
  636. package/src/utils/fuzzy.ts +108 -0
  637. package/src/utils/ignore-files.ts +119 -0
  638. package/src/utils/image-convert.ts +27 -0
  639. package/src/utils/image-resize.ts +236 -0
  640. package/src/utils/mime.ts +30 -0
  641. package/src/utils/open.ts +20 -0
  642. package/src/utils/shell-snapshot.ts +199 -0
  643. package/src/utils/timings.ts +26 -0
  644. package/src/utils/title-generator.ts +167 -0
  645. package/src/utils/tools-manager.ts +362 -0
  646. package/src/web/scrapers/artifacthub.ts +215 -0
  647. package/src/web/scrapers/arxiv.ts +88 -0
  648. package/src/web/scrapers/aur.ts +175 -0
  649. package/src/web/scrapers/biorxiv.ts +141 -0
  650. package/src/web/scrapers/bluesky.ts +284 -0
  651. package/src/web/scrapers/brew.ts +177 -0
  652. package/src/web/scrapers/cheatsh.ts +78 -0
  653. package/src/web/scrapers/chocolatey.ts +158 -0
  654. package/src/web/scrapers/choosealicense.ts +110 -0
  655. package/src/web/scrapers/cisa-kev.ts +100 -0
  656. package/src/web/scrapers/clojars.ts +180 -0
  657. package/src/web/scrapers/coingecko.ts +184 -0
  658. package/src/web/scrapers/crates-io.ts +128 -0
  659. package/src/web/scrapers/crossref.ts +149 -0
  660. package/src/web/scrapers/devto.ts +177 -0
  661. package/src/web/scrapers/discogs.ts +307 -0
  662. package/src/web/scrapers/discourse.ts +221 -0
  663. package/src/web/scrapers/dockerhub.ts +160 -0
  664. package/src/web/scrapers/fdroid.ts +158 -0
  665. package/src/web/scrapers/firefox-addons.ts +214 -0
  666. package/src/web/scrapers/flathub.ts +239 -0
  667. package/src/web/scrapers/github-gist.ts +68 -0
  668. package/src/web/scrapers/github.ts +490 -0
  669. package/src/web/scrapers/gitlab.ts +456 -0
  670. package/src/web/scrapers/go-pkg.ts +275 -0
  671. package/src/web/scrapers/hackage.ts +94 -0
  672. package/src/web/scrapers/hackernews.ts +208 -0
  673. package/src/web/scrapers/hex.ts +121 -0
  674. package/src/web/scrapers/huggingface.ts +385 -0
  675. package/src/web/scrapers/iacr.ts +86 -0
  676. package/src/web/scrapers/index.ts +249 -0
  677. package/src/web/scrapers/jetbrains-marketplace.ts +169 -0
  678. package/src/web/scrapers/lemmy.ts +220 -0
  679. package/src/web/scrapers/lobsters.ts +186 -0
  680. package/src/web/scrapers/mastodon.ts +310 -0
  681. package/src/web/scrapers/maven.ts +152 -0
  682. package/src/web/scrapers/mdn.ts +172 -0
  683. package/src/web/scrapers/metacpan.ts +253 -0
  684. package/src/web/scrapers/musicbrainz.ts +272 -0
  685. package/src/web/scrapers/npm.ts +114 -0
  686. package/src/web/scrapers/nuget.ts +205 -0
  687. package/src/web/scrapers/nvd.ts +243 -0
  688. package/src/web/scrapers/ollama.ts +265 -0
  689. package/src/web/scrapers/open-vsx.ts +119 -0
  690. package/src/web/scrapers/opencorporates.ts +275 -0
  691. package/src/web/scrapers/openlibrary.ts +319 -0
  692. package/src/web/scrapers/orcid.ts +298 -0
  693. package/src/web/scrapers/osv.ts +192 -0
  694. package/src/web/scrapers/packagist.ts +174 -0
  695. package/src/web/scrapers/pub-dev.ts +185 -0
  696. package/src/web/scrapers/pubmed.ts +177 -0
  697. package/src/web/scrapers/pypi.ts +129 -0
  698. package/src/web/scrapers/rawg.ts +124 -0
  699. package/src/web/scrapers/readthedocs.ts +125 -0
  700. package/src/web/scrapers/reddit.ts +104 -0
  701. package/src/web/scrapers/repology.ts +262 -0
  702. package/src/web/scrapers/rfc.ts +209 -0
  703. package/src/web/scrapers/rubygems.ts +117 -0
  704. package/src/web/scrapers/searchcode.ts +217 -0
  705. package/src/web/scrapers/sec-edgar.ts +274 -0
  706. package/src/web/scrapers/semantic-scholar.ts +190 -0
  707. package/src/web/scrapers/snapcraft.ts +200 -0
  708. package/src/web/scrapers/sourcegraph.ts +373 -0
  709. package/src/web/scrapers/spdx.ts +121 -0
  710. package/src/web/scrapers/spotify.ts +217 -0
  711. package/src/web/scrapers/stackoverflow.ts +124 -0
  712. package/src/web/scrapers/terraform.ts +304 -0
  713. package/src/web/scrapers/tldr.ts +51 -0
  714. package/src/web/scrapers/twitter.ts +97 -0
  715. package/src/web/scrapers/types.ts +200 -0
  716. package/src/web/scrapers/utils.ts +142 -0
  717. package/src/web/scrapers/vimeo.ts +152 -0
  718. package/src/web/scrapers/vscode-marketplace.ts +195 -0
  719. package/src/web/scrapers/w3c.ts +163 -0
  720. package/src/web/scrapers/wikidata.ts +357 -0
  721. package/src/web/scrapers/wikipedia.ts +95 -0
  722. package/src/web/scrapers/youtube.ts +312 -0
  723. package/src/web/search/auth.ts +178 -0
  724. package/src/web/search/index.ts +598 -0
  725. package/src/web/search/provider.ts +77 -0
  726. package/src/web/search/providers/anthropic.ts +284 -0
  727. package/src/web/search/providers/base.ts +22 -0
  728. package/src/web/search/providers/brave.ts +165 -0
  729. package/src/web/search/providers/codex.ts +377 -0
  730. package/src/web/search/providers/exa.ts +158 -0
  731. package/src/web/search/providers/gemini.ts +437 -0
  732. package/src/web/search/providers/jina.ts +99 -0
  733. package/src/web/search/providers/kimi.ts +196 -0
  734. package/src/web/search/providers/perplexity.ts +546 -0
  735. package/src/web/search/providers/synthetic.ts +136 -0
  736. package/src/web/search/providers/zai.ts +352 -0
  737. package/src/web/search/render.ts +299 -0
  738. package/src/web/search/types.ts +437 -0
@@ -0,0 +1,964 @@
1
+ /**
2
+ * Edit tool module.
3
+ *
4
+ * Supports three modes:
5
+ * - Replace mode (default): oldText/newText replacement with fuzzy matching
6
+ * - Patch mode: structured diff format with explicit operation type
7
+ * - Hashline mode: line-addressed edits using content hashes for integrity
8
+ *
9
+ * The mode is determined by the `edit.mode` setting.
10
+ */
11
+ import * as fs from "node:fs/promises";
12
+ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@nghyane/arcane-agent";
13
+ import { StringEnum } from "@nghyane/arcane-ai";
14
+ import { type Static, Type } from "@sinclair/typebox";
15
+ import { renderPromptTemplate } from "../config/prompt-templates";
16
+ import {
17
+ createLspWritethrough,
18
+ type FileDiagnosticsResult,
19
+ flushLspWritethroughBatch,
20
+ type WritethroughCallback,
21
+ writethroughNoop,
22
+ } from "../lsp";
23
+ import hashlineDescription from "../prompts/tools/hashline.md" with { type: "text" };
24
+ import patchDescription from "../prompts/tools/patch.md" with { type: "text" };
25
+ import replaceDescription from "../prompts/tools/replace.md" with { type: "text" };
26
+ import type { ToolSession } from "../tools";
27
+ import {
28
+ invalidateFsScanAfterDelete,
29
+ invalidateFsScanAfterRename,
30
+ invalidateFsScanAfterWrite,
31
+ } from "../tools/fs-cache-invalidation";
32
+ import { outputMeta } from "../tools/output-meta";
33
+ import { resolveToCwd } from "../tools/path-utils";
34
+ import { saveForUndo } from "../tools/undo-history";
35
+ import { applyPatch } from "./applicator";
36
+ import { generateDiffString, generateUnifiedDiffString, replaceText } from "./diff";
37
+ import { findMatch } from "./fuzzy";
38
+ import {
39
+ applyHashlineEdits,
40
+ computeLineHash,
41
+ type HashlineEdit,
42
+ type LineTag,
43
+ parseTag,
44
+ type ReplaceTextEdit,
45
+ } from "./hashline";
46
+ import { detectLineEnding, normalizeToLF, restoreLineEndings, stripBom } from "./normalize";
47
+ import { buildNormativeUpdateInput } from "./normative";
48
+ import { type EditToolDetails, getLspBatchRequest } from "./shared";
49
+ // Internal imports
50
+ import type { FileSystem, Operation, PatchInput } from "./types";
51
+ import { EditMatchError } from "./types";
52
+
53
+ // ═══════════════════════════════════════════════════════════════════════════
54
+ // Re-exports
55
+ // ═══════════════════════════════════════════════════════════════════════════
56
+
57
+ // Application
58
+ export { applyPatch, defaultFileSystem, previewPatch } from "./applicator";
59
+ // Diff generation
60
+ export {
61
+ computeEditDiff,
62
+ computeHashlineDiff,
63
+ computePatchDiff,
64
+ generateDiffString,
65
+ generateUnifiedDiffString,
66
+ replaceText,
67
+ } from "./diff";
68
+
69
+ // Fuzzy matching
70
+ export { DEFAULT_FUZZY_THRESHOLD, findContextLine, findMatch as findEditMatch, findMatch, seekSequence } from "./fuzzy";
71
+ // Hashline
72
+ export {
73
+ applyHashlineEdits,
74
+ computeLineHash,
75
+ formatHashLines,
76
+ HashlineMismatchError,
77
+ parseTag,
78
+ streamHashLinesFromLines,
79
+ streamHashLinesFromUtf8,
80
+ validateLineRef,
81
+ } from "./hashline";
82
+ // Normalization
83
+ export { adjustIndentation, detectLineEnding, normalizeToLF, restoreLineEndings, stripBom } from "./normalize";
84
+ // Parsing
85
+ export { normalizeCreateContent, normalizeDiff, parseHunks as parseDiffHunks } from "./parser";
86
+ export type { EditRenderContext, EditToolDetails } from "./shared";
87
+ // Rendering
88
+ export { editToolRenderer, getLspBatchRequest } from "./shared";
89
+ export type {
90
+ ApplyPatchOptions,
91
+ ApplyPatchResult,
92
+ ContextLineResult,
93
+ DiffError,
94
+ DiffError as EditDiffError,
95
+ DiffHunk,
96
+ DiffHunk as UpdateChunk,
97
+ DiffHunk as UpdateFileChunk,
98
+ DiffResult,
99
+ DiffResult as EditDiffResult,
100
+ FileChange,
101
+ FileSystem,
102
+ FuzzyMatch as EditMatch,
103
+ FuzzyMatch,
104
+ HashMismatch,
105
+ MatchOutcome as EditMatchOutcome,
106
+ MatchOutcome,
107
+ Operation,
108
+ PatchInput,
109
+ SequenceSearchResult,
110
+ } from "./types";
111
+ // Types
112
+ // Legacy aliases for backwards compatibility
113
+ export { ApplyPatchError, EditMatchError, ParseError } from "./types";
114
+
115
+ // ═══════════════════════════════════════════════════════════════════════════
116
+ // Schemas
117
+ // ═══════════════════════════════════════════════════════════════════════════
118
+
119
+ const replaceEditSchema = Type.Object({
120
+ path: Type.String({ description: "File path (relative or absolute)" }),
121
+ old_text: Type.String({ description: "Text to find (fuzzy whitespace matching enabled)" }),
122
+ new_text: Type.String({ description: "Replacement text" }),
123
+ all: Type.Optional(Type.Boolean({ description: "Replace all occurrences (default: unique match required)" })),
124
+ });
125
+
126
+ const patchEditSchema = Type.Object({
127
+ path: Type.String({ description: "File path" }),
128
+ op: Type.Optional(
129
+ StringEnum(["create", "delete", "update"], {
130
+ description: "Operation (default: update)",
131
+ }),
132
+ ),
133
+ rename: Type.Optional(Type.String({ description: "New path for move" })),
134
+ diff: Type.Optional(Type.String({ description: "Diff hunks (update) or full content (create)" })),
135
+ });
136
+
137
+ export type ReplaceParams = Static<typeof replaceEditSchema>;
138
+ export type PatchParams = Static<typeof patchEditSchema>;
139
+
140
+ /** Pattern matching hashline display format: `LINE#ID:CONTENT` */
141
+ const HASHLINE_PREFIX_RE = /^\s*(?:>>>|>>)?\s*\d+#[0-9a-zA-Z]{1,16}:/;
142
+
143
+ /** Pattern matching a unified-diff `+` prefix (but not `++`) */
144
+ const DIFF_PLUS_RE = /^[+-](?![+-])/;
145
+
146
+ /**
147
+ * Strip hashline display prefixes and diff `+` markers from replacement lines.
148
+ *
149
+ * Models frequently copy the `LINE#ID ` prefix from read output into their
150
+ * replacement content, or include unified-diff `+` prefixes. Both corrupt the
151
+ * output file. This strips them heuristically before application.
152
+ */
153
+ function stripNewLinePrefixes(lines: string[]): string[] {
154
+ // Detect whether the *majority* of non-empty lines carry a prefix —
155
+ // if only one line out of many has a match it's likely real content.
156
+ let hashPrefixCount = 0;
157
+ let diffPlusCount = 0;
158
+ let nonEmpty = 0;
159
+ for (const l of lines) {
160
+ if (l.length === 0) continue;
161
+ nonEmpty++;
162
+ if (HASHLINE_PREFIX_RE.test(l)) hashPrefixCount++;
163
+ if (DIFF_PLUS_RE.test(l)) diffPlusCount++;
164
+ }
165
+ if (nonEmpty === 0) return lines;
166
+
167
+ const stripHash = hashPrefixCount > 0 && hashPrefixCount >= nonEmpty * 0.5;
168
+ const stripPlus = !stripHash && nonEmpty >= 2 && diffPlusCount > 0 && diffPlusCount >= nonEmpty * 0.5;
169
+
170
+ if (!stripHash && !stripPlus) return lines;
171
+
172
+ return lines.map(l => {
173
+ if (stripHash) return l.replace(HASHLINE_PREFIX_RE, "");
174
+ if (stripPlus) return l.replace(DIFF_PLUS_RE, "");
175
+ return l;
176
+ });
177
+ }
178
+
179
+ const hashlineReplaceContentFormat = (kind: string) =>
180
+ Type.Union([
181
+ Type.Null(),
182
+ Type.Array(Type.String(), { description: `${kind} lines` }),
183
+ Type.String({ description: `${kind} line` }),
184
+ ]);
185
+
186
+ const hashlineInsertContentFormat = (kind: string) =>
187
+ Type.Union([
188
+ Type.Array(Type.String(), { description: `${kind} lines`, minItems: 1 }),
189
+ Type.String({ description: `${kind} line`, minLength: 1 }),
190
+ ]);
191
+
192
+ const hashlineTagFormat = (what: string) =>
193
+ Type.String({
194
+ description: `Tag identifying the ${what} — format "N#XX" (e.g. "5#PM"), copied verbatim from read output`,
195
+ });
196
+
197
+ function hashlineParseContent(edit: string | string[] | null): string[] {
198
+ if (edit === null) return [];
199
+ if (Array.isArray(edit)) return edit;
200
+ const lines = stripNewLinePrefixes(edit.split("\n"));
201
+ if (lines.length === 0) return [];
202
+ if (lines[lines.length - 1].trim() === "") return lines.slice(0, -1);
203
+ return lines;
204
+ }
205
+
206
+ function hashlineParseContentString(edit: string | string[] | null): string {
207
+ if (edit === null) return "";
208
+ if (Array.isArray(edit)) return edit.join("\n");
209
+ return edit;
210
+ }
211
+
212
+ const hashlineTargetEditSchema = Type.Object(
213
+ {
214
+ op: Type.Literal("set"),
215
+ tag: hashlineTagFormat("line being replaced"),
216
+ content: hashlineReplaceContentFormat("Replacement"),
217
+ },
218
+ { additionalProperties: false },
219
+ );
220
+
221
+ const hashlineAppendEditSchema = Type.Object(
222
+ {
223
+ op: Type.Literal("append"),
224
+ after: Type.Optional(hashlineTagFormat("line after which to append")),
225
+ content: hashlineInsertContentFormat("Appended"),
226
+ },
227
+ { additionalProperties: false },
228
+ );
229
+
230
+ const hashlinePrependEditSchema = Type.Object(
231
+ {
232
+ op: Type.Literal("prepend"),
233
+ before: Type.Optional(hashlineTagFormat("line before which to prepend")),
234
+ content: hashlineInsertContentFormat("Prepended"),
235
+ },
236
+ { additionalProperties: false },
237
+ );
238
+
239
+ const hashlineRangeEditSchema = Type.Object(
240
+ {
241
+ op: Type.Literal("replace"),
242
+ first: hashlineTagFormat("first line"),
243
+ last: hashlineTagFormat("last line"),
244
+ content: hashlineReplaceContentFormat("Replacement"),
245
+ },
246
+ { additionalProperties: false },
247
+ );
248
+
249
+ const hashlineInsertEditSchema = Type.Object(
250
+ {
251
+ op: Type.Literal("insert"),
252
+ before: Type.Optional(hashlineTagFormat("line before which to insert")),
253
+ after: Type.Optional(hashlineTagFormat("line after which to insert")),
254
+ content: hashlineInsertContentFormat("Inserted"),
255
+ },
256
+ { additionalProperties: false },
257
+ );
258
+
259
+ const hashlineReplaceTextEditSchema = Type.Object(
260
+ {
261
+ op: Type.Literal("replaceText"),
262
+ old_text: Type.String({ description: "Text to find", minLength: 1 }),
263
+ new_text: hashlineReplaceContentFormat("Replacement"),
264
+ all: Type.Optional(Type.Boolean({ description: "Replace all occurrences" })),
265
+ },
266
+ { additionalProperties: false },
267
+ );
268
+
269
+ const HL_REPLACE_ENABLED = Bun.env.ARCANE_HL_REPLACETXT === "1";
270
+
271
+ const hashlineEditSpecSchema = Type.Union([
272
+ hashlineTargetEditSchema,
273
+ hashlineRangeEditSchema,
274
+ hashlineAppendEditSchema,
275
+ hashlinePrependEditSchema,
276
+ hashlineInsertEditSchema,
277
+ ...(HL_REPLACE_ENABLED ? [hashlineReplaceTextEditSchema] : []),
278
+ ]);
279
+
280
+ const hashlineEditSchema = Type.Object(
281
+ {
282
+ path: Type.String({ description: "File path (relative or absolute)" }),
283
+ edits: Type.Array(hashlineEditSpecSchema, {
284
+ description: "Changes to apply to the file at `path`",
285
+ minItems: 0,
286
+ }),
287
+ delete: Type.Optional(Type.Boolean({ description: "Delete the file when true" })),
288
+ rename: Type.Optional(Type.String({ description: "New path if moving" })),
289
+ },
290
+ { additionalProperties: false },
291
+ );
292
+
293
+ export type HashlineToolEdit = Static<typeof hashlineEditSpecSchema>;
294
+ export type HashlineParams = Static<typeof hashlineEditSchema>;
295
+
296
+ // ═══════════════════════════════════════════════════════════════════════════
297
+ // LSP FileSystem for patch mode
298
+ // ═══════════════════════════════════════════════════════════════════════════
299
+
300
+ class LspFileSystem implements FileSystem {
301
+ #lastDiagnostics: FileDiagnosticsResult | undefined;
302
+ #fileCache: Record<string, Bun.BunFile> = {};
303
+
304
+ constructor(
305
+ private readonly writethrough: (
306
+ dst: string,
307
+ content: string,
308
+ signal?: AbortSignal,
309
+ file?: import("bun").BunFile,
310
+ batch?: { id: string; flush: boolean },
311
+ ) => Promise<FileDiagnosticsResult | undefined>,
312
+ private readonly signal?: AbortSignal,
313
+ private readonly batchRequest?: { id: string; flush: boolean },
314
+ ) {}
315
+
316
+ #getFile(path: string): Bun.BunFile {
317
+ if (this.#fileCache[path]) {
318
+ return this.#fileCache[path];
319
+ }
320
+ const file = Bun.file(path);
321
+ this.#fileCache[path] = file;
322
+ return file;
323
+ }
324
+
325
+ async exists(path: string): Promise<boolean> {
326
+ return this.#getFile(path).exists();
327
+ }
328
+
329
+ async read(path: string): Promise<string> {
330
+ return this.#getFile(path).text();
331
+ }
332
+
333
+ async readBinary(path: string): Promise<Uint8Array> {
334
+ const buffer = await this.#getFile(path).arrayBuffer();
335
+ return new Uint8Array(buffer);
336
+ }
337
+
338
+ async write(path: string, content: string): Promise<void> {
339
+ const file = this.#getFile(path);
340
+ const result = await this.writethrough(path, content, this.signal, file, this.batchRequest);
341
+ if (result) {
342
+ this.#lastDiagnostics = result;
343
+ }
344
+ }
345
+
346
+ async delete(path: string): Promise<void> {
347
+ await this.#getFile(path).unlink();
348
+ }
349
+
350
+ async mkdir(path: string): Promise<void> {
351
+ await fs.mkdir(path, { recursive: true });
352
+ }
353
+
354
+ getDiagnostics(): FileDiagnosticsResult | undefined {
355
+ return this.#lastDiagnostics;
356
+ }
357
+ }
358
+
359
+ function mergeDiagnosticsWithWarnings(
360
+ diagnostics: FileDiagnosticsResult | undefined,
361
+ warnings: string[],
362
+ ): FileDiagnosticsResult | undefined {
363
+ if (warnings.length === 0) return diagnostics;
364
+ const warningMessages = warnings.map(warning => `patch: ${warning}`);
365
+ if (!diagnostics) {
366
+ return {
367
+ server: "patch",
368
+ messages: warningMessages,
369
+ summary: `Patch warnings: ${warnings.length}`,
370
+ errored: false,
371
+ };
372
+ }
373
+ return {
374
+ ...diagnostics,
375
+ messages: [...warningMessages, ...diagnostics.messages],
376
+ summary: `${diagnostics.summary}; Patch warnings: ${warnings.length}`,
377
+ };
378
+ }
379
+
380
+ // ═══════════════════════════════════════════════════════════════════════════
381
+ // Tool Class
382
+ // ═══════════════════════════════════════════════════════════════════════════
383
+
384
+ type TInput = typeof replaceEditSchema | typeof patchEditSchema | typeof hashlineEditSchema;
385
+
386
+ export type EditMode = "replace" | "patch" | "hashline";
387
+
388
+ export const DEFAULT_EDIT_MODE: EditMode = "patch";
389
+
390
+ export function normalizeEditMode(mode?: string | null): EditMode | null {
391
+ switch (mode) {
392
+ case "replace":
393
+ return "replace";
394
+ case "patch":
395
+ return "patch";
396
+ case "hashline":
397
+ return "hashline";
398
+ default:
399
+ return null;
400
+ }
401
+ }
402
+
403
+ /**
404
+ * Edit tool implementation.
405
+ *
406
+ * Creates replace-mode, patch-mode, or hashline-mode behavior based on session settings.
407
+ */
408
+ export class EditTool implements AgentTool<TInput> {
409
+ readonly name = "edit";
410
+ readonly label = "Edit";
411
+ readonly nonAbortable = true;
412
+ readonly concurrency = "exclusive";
413
+
414
+ readonly #allowFuzzy: boolean;
415
+ readonly #fuzzyThreshold: number;
416
+ readonly #writethrough: WritethroughCallback;
417
+ readonly #editMode?: EditMode | null;
418
+
419
+ constructor(private readonly session: ToolSession) {
420
+ const {
421
+ ARCANE_EDIT_FUZZY: editFuzzy = "auto",
422
+ ARCANE_EDIT_FUZZY_THRESHOLD: editFuzzyThreshold = "auto",
423
+ ARCANE_EDIT_VARIANT: envEditVariant = "auto",
424
+ } = Bun.env;
425
+
426
+ if (envEditVariant && envEditVariant !== "auto") {
427
+ const editMode = normalizeEditMode(envEditVariant);
428
+ if (!editMode) {
429
+ throw new Error(`Invalid ARCANE_EDIT_VARIANT: ${envEditVariant}`);
430
+ }
431
+ this.#editMode = editMode;
432
+ }
433
+
434
+ switch (editFuzzy) {
435
+ case "true":
436
+ case "1":
437
+ this.#allowFuzzy = true;
438
+ break;
439
+ case "false":
440
+ case "0":
441
+ this.#allowFuzzy = false;
442
+ break;
443
+ case "auto":
444
+ this.#allowFuzzy = session.settings.get("edit.fuzzyMatch");
445
+ break;
446
+ default:
447
+ throw new Error(`Invalid ARCANE_EDIT_FUZZY: ${editFuzzy}`);
448
+ }
449
+ switch (editFuzzyThreshold) {
450
+ case "auto":
451
+ this.#fuzzyThreshold = session.settings.get("edit.fuzzyThreshold");
452
+ break;
453
+ default:
454
+ this.#fuzzyThreshold = parseFloat(editFuzzyThreshold);
455
+ if (Number.isNaN(this.#fuzzyThreshold) || this.#fuzzyThreshold < 0 || this.#fuzzyThreshold > 1) {
456
+ throw new Error(`Invalid ARCANE_EDIT_FUZZY_THRESHOLD: ${editFuzzyThreshold}`);
457
+ }
458
+ break;
459
+ }
460
+
461
+ const enableLsp = session.enableLsp ?? true;
462
+ const enableDiagnostics = enableLsp && session.settings.get("lsp.diagnosticsOnEdit");
463
+ const enableFormat = enableLsp && session.settings.get("lsp.formatOnWrite");
464
+ this.#writethrough = enableLsp
465
+ ? createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics })
466
+ : writethroughNoop;
467
+ }
468
+
469
+ /**
470
+ * Determine edit mode dynamically based on current model.
471
+ * This is re-evaluated on each access so tool definitions stay current when model changes.
472
+ */
473
+ get mode(): EditMode {
474
+ if (this.#editMode) return this.#editMode;
475
+ const activeModel = this.session.getActiveModelString?.();
476
+ const editVariant =
477
+ this.session.settings.getEditVariantForModel(activeModel) ??
478
+ normalizeEditMode(this.session.settings.get("edit.mode"));
479
+ return editVariant ?? DEFAULT_EDIT_MODE;
480
+ }
481
+
482
+ /**
483
+ * Dynamic description based on current edit mode (which depends on current model).
484
+ */
485
+ get description(): string {
486
+ switch (this.mode) {
487
+ case "patch":
488
+ return renderPromptTemplate(patchDescription);
489
+ case "hashline":
490
+ return renderPromptTemplate(hashlineDescription, { allowReplaceText: HL_REPLACE_ENABLED });
491
+ default:
492
+ return renderPromptTemplate(replaceDescription);
493
+ }
494
+ }
495
+
496
+ /**
497
+ * Dynamic parameters schema based on current edit mode (which depends on current model).
498
+ */
499
+ get parameters(): TInput {
500
+ switch (this.mode) {
501
+ case "patch":
502
+ return patchEditSchema;
503
+ case "hashline":
504
+ return hashlineEditSchema;
505
+ default:
506
+ return replaceEditSchema;
507
+ }
508
+ }
509
+
510
+ async execute(
511
+ _toolCallId: string,
512
+ params: ReplaceParams | PatchParams | HashlineParams,
513
+ signal?: AbortSignal,
514
+ _onUpdate?: AgentToolUpdateCallback<EditToolDetails, TInput>,
515
+ context?: AgentToolContext,
516
+ ): Promise<AgentToolResult<EditToolDetails, TInput>> {
517
+ const batchRequest = getLspBatchRequest(context?.toolCall);
518
+
519
+ // ─────────────────────────────────────────────────────────────────
520
+ // Hashline mode execution
521
+ // ─────────────────────────────────────────────────────────────────
522
+ if (this.mode === "hashline") {
523
+ const { path, edits, delete: deleteFile, rename } = params as HashlineParams;
524
+
525
+ if (path.endsWith(".ipynb") && edits?.length > 0) {
526
+ throw new Error("Cannot edit Jupyter notebooks with the Edit tool. Use the NotebookEdit tool instead.");
527
+ }
528
+
529
+ const absolutePath = resolveToCwd(path, this.session.cwd);
530
+ const resolvedRename = rename ? resolveToCwd(rename, this.session.cwd) : undefined;
531
+ const file = Bun.file(absolutePath);
532
+
533
+ if (deleteFile) {
534
+ if (await file.exists()) {
535
+ await file.unlink();
536
+ }
537
+ invalidateFsScanAfterDelete(absolutePath);
538
+ return {
539
+ content: [{ type: "text", text: `Deleted ${path}` }],
540
+ details: {
541
+ diff: "",
542
+ op: "delete",
543
+ meta: outputMeta().get(),
544
+ },
545
+ };
546
+ }
547
+
548
+ if (!(await file.exists())) {
549
+ const content: string[] = [];
550
+ for (const edit of edits) {
551
+ switch (edit.op) {
552
+ case "append": {
553
+ if (edit.after) {
554
+ throw new Error(`File not found: ${path}`);
555
+ }
556
+ content.push(...hashlineParseContent(edit.content));
557
+ break;
558
+ }
559
+ case "prepend": {
560
+ if (edit.before) {
561
+ throw new Error(`File not found: ${path}`);
562
+ }
563
+ content.unshift(...hashlineParseContent(edit.content));
564
+ break;
565
+ }
566
+ default: {
567
+ throw new Error(`File not found: ${path}`);
568
+ }
569
+ }
570
+ }
571
+ await file.write(content.join("\n"));
572
+ return {
573
+ content: [{ type: "text", text: `Created ${path}` }],
574
+ details: {
575
+ diff: "",
576
+ op: "create",
577
+ meta: outputMeta().get(),
578
+ },
579
+ };
580
+ }
581
+
582
+ const anchorEdits: HashlineEdit[] = [];
583
+ const replaceEdits: ReplaceTextEdit[] = [];
584
+ for (const edit of edits) {
585
+ switch (edit.op) {
586
+ case "set": {
587
+ const { tag, content } = edit;
588
+ anchorEdits.push({ op: "set", tag: parseTag(tag), content: hashlineParseContent(content) });
589
+ break;
590
+ }
591
+ case "replace": {
592
+ const { first, last, content } = edit;
593
+ anchorEdits.push({
594
+ op: "replace",
595
+ first: parseTag(first),
596
+ last: parseTag(last),
597
+ content: hashlineParseContent(content),
598
+ });
599
+ break;
600
+ }
601
+ case "append": {
602
+ const { after, content } = edit;
603
+ anchorEdits.push({
604
+ op: "append",
605
+ ...(after ? { after: parseTag(after) } : {}),
606
+ content: hashlineParseContent(content),
607
+ });
608
+ break;
609
+ }
610
+ case "prepend": {
611
+ const { before, content } = edit;
612
+ anchorEdits.push({
613
+ op: "prepend",
614
+ ...(before ? { before: parseTag(before) } : {}),
615
+ content: hashlineParseContent(content),
616
+ });
617
+ break;
618
+ }
619
+ case "insert": {
620
+ const { before, after, content } = edit;
621
+ if (before && !after) {
622
+ anchorEdits.push({
623
+ op: "prepend",
624
+ before: parseTag(before),
625
+ content: hashlineParseContent(content),
626
+ });
627
+ } else if (after && !before) {
628
+ anchorEdits.push({
629
+ op: "append",
630
+ after: parseTag(after),
631
+ content: hashlineParseContent(content),
632
+ });
633
+ } else if (before && after) {
634
+ anchorEdits.push({
635
+ op: "insert",
636
+ before: parseTag(before),
637
+ after: parseTag(after),
638
+ content: hashlineParseContent(content),
639
+ });
640
+ } else {
641
+ throw new Error(`Insert must have both before and after tags.`);
642
+ }
643
+ break;
644
+ }
645
+ case "replaceText": {
646
+ const { old_text, new_text, all } = edit;
647
+ replaceEdits.push({
648
+ op: "replaceText",
649
+ old_text: old_text,
650
+ new_text: hashlineParseContentString(new_text),
651
+ all: all ?? false,
652
+ });
653
+ break;
654
+ }
655
+ default:
656
+ throw new Error(`Invalid edit operation: ${JSON.stringify(edit)}`);
657
+ }
658
+ }
659
+
660
+ const rawContent = await file.text();
661
+ const { bom, text: content } = stripBom(rawContent);
662
+ const originalEnding = detectLineEnding(content);
663
+ const originalNormalized = normalizeToLF(content);
664
+ let normalizedContent = originalNormalized;
665
+
666
+ // Apply anchor-based edits first (set, set_range, insert)
667
+ const anchorResult = applyHashlineEdits(normalizedContent, anchorEdits);
668
+ normalizedContent = anchorResult.content;
669
+
670
+ // Apply content-replace edits (substr-style fuzzy replace)
671
+ for (const r of replaceEdits) {
672
+ if (r.old_text.length === 0) {
673
+ throw new Error("old_text must not be empty.");
674
+ }
675
+ const rep = replaceText(normalizedContent, r.old_text, r.new_text, {
676
+ fuzzy: this.#allowFuzzy,
677
+ all: r.all ?? false,
678
+ threshold: this.#fuzzyThreshold,
679
+ });
680
+ normalizedContent = rep.content;
681
+ }
682
+
683
+ const result = {
684
+ content: normalizedContent,
685
+ firstChangedLine: anchorResult.firstChangedLine,
686
+ warnings: anchorResult.warnings,
687
+ noopEdits: anchorResult.noopEdits,
688
+ };
689
+ if (originalNormalized === result.content && !rename) {
690
+ let diagnostic = `No changes made to ${path}. The edits produced identical content.`;
691
+ if (result.noopEdits && result.noopEdits.length > 0) {
692
+ const details = result.noopEdits
693
+ .map(
694
+ e =>
695
+ `Edit ${e.editIndex}: replacement for ${e.loc} is identical to current content:\n ${e.loc}| ${e.currentContent}`,
696
+ )
697
+ .join("\n");
698
+ diagnostic += `\n${details}`;
699
+ diagnostic +=
700
+ "\nYour content must differ from what the file already contains. Re-read the file to see the current state.";
701
+ } else {
702
+ // Edits were not literally identical but heuristics normalized them back
703
+ const lines = result.content.split("\n");
704
+ const targetLines: string[] = [];
705
+ const refs: LineTag[] = [];
706
+ for (const edit of anchorEdits) {
707
+ refs.length = 0;
708
+ switch (edit.op) {
709
+ case "set":
710
+ refs.push(edit.tag);
711
+ break;
712
+ case "replace":
713
+ refs.push(edit.first, edit.last);
714
+ break;
715
+ case "append":
716
+ if (edit.after) refs.push(edit.after);
717
+ break;
718
+ case "prepend":
719
+ if (edit.before) refs.push(edit.before);
720
+ break;
721
+ case "insert":
722
+ refs.push(edit.after, edit.before);
723
+ break;
724
+ default:
725
+ break;
726
+ }
727
+
728
+ for (const ref of refs) {
729
+ try {
730
+ if (ref.line >= 1 && ref.line <= lines.length) {
731
+ const lineContent = lines[ref.line - 1];
732
+ const hash = computeLineHash(ref.line, lineContent);
733
+ targetLines.push(`${ref.line}#${hash}:${lineContent}`);
734
+ }
735
+ } catch {
736
+ /* skip malformed refs */
737
+ }
738
+ }
739
+ }
740
+ if (targetLines.length > 0) {
741
+ const preview = [...new Set(targetLines)].slice(0, 5).join("\n");
742
+ diagnostic += `\nThe file currently contains these lines:\n${preview}\nYour edits were normalized back to the original content (whitespace-only differences are preserved as-is). Ensure your replacement changes actual code, not just formatting.`;
743
+ }
744
+ }
745
+ throw new Error(diagnostic);
746
+ }
747
+
748
+ const finalContent = bom + restoreLineEndings(result.content, originalEnding);
749
+ const writePath = resolvedRename ?? absolutePath;
750
+ saveForUndo(absolutePath, rawContent);
751
+ const diagnostics = await this.#writethrough(
752
+ writePath,
753
+ finalContent,
754
+ signal,
755
+ Bun.file(writePath),
756
+ batchRequest,
757
+ );
758
+ if (resolvedRename && resolvedRename !== absolutePath) {
759
+ await file.unlink();
760
+ invalidateFsScanAfterRename(absolutePath, resolvedRename);
761
+ } else {
762
+ invalidateFsScanAfterWrite(absolutePath);
763
+ }
764
+ const diffResult = generateDiffString(originalNormalized, result.content);
765
+
766
+ const normative = buildNormativeUpdateInput({
767
+ path,
768
+ ...(rename ? { rename } : {}),
769
+ oldContent: rawContent,
770
+ newContent: finalContent,
771
+ });
772
+
773
+ const meta = outputMeta()
774
+ .diagnostics(diagnostics?.summary ?? "", diagnostics?.messages ?? [])
775
+ .get();
776
+
777
+ const resultText = rename ? `Updated and moved ${path} to ${rename}` : `Updated ${path}`;
778
+ return {
779
+ content: [
780
+ {
781
+ type: "text",
782
+ text: `${resultText}${result.warnings?.length ? `\n\nWarnings:\n${result.warnings.join("\n")}` : ""}`,
783
+ },
784
+ ],
785
+ details: {
786
+ diff: diffResult.diff,
787
+ firstChangedLine: result.firstChangedLine ?? diffResult.firstChangedLine,
788
+ diagnostics,
789
+ op: "update",
790
+ rename,
791
+ meta,
792
+ },
793
+ $normative: normative,
794
+ };
795
+ }
796
+
797
+ // ─────────────────────────────────────────────────────────────────
798
+ // Patch mode execution
799
+ // ─────────────────────────────────────────────────────────────────
800
+ if (this.mode === "patch") {
801
+ const { path, op: rawOp, rename, diff } = params as PatchParams;
802
+
803
+ // Normalize unrecognized operations to "update"
804
+ const op: Operation = rawOp === "create" || rawOp === "delete" ? rawOp : "update";
805
+
806
+ const resolvedPath = resolveToCwd(path, this.session.cwd);
807
+ const resolvedRename = rename ? resolveToCwd(rename, this.session.cwd) : undefined;
808
+
809
+ if (path.endsWith(".ipynb")) {
810
+ throw new Error("Cannot edit Jupyter notebooks with the Edit tool. Use the NotebookEdit tool instead.");
811
+ }
812
+ if (rename?.endsWith(".ipynb")) {
813
+ throw new Error("Cannot edit Jupyter notebooks with the Edit tool. Use the NotebookEdit tool instead.");
814
+ }
815
+
816
+ const input: PatchInput = { path: resolvedPath, op, rename: resolvedRename, diff };
817
+ const fs = new LspFileSystem(this.#writethrough, signal, batchRequest);
818
+ const result = await applyPatch(input, {
819
+ cwd: this.session.cwd,
820
+ fs,
821
+ fuzzyThreshold: this.#fuzzyThreshold,
822
+ allowFuzzy: this.#allowFuzzy,
823
+ });
824
+ if (result.change.oldContent !== undefined) {
825
+ saveForUndo(resolvedPath, result.change.oldContent);
826
+ }
827
+ if (resolvedRename) {
828
+ invalidateFsScanAfterRename(resolvedPath, resolvedRename);
829
+ } else if (result.change.type === "delete") {
830
+ invalidateFsScanAfterDelete(resolvedPath);
831
+ } else {
832
+ invalidateFsScanAfterWrite(resolvedPath);
833
+ }
834
+ const effRename = result.change.newPath ? rename : undefined;
835
+
836
+ // Generate diff for display
837
+ let diffResult = { diff: "", firstChangedLine: undefined as number | undefined };
838
+ if (result.change.type === "update" && result.change.oldContent && result.change.newContent) {
839
+ const normalizedOld = normalizeToLF(stripBom(result.change.oldContent).text);
840
+ const normalizedNew = normalizeToLF(stripBom(result.change.newContent).text);
841
+ diffResult = generateUnifiedDiffString(normalizedOld, normalizedNew);
842
+ }
843
+
844
+ let resultText: string;
845
+ switch (result.change.type) {
846
+ case "create":
847
+ resultText = `Created ${path}`;
848
+ break;
849
+ case "delete":
850
+ resultText = `Deleted ${path}`;
851
+ break;
852
+ case "update":
853
+ resultText = effRename ? `Updated and moved ${path} to ${effRename}` : `Updated ${path}`;
854
+ break;
855
+ }
856
+
857
+ let diagnostics = fs.getDiagnostics();
858
+ if (op === "delete" && batchRequest?.flush) {
859
+ const flushedDiagnostics = await flushLspWritethroughBatch(batchRequest.id, this.session.cwd, signal);
860
+ diagnostics ??= flushedDiagnostics;
861
+ }
862
+ const patchWarnings = result.warnings ?? [];
863
+ const mergedDiagnostics = mergeDiagnosticsWithWarnings(diagnostics, patchWarnings);
864
+
865
+ const meta = outputMeta()
866
+ .diagnostics(mergedDiagnostics?.summary ?? "", mergedDiagnostics?.messages ?? [])
867
+ .get();
868
+
869
+ return {
870
+ content: [{ type: "text", text: resultText }],
871
+ details: {
872
+ diff: diffResult.diff,
873
+ firstChangedLine: diffResult.firstChangedLine,
874
+ diagnostics: mergedDiagnostics,
875
+ op,
876
+ rename: effRename,
877
+ meta,
878
+ },
879
+ };
880
+ }
881
+
882
+ // ─────────────────────────────────────────────────────────────────
883
+ // Replace mode execution
884
+ // ─────────────────────────────────────────────────────────────────
885
+ const { path, old_text, new_text, all } = params as ReplaceParams;
886
+
887
+ if (path.endsWith(".ipynb")) {
888
+ throw new Error("Cannot edit Jupyter notebooks with the Edit tool. Use the NotebookEdit tool instead.");
889
+ }
890
+
891
+ if (old_text.length === 0) {
892
+ throw new Error("old_text must not be empty.");
893
+ }
894
+
895
+ const absolutePath = resolveToCwd(path, this.session.cwd);
896
+ const file = Bun.file(absolutePath);
897
+
898
+ if (!(await file.exists())) {
899
+ throw new Error(`File not found: ${path}`);
900
+ }
901
+
902
+ const rawContent = await file.text();
903
+ const { bom, text: content } = stripBom(rawContent);
904
+ const originalEnding = detectLineEnding(content);
905
+ const normalizedContent = normalizeToLF(content);
906
+ const normalizedOldText = normalizeToLF(old_text);
907
+ const normalizedNewText = normalizeToLF(new_text);
908
+
909
+ const result = replaceText(normalizedContent, normalizedOldText, normalizedNewText, {
910
+ fuzzy: this.#allowFuzzy,
911
+ all: all ?? false,
912
+ threshold: this.#fuzzyThreshold,
913
+ });
914
+
915
+ if (result.count === 0) {
916
+ // Get error details
917
+ const matchOutcome = findMatch(normalizedContent, normalizedOldText, {
918
+ allowFuzzy: this.#allowFuzzy,
919
+ threshold: this.#fuzzyThreshold,
920
+ });
921
+
922
+ if (matchOutcome.occurrences && matchOutcome.occurrences > 1) {
923
+ const previews = matchOutcome.occurrencePreviews?.join("\n\n") ?? "";
924
+ const moreMsg = matchOutcome.occurrences > 5 ? ` (showing first 5 of ${matchOutcome.occurrences})` : "";
925
+ throw new Error(
926
+ `Found ${matchOutcome.occurrences} occurrences in ${path}${moreMsg}:\n\n${previews}\n\n` +
927
+ `Add more context lines to disambiguate.`,
928
+ );
929
+ }
930
+
931
+ throw new EditMatchError(path, normalizedOldText, matchOutcome.closest, {
932
+ allowFuzzy: this.#allowFuzzy,
933
+ threshold: this.#fuzzyThreshold,
934
+ fuzzyMatches: matchOutcome.fuzzyMatches,
935
+ });
936
+ }
937
+
938
+ if (normalizedContent === result.content) {
939
+ throw new Error(
940
+ `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.`,
941
+ );
942
+ }
943
+
944
+ const finalContent = bom + restoreLineEndings(result.content, originalEnding);
945
+ saveForUndo(absolutePath, rawContent);
946
+ const diagnostics = await this.#writethrough(absolutePath, finalContent, signal, file, batchRequest);
947
+ invalidateFsScanAfterWrite(absolutePath);
948
+ const diffResult = generateDiffString(normalizedContent, result.content);
949
+
950
+ const resultText =
951
+ result.count > 1
952
+ ? `Successfully replaced ${result.count} occurrences in ${path}.`
953
+ : `Successfully replaced text in ${path}.`;
954
+
955
+ const meta = outputMeta()
956
+ .diagnostics(diagnostics?.summary ?? "", diagnostics?.messages ?? [])
957
+ .get();
958
+
959
+ return {
960
+ content: [{ type: "text", text: resultText }],
961
+ details: { diff: diffResult.diff, firstChangedLine: diffResult.firstChangedLine, diagnostics, meta },
962
+ };
963
+ }
964
+ }