rovecode 0.3.3 → 0.4.0-beta.3

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 (495) hide show
  1. package/CHANGELOG.md +527 -0
  2. package/README.md +46 -48
  3. package/bin/rovecode.js +24 -0
  4. package/dist/cli/app-j6gn14w3.js +2 -0
  5. package/dist/cli/ask-user-cwstt8fz.js +2 -0
  6. package/dist/cli/auth-login-9bbp9915.js +2 -0
  7. package/dist/cli/auth-m8p9grty.js +2 -0
  8. package/dist/cli/bench-16zqdms5.js +9 -0
  9. package/dist/cli/catalog-1xchffa4.js +2 -0
  10. package/dist/cli/cli-1n1zb64f.js +2 -0
  11. package/dist/cli/client-2t9gjkck.js +2 -0
  12. package/dist/cli/commands-exafvm2b.js +2 -0
  13. package/dist/cli/connect-6zde0kn3.js +2 -0
  14. package/dist/cli/context-cmd-5t43wgqt.js +2 -0
  15. package/dist/cli/context-report-kt01pw8y.js +2 -0
  16. package/dist/cli/count-remote-ap7x3vh6.js +2 -0
  17. package/dist/cli/design-ne5zszyh.js +2 -0
  18. package/dist/cli/dispatch-2r5myxye.js +2 -0
  19. package/dist/cli/doctor-ws4fh4tn.js +3 -0
  20. package/dist/cli/executor-bdrjn634.js +2 -0
  21. package/dist/cli/export-1mxb9g5p.js +2 -0
  22. package/dist/cli/files-g104xghh.js +2 -0
  23. package/dist/cli/gauntlet-07xrjpj7.js +2 -0
  24. package/dist/cli/gauntlet-runner-xvy64436.js +10 -0
  25. package/dist/cli/gauntlet-wave3-jm91yt5w.js +5 -0
  26. package/dist/cli/gauntlet-wave4-r13py7p1.js +14 -0
  27. package/dist/cli/hashline-znvrat11.js +2 -0
  28. package/dist/cli/http-xafw6fsh.js +143 -0
  29. package/dist/cli/index-1sgjm25y.js +2 -0
  30. package/dist/cli/init-g2m0tn4m.js +51 -0
  31. package/dist/cli/install-avaqjjqq.js +2 -0
  32. package/dist/cli/loop-mmpfft01.js +2 -0
  33. package/dist/cli/main-0904f6ps.js +5 -0
  34. package/dist/cli/main-0ab9fc26.js +9 -0
  35. package/dist/cli/main-0jys2ccn.js +3 -0
  36. package/dist/cli/main-0mtcdbs7.js +3 -0
  37. package/dist/cli/main-0z1w2zsg.js +3 -0
  38. package/dist/cli/main-1dchs7xv.js +18 -0
  39. package/dist/cli/main-1ereejm1.js +3 -0
  40. package/dist/cli/main-1k1kw6b5.js +3 -0
  41. package/dist/cli/main-27y4sm2k.js +38 -0
  42. package/dist/cli/main-2wwjex5j.js +58 -0
  43. package/dist/cli/main-2yeveeve.js +6 -0
  44. package/dist/cli/main-2yfck9b5.js +3 -0
  45. package/dist/cli/main-2zmzgkwh.js +3 -0
  46. package/dist/cli/main-351pz3z7.js +7 -0
  47. package/dist/cli/main-3gjqfh7a.js +6 -0
  48. package/dist/cli/main-3nf3kgve.js +3 -0
  49. package/dist/cli/main-3pjrb2hd.js +3 -0
  50. package/dist/cli/main-3rxcvgna.js +19 -0
  51. package/dist/cli/main-4b3jgy66.js +19 -0
  52. package/dist/cli/main-4wndhjdc.js +7 -0
  53. package/dist/cli/main-4xcmvxnk.js +3 -0
  54. package/dist/cli/main-5tbz0wbz.js +4 -0
  55. package/dist/cli/main-5ywnwthm.js +3 -0
  56. package/dist/cli/main-6b62vkz0.js +14 -0
  57. package/dist/cli/main-6dnk69vp.js +3 -0
  58. package/dist/cli/main-6genrmhs.js +136 -0
  59. package/dist/cli/main-73g7eff4.js +15 -0
  60. package/dist/cli/main-7c5thhjd.js +5 -0
  61. package/dist/cli/main-7rn6bqje.js +3 -0
  62. package/dist/cli/main-80haw7qk.js +4 -0
  63. package/dist/cli/main-875s60s2.js +4 -0
  64. package/dist/cli/main-8kjxbpw4.js +8 -0
  65. package/dist/cli/main-90ds1z4e.js +10 -0
  66. package/dist/cli/main-9etavkew.js +3 -0
  67. package/dist/cli/main-a9njrkk1.js +3 -0
  68. package/dist/cli/main-aecrjq2d.js +12 -0
  69. package/dist/cli/main-ck9asesq.js +9 -0
  70. package/dist/cli/main-cta9racd.js +4 -0
  71. package/dist/cli/main-ddv7j2ag.js +3 -0
  72. package/dist/cli/main-dfreez27.js +10 -0
  73. package/dist/cli/main-f7rw7des.js +3 -0
  74. package/dist/cli/main-ggcn7rd7.js +5 -0
  75. package/dist/cli/main-gzkmycnv.js +3 -0
  76. package/dist/cli/main-hq51jg8v.js +18 -0
  77. package/dist/cli/main-jft389w9.js +8 -0
  78. package/dist/cli/main-k1eqkg83.js +3 -0
  79. package/dist/cli/main-k2y8a2aw.js +9 -0
  80. package/dist/cli/main-kcpbykxz.js +4 -0
  81. package/dist/cli/main-kd488vje.js +22 -0
  82. package/dist/cli/main-kh32yvgk.js +5 -0
  83. package/dist/cli/main-kqxnqjnv.js +25 -0
  84. package/dist/cli/main-kyn0xnsg.js +3 -0
  85. package/dist/cli/main-m1kk6fp5.js +21 -0
  86. package/dist/cli/main-mv40pcr2.js +4 -0
  87. package/dist/cli/main-n0t3973w.js +3 -0
  88. package/dist/cli/main-nqveez48.js +4 -0
  89. package/dist/cli/main-pknhvrmj.js +3 -0
  90. package/dist/cli/main-pn1w7a7j.js +3 -0
  91. package/dist/cli/main-prxxs70n.js +4 -0
  92. package/dist/cli/main-q3vsesf9.js +3 -0
  93. package/dist/cli/main-qsevpgsv.js +3 -0
  94. package/dist/cli/main-rdgdw24b.js +25 -0
  95. package/dist/cli/main-rfth4tbm.js +16 -0
  96. package/dist/cli/main-rg0wn0xf.js +5 -0
  97. package/dist/cli/main-sdmxhtv8.js +4 -0
  98. package/dist/cli/main-skbp13js.js +18 -0
  99. package/dist/cli/main-t4xnd213.js +7 -0
  100. package/dist/cli/main-vqak588n.js +4 -0
  101. package/dist/cli/main-w2n1303f.js +9 -0
  102. package/dist/cli/main-wbrdspr2.js +5 -0
  103. package/dist/cli/main-wsrg79c1.js +7 -0
  104. package/dist/cli/main-x4r0fne4.js +5 -0
  105. package/dist/cli/main-xea2f3tn.js +6 -0
  106. package/dist/cli/main-xg704a3c.js +3 -0
  107. package/dist/cli/main-xvnrabfp.js +16 -0
  108. package/dist/cli/main-xy53xf0r.js +4 -0
  109. package/dist/cli/main-y1fqy60y.js +3 -0
  110. package/dist/cli/main-yn8cd281.js +34 -0
  111. package/dist/cli/main-yr0ksc0h.js +4 -0
  112. package/dist/cli/main-z2ex2vyf.js +4 -0
  113. package/dist/cli/main-z3aayzvq.js +3 -0
  114. package/dist/cli/main-zaqh35jg.js +3 -0
  115. package/dist/cli/main-zc2e8e46.js +4 -0
  116. package/dist/cli/main-zzrfw6cf.js +13 -0
  117. package/dist/cli/main.js +280 -0
  118. package/dist/cli/market-cmd-e14kmx9n.js +5 -0
  119. package/dist/cli/mcp-login-wq7ktdek.js +2 -0
  120. package/dist/cli/mcp-market-cmd-9mg3jecy.js +2 -0
  121. package/dist/cli/notify-b7qc0cjb.js +2 -0
  122. package/dist/cli/oauth-z8whcgfx.js +2 -0
  123. package/dist/cli/output-b3ewj3ps.js +16 -0
  124. package/dist/cli/profiles-6mr5he5e.js +2 -0
  125. package/dist/cli/provider-config-g7j42q8x.js +2 -0
  126. package/dist/cli/provider-jr1y8vvm.js +2 -0
  127. package/dist/cli/registry-s8yk86g0.js +2 -0
  128. package/dist/cli/registry-t6p8d4mn.js +2 -0
  129. package/dist/cli/repl-bajwe1mh.js +11 -0
  130. package/dist/cli/resume-rwn9nz7y.js +2 -0
  131. package/dist/cli/run-flags-nah7ndpt.js +2 -0
  132. package/dist/cli/runtime-n7gafzhb.js +2 -0
  133. package/dist/cli/sandbox-config-emdy18x4.js +2 -0
  134. package/dist/cli/server-b0nvs2bn.js +5 -0
  135. package/dist/cli/session-arg-y75wd4kj.js +2 -0
  136. package/dist/cli/session-j62evmjq.js +2 -0
  137. package/dist/cli/sessions-cmd-tsnwz0ns.js +7 -0
  138. package/dist/cli/settings-df10wfez.js +2 -0
  139. package/dist/cli/setup-jzvv72fg.js +2 -0
  140. package/dist/cli/sextant-smoke-37m81ke6.js +5 -0
  141. package/dist/cli/skills-cmd-gjxnxnhx.js +2 -0
  142. package/dist/cli/smoke-p7748apt.js +8 -0
  143. package/dist/cli/start-chat-s4st3mm0.js +12 -0
  144. package/dist/cli/stream-gmeyewds.js +2 -0
  145. package/dist/cli/task-gh0kkp3n.js +2 -0
  146. package/dist/cli/tasks-z1kfpe8e.js +2 -0
  147. package/dist/cli/thinking-0eqkrz6t.js +2 -0
  148. package/dist/cli/todo-5brcrt9m.js +2 -0
  149. package/dist/cli/tools-7pzm0vj9.js +2 -0
  150. package/dist/cli/tools-s635p6s8.js +2 -0
  151. package/dist/cli/trust-cmd-cjav8zgm.js +2 -0
  152. package/dist/cli/update-check-pt31bm2f.js +2 -0
  153. package/dist/cli/update-cmd-tk131s9t.js +2 -0
  154. package/dist/cli/voice-56nabd8d.js +2 -0
  155. package/dist/cli/webfetch-xd8q596m.js +2 -0
  156. package/dist/cli/websearch-5hkf98k1.js +2 -0
  157. package/dist/cli/workflow-cmd-cy3cvzjp.js +4 -0
  158. package/dist/cli/workspace-q10g5z3e.js +2 -0
  159. package/dist/lib/index.js +62 -0
  160. package/dist/lib/models-index.json +1 -0
  161. package/dist/lib/plugins.js +55 -0
  162. package/dist/lib/providers.js +17 -0
  163. package/dist/lib/public-api.js +20 -0
  164. package/dist/lib/sdk.js +360 -0
  165. package/package.json +84 -72
  166. package/bin/rovecode.ts +0 -21
  167. package/src/acp/server.ts +0 -374
  168. package/src/cli/auth-login.ts +0 -122
  169. package/src/cli/connect.ts +0 -244
  170. package/src/cli/context-cmd.ts +0 -199
  171. package/src/cli/dispatch.ts +0 -82
  172. package/src/cli/doctor.ts +0 -362
  173. package/src/cli/export.ts +0 -276
  174. package/src/cli/help.ts +0 -293
  175. package/src/cli/is-tui-invocation.ts +0 -8
  176. package/src/cli/main.ts +0 -583
  177. package/src/cli/market-cmd.ts +0 -658
  178. package/src/cli/mcp-login.ts +0 -141
  179. package/src/cli/mcp-market-cmd.ts +0 -302
  180. package/src/cli/output.ts +0 -382
  181. package/src/cli/repl.ts +0 -250
  182. package/src/cli/repomap-root.ts +0 -14
  183. package/src/cli/resume.ts +0 -57
  184. package/src/cli/run-flags.ts +0 -43
  185. package/src/cli/run-limits.ts +0 -78
  186. package/src/cli/runtime.ts +0 -931
  187. package/src/cli/session-arg.ts +0 -30
  188. package/src/cli/sessions-cmd.ts +0 -145
  189. package/src/cli/setup.ts +0 -153
  190. package/src/cli/skills-cmd.ts +0 -194
  191. package/src/cli/start-chat.ts +0 -65
  192. package/src/cli/trust-cmd.ts +0 -52
  193. package/src/coding/bash.ts +0 -148
  194. package/src/coding/checkpoints.ts +0 -327
  195. package/src/coding/diff.ts +0 -138
  196. package/src/coding/files.ts +0 -341
  197. package/src/coding/hashline.ts +0 -274
  198. package/src/coding/lsp-gate.ts +0 -254
  199. package/src/coding/lsp-servers.ts +0 -147
  200. package/src/coding/lsp.ts +0 -283
  201. package/src/coding/repomap-cache.ts +0 -99
  202. package/src/coding/repomap-files.ts +0 -192
  203. package/src/coding/repomap.ts +0 -481
  204. package/src/core/agents.ts +0 -255
  205. package/src/core/compaction.ts +0 -259
  206. package/src/core/config.ts +0 -289
  207. package/src/core/context-report.ts +0 -228
  208. package/src/core/context.ts +0 -60
  209. package/src/core/count-remote.ts +0 -107
  210. package/src/core/execpolicy-rules.ts +0 -196
  211. package/src/core/execpolicy.ts +0 -385
  212. package/src/core/executor.ts +0 -454
  213. package/src/core/guardrails.ts +0 -400
  214. package/src/core/hooks.ts +0 -411
  215. package/src/core/images.ts +0 -230
  216. package/src/core/intro.ts +0 -266
  217. package/src/core/loop.ts +0 -567
  218. package/src/core/modes.ts +0 -372
  219. package/src/core/orchestrator.ts +0 -245
  220. package/src/core/proc-group.ts +0 -48
  221. package/src/core/project-trust.ts +0 -98
  222. package/src/core/reflection.ts +0 -165
  223. package/src/core/sandbox-config.ts +0 -186
  224. package/src/core/session-id.ts +0 -24
  225. package/src/core/session-images.ts +0 -73
  226. package/src/core/session-ops.ts +0 -183
  227. package/src/core/session-text.ts +0 -29
  228. package/src/core/session.ts +0 -469
  229. package/src/core/settings.ts +0 -170
  230. package/src/core/tasks.ts +0 -646
  231. package/src/core/token-scale.ts +0 -108
  232. package/src/core/tools.ts +0 -309
  233. package/src/core/trust.ts +0 -104
  234. package/src/core/types.ts +0 -330
  235. package/src/core/update-check.ts +0 -171
  236. package/src/core/usage.ts +0 -204
  237. package/src/core/validate.ts +0 -121
  238. package/src/core/verify-gate.ts +0 -159
  239. package/src/core/verify.ts +0 -236
  240. package/src/core/voice.ts +0 -158
  241. package/src/core/win-job.ts +0 -183
  242. package/src/core/workspace.ts +0 -184
  243. package/src/design/audit.ts +0 -797
  244. package/src/design/direction.ts +0 -190
  245. package/src/design/rules.ts +0 -157
  246. package/src/eval/bench.ts +0 -150
  247. package/src/eval/gauntlet-runner.ts +0 -215
  248. package/src/eval/gauntlet-support.ts +0 -84
  249. package/src/eval/gauntlet-wave3.ts +0 -269
  250. package/src/eval/gauntlet-wave4.ts +0 -217
  251. package/src/eval/gauntlet.ts +0 -253
  252. package/src/index.ts +0 -17
  253. package/src/lanes/agy.ts +0 -95
  254. package/src/lanes/approval.ts +0 -24
  255. package/src/lanes/claude.ts +0 -129
  256. package/src/lanes/codex.ts +0 -127
  257. package/src/lanes/events.ts +0 -130
  258. package/src/lanes/job.ts +0 -142
  259. package/src/lanes/opencode.ts +0 -122
  260. package/src/lanes/process.ts +0 -184
  261. package/src/lanes/progress.ts +0 -183
  262. package/src/lanes/registry.ts +0 -178
  263. package/src/lanes/runner.ts +0 -124
  264. package/src/lanes/types.ts +0 -112
  265. package/src/market/catalogs/mcp-docs.json +0 -111
  266. package/src/market/catalogs/plugins.json +0 -111
  267. package/src/market/catalogs/skills.json +0 -478
  268. package/src/market/clone.ts +0 -72
  269. package/src/market/context-cost.ts +0 -121
  270. package/src/market/digest.ts +0 -106
  271. package/src/market/index.ts +0 -22
  272. package/src/market/install.ts +0 -578
  273. package/src/market/manifest.ts +0 -187
  274. package/src/market/prereq.ts +0 -145
  275. package/src/market/registry.ts +0 -363
  276. package/src/market/resolve.ts +0 -111
  277. package/src/market/types.ts +0 -236
  278. package/src/market/validate.ts +0 -227
  279. package/src/mcp/client.ts +0 -449
  280. package/src/mcp/config.ts +0 -252
  281. package/src/mcp/local-package.ts +0 -211
  282. package/src/mcp/market-catalog.ts +0 -84
  283. package/src/mcp/market-install.ts +0 -289
  284. package/src/mcp/market.ts +0 -362
  285. package/src/mcp/oauth.ts +0 -251
  286. package/src/mcp/prompts-resources.ts +0 -249
  287. package/src/mcp/shared.ts +0 -149
  288. package/src/mcp/status.ts +0 -67
  289. package/src/mcp/tools.ts +0 -275
  290. package/src/mcp/transport.ts +0 -122
  291. package/src/mcp/trust.ts +0 -25
  292. package/src/memory/blocks.ts +0 -278
  293. package/src/memory/recall.ts +0 -355
  294. package/src/memory/scope.ts +0 -182
  295. package/src/memory/store.ts +0 -105
  296. package/src/memory/tools.ts +0 -99
  297. package/src/plugins/cli.ts +0 -119
  298. package/src/plugins/discover.ts +0 -108
  299. package/src/plugins/index.ts +0 -50
  300. package/src/plugins/install.ts +0 -184
  301. package/src/plugins/load.ts +0 -124
  302. package/src/plugins/manifest.ts +0 -92
  303. package/src/plugins/state.ts +0 -83
  304. package/src/providers/auth.ts +0 -408
  305. package/src/providers/cache.ts +0 -223
  306. package/src/providers/catalog-local.ts +0 -160
  307. package/src/providers/catalog.ts +0 -421
  308. package/src/providers/middleware-context.ts +0 -86
  309. package/src/providers/middleware.ts +0 -373
  310. package/src/providers/model-list.ts +0 -23
  311. package/src/providers/oauth/common.ts +0 -105
  312. package/src/providers/oauth/device-code.ts +0 -107
  313. package/src/providers/oauth/github-copilot.ts +0 -146
  314. package/src/providers/oauth/loopback.ts +0 -158
  315. package/src/providers/oauth/openai.ts +0 -163
  316. package/src/providers/oauth/openrouter.ts +0 -89
  317. package/src/providers/oauth/pkce.ts +0 -45
  318. package/src/providers/oauth/registry.ts +0 -39
  319. package/src/providers/oauth/seam.ts +0 -89
  320. package/src/providers/profile-glm53.ts +0 -111
  321. package/src/providers/profile-sonnet5-persona.ts +0 -65
  322. package/src/providers/profile-sonnet5-voice.ts +0 -23
  323. package/src/providers/profiles.ts +0 -156
  324. package/src/providers/provider-config.ts +0 -311
  325. package/src/providers/registry.ts +0 -333
  326. package/src/providers/responses.ts +0 -209
  327. package/src/providers/retry.ts +0 -234
  328. package/src/providers/router.ts +0 -294
  329. package/src/providers/sse.ts +0 -26
  330. package/src/providers/stream-errors.ts +0 -117
  331. package/src/providers/stream.ts +0 -566
  332. package/src/providers/thinking.ts +0 -189
  333. package/src/providers/wire-messages.ts +0 -129
  334. package/src/providers/wire-responses.ts +0 -79
  335. package/src/providers/wire-select.ts +0 -53
  336. package/src/server/http.ts +0 -291
  337. package/src/server/openapi.ts +0 -246
  338. package/src/sextant/card-hits.ts +0 -102
  339. package/src/sextant/card-keys.ts +0 -55
  340. package/src/sextant/context-source.ts +0 -157
  341. package/src/sextant/crew-cards.ts +0 -350
  342. package/src/sextant/draw-agents.ts +0 -273
  343. package/src/sextant/draw-code.ts +0 -388
  344. package/src/sextant/draw-context.ts +0 -222
  345. package/src/sextant/draw-frame.ts +0 -164
  346. package/src/sextant/draw-market.ts +0 -573
  347. package/src/sextant/draw-messages.ts +0 -386
  348. package/src/sextant/draw-pet.ts +0 -230
  349. package/src/sextant/draw-plan.ts +0 -187
  350. package/src/sextant/draw-tabs.ts +0 -85
  351. package/src/sextant/draw-util.ts +0 -65
  352. package/src/sextant/draw-wizard.ts +0 -378
  353. package/src/sextant/engine.ts +0 -230
  354. package/src/sextant/frame-hits.ts +0 -25
  355. package/src/sextant/frame.ts +0 -101
  356. package/src/sextant/git-status.ts +0 -197
  357. package/src/sextant/grid.ts +0 -59
  358. package/src/sextant/input.ts +0 -119
  359. package/src/sextant/keys.ts +0 -521
  360. package/src/sextant/layout.ts +0 -86
  361. package/src/sextant/local-commands.ts +0 -169
  362. package/src/sextant/market-source.ts +0 -287
  363. package/src/sextant/mentions.ts +0 -200
  364. package/src/sextant/message-hits.ts +0 -26
  365. package/src/sextant/model.ts +0 -387
  366. package/src/sextant/overlays.ts +0 -456
  367. package/src/sextant/panel-hits.ts +0 -38
  368. package/src/sextant/pet.ts +0 -399
  369. package/src/sextant/screen.ts +0 -324
  370. package/src/sextant/scroll-hits.ts +0 -66
  371. package/src/sextant/scrollbar.ts +0 -82
  372. package/src/sextant/sextant-bridge.ts +0 -174
  373. package/src/sextant/sextant-cards.ts +0 -142
  374. package/src/sextant/sextant-diff-base.ts +0 -63
  375. package/src/sextant/sextant-files.ts +0 -154
  376. package/src/sextant/sextant-frame-loop.ts +0 -335
  377. package/src/sextant/sextant-renderer.ts +0 -574
  378. package/src/sextant/sextant-repo.ts +0 -140
  379. package/src/sextant/theme.ts +0 -66
  380. package/src/sextant/tool-rows.ts +0 -189
  381. package/src/sextant/types.ts +0 -493
  382. package/src/skills/index.ts +0 -387
  383. package/src/skills/pack.ts +0 -220
  384. package/src/skills/spec.ts +0 -162
  385. package/src/skills/tools.ts +0 -69
  386. package/src/skills/versioned.ts +0 -227
  387. package/src/telemetry/otel-export.ts +0 -122
  388. package/src/telemetry/otel-lanes.ts +0 -89
  389. package/src/telemetry/otel-logs.ts +0 -131
  390. package/src/telemetry/otel-metrics.ts +0 -136
  391. package/src/telemetry/otel.ts +0 -397
  392. package/src/telemetry/otlp.ts +0 -76
  393. package/src/tools/ask-user.ts +0 -156
  394. package/src/tools/bash-bg.ts +0 -94
  395. package/src/tools/bash-jobs.ts +0 -237
  396. package/src/tools/design.ts +0 -151
  397. package/src/tools/evalcell.ts +0 -338
  398. package/src/tools/html-text.ts +0 -139
  399. package/src/tools/provider.ts +0 -149
  400. package/src/tools/task.ts +0 -250
  401. package/src/tools/todo.ts +0 -320
  402. package/src/tools/webfetch.ts +0 -332
  403. package/src/tools/websearch.ts +0 -359
  404. package/src/tui/agents-cmd.ts +0 -41
  405. package/src/tui/app.ts +0 -749
  406. package/src/tui/attach.ts +0 -127
  407. package/src/tui/boot-notes.ts +0 -41
  408. package/src/tui/builtin-prompts.ts +0 -59
  409. package/src/tui/checkpoints-cmd.ts +0 -70
  410. package/src/tui/clipboard-image.ts +0 -81
  411. package/src/tui/clipboard.ts +0 -78
  412. package/src/tui/commands.ts +0 -283
  413. package/src/tui/config-view.ts +0 -53
  414. package/src/tui/context-cmds.ts +0 -282
  415. package/src/tui/cost.ts +0 -108
  416. package/src/tui/crash-guard.ts +0 -173
  417. package/src/tui/focus-terminal.ts +0 -34
  418. package/src/tui/git-cmds.ts +0 -273
  419. package/src/tui/git-plain.ts +0 -58
  420. package/src/tui/info-cmd.ts +0 -150
  421. package/src/tui/input-plain.ts +0 -76
  422. package/src/tui/mcp-cmd.ts +0 -128
  423. package/src/tui/memory-note.ts +0 -77
  424. package/src/tui/modes-cmd.ts +0 -45
  425. package/src/tui/notify-seq.ts +0 -100
  426. package/src/tui/notify.ts +0 -318
  427. package/src/tui/overlays.ts +0 -97
  428. package/src/tui/pi-renderer.ts +0 -428
  429. package/src/tui/providers-cmd.ts +0 -377
  430. package/src/tui/reasoning-view.ts +0 -56
  431. package/src/tui/renderer.ts +0 -128
  432. package/src/tui/replay-marker.ts +0 -29
  433. package/src/tui/session-cmd.ts +0 -148
  434. package/src/tui/session-manage.ts +0 -95
  435. package/src/tui/sextant-attach.ts +0 -102
  436. package/src/tui/sextant-io.ts +0 -202
  437. package/src/tui/sextant-smoke.ts +0 -110
  438. package/src/tui/shell-cmd.ts +0 -158
  439. package/src/tui/smoke.ts +0 -72
  440. package/src/tui/staged-terminal.ts +0 -50
  441. package/src/tui/startup.ts +0 -12
  442. package/src/tui/theme.ts +0 -59
  443. package/src/tui/todo-label.ts +0 -7
  444. package/src/tui/trust-card.ts +0 -107
  445. package/src/tui/tui-commands.ts +0 -87
  446. package/tsconfig.json +0 -30
  447. package/vendor/pi-tui/LICENSE +0 -21
  448. package/vendor/pi-tui/PATCHES.md +0 -12
  449. package/vendor/pi-tui/PROVENANCE.md +0 -12
  450. package/vendor/pi-tui/README.upstream.md +0 -854
  451. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  452. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  453. package/vendor/pi-tui/src/alt-screen-search.ts +0 -158
  454. package/vendor/pi-tui/src/autocomplete.ts +0 -827
  455. package/vendor/pi-tui/src/components/alt-screen-flash.ts +0 -52
  456. package/vendor/pi-tui/src/components/box.ts +0 -138
  457. package/vendor/pi-tui/src/components/cancellable-loader.ts +0 -41
  458. package/vendor/pi-tui/src/components/editor.ts +0 -2364
  459. package/vendor/pi-tui/src/components/h-stack.ts +0 -45
  460. package/vendor/pi-tui/src/components/image.ts +0 -128
  461. package/vendor/pi-tui/src/components/input.ts +0 -448
  462. package/vendor/pi-tui/src/components/loader.ts +0 -93
  463. package/vendor/pi-tui/src/components/markdown.ts +0 -1016
  464. package/vendor/pi-tui/src/components/scroll-view.ts +0 -217
  465. package/vendor/pi-tui/src/components/select-list.ts +0 -230
  466. package/vendor/pi-tui/src/components/settings-list.ts +0 -277
  467. package/vendor/pi-tui/src/components/spacer.ts +0 -29
  468. package/vendor/pi-tui/src/components/stack.ts +0 -155
  469. package/vendor/pi-tui/src/components/text.ts +0 -108
  470. package/vendor/pi-tui/src/components/truncated-text.ts +0 -66
  471. package/vendor/pi-tui/src/components/v-stack.ts +0 -34
  472. package/vendor/pi-tui/src/editor-component.ts +0 -75
  473. package/vendor/pi-tui/src/fuzzy.ts +0 -138
  474. package/vendor/pi-tui/src/index.ts +0 -149
  475. package/vendor/pi-tui/src/keybindings.ts +0 -321
  476. package/vendor/pi-tui/src/keys.ts +0 -1402
  477. package/vendor/pi-tui/src/kill-ring.ts +0 -47
  478. package/vendor/pi-tui/src/latex.ts +0 -1381
  479. package/vendor/pi-tui/src/layout-node.ts +0 -52
  480. package/vendor/pi-tui/src/layout.ts +0 -411
  481. package/vendor/pi-tui/src/native-modifiers.ts +0 -60
  482. package/vendor/pi-tui/src/native-module-path.ts +0 -32
  483. package/vendor/pi-tui/src/stdin-buffer.ts +0 -445
  484. package/vendor/pi-tui/src/terminal-colors.ts +0 -74
  485. package/vendor/pi-tui/src/terminal-image.ts +0 -701
  486. package/vendor/pi-tui/src/terminal.ts +0 -554
  487. package/vendor/pi-tui/src/tui-alt-screen.ts +0 -1379
  488. package/vendor/pi-tui/src/tui-main-screen.ts +0 -655
  489. package/vendor/pi-tui/src/tui.ts +0 -1264
  490. package/vendor/pi-tui/src/undo-stack.ts +0 -29
  491. package/vendor/pi-tui/src/utils.ts +0 -1327
  492. package/vendor/pi-tui/src/word-navigation.ts +0 -118
  493. package/vendor/pi-tui/test/test-themes.ts +0 -39
  494. package/vendor/pi-tui/test/virtual-terminal.ts +0 -219
  495. /package/{src/providers → dist/cli}/models-index.json +0 -0
@@ -1,341 +0,0 @@
1
- /** First-class glob/grep/ls tools (PORT #22). Ported from opencode (MIT,
2
- * snapshot ebece6e): glob/grep tool contracts, limits, and truncation markers
3
- * (packages/opencode/src/tool/glob.ts:49-62, grep.ts:80-102) and the per-line
4
- * 2000-char cap w/ surrogate guard (packages/core/src/ripgrep.ts:267-270).
5
- * Bounded-output + ignore semantics follow gemini-cli (Apache-2.0, snapshot
6
- * 0bd1d43): glob recency-then-alpha sort (packages/core/src/tools/glob.ts:47-70)
7
- * and the ls output shape — dirs-first sort, [DIR] markers, "(N ignored)" note
8
- * (packages/core/src/tools/ls.ts:191-271). Upstream shells out to ripgrep;
9
- * rg is NOT guaranteed on PATH here, so matching is pure Bun/TS over a
10
- * gitignore-aware enumeration: `git ls-files` in repos (repomap-files.ts
11
- * seam, wave-2), a bounded walk skipping dotfiles/node_modules outside git. */
12
-
13
- import { readdirSync, statSync, lstatSync, existsSync } from "node:fs";
14
- import { readFile, stat } from "node:fs/promises";
15
- import { join, basename } from "node:path";
16
- import { toolPath } from "../core/workspace.ts";
17
- import { spawnSync } from "node:child_process";
18
- import type { Tool, ToolOutput } from "../core/types.ts";
19
- import { gitListFiles } from "./repomap-files.ts";
20
-
21
- // ---------- bounds (advertised in schemas; args are clamped, never trusted) ----------
22
-
23
- export const GLOB_LIMIT_DEFAULT = 100; // opencode glob.ts:49
24
- export const GREP_LIMIT_DEFAULT = 100; // opencode grep.ts:67, gemini-cli DEFAULT_TOTAL_MAX_MATCHES
25
- export const LS_LIMIT_DEFAULT = 200;
26
- export const LIMIT_CAP = 1000; // hard ceiling for maxFiles/maxMatches/maxEntries args
27
- export const SCAN_CAP = 10_000; // candidate files enumerated per call
28
- export const GREP_LINE_CAP = 2000; // chars per matching line (opencode ripgrep.ts:268)
29
- export const GREP_FILE_BYTES_CAP = 1024 * 1024; // grep skips larger files
30
- const BINARY_SNIFF_BYTES = 8192; // NUL within the first 8k → binary, skip (rg default)
31
-
32
- /** Exported so the cap CONTRACT is pinnable: ceiling (LIMIT_CAP), floor
33
- * (absent / NaN / non-positive → the tool default), integer truncation. */
34
- export function clampLimit(v: number | undefined, dflt: number): number {
35
- return Number.isFinite(v) && v! > 0 ? Math.min(Math.floor(v!), LIMIT_CAP) : dflt;
36
- }
37
- /** the ladder's own spelling (core/workspace.ts toolPath): `..` collapses lexically BEFORE the open, never through a symlink */
38
- function resolvePath(cwd: string, p: string | undefined): string {
39
- if (!p) return cwd;
40
- return toolPath(cwd, p);
41
- }
42
-
43
- // ---------- gitignore-aware enumeration ----------
44
-
45
- export interface FileListing { rel: string[]; viaGit: boolean; capped: boolean }
46
-
47
- /** Relative file paths (forward slashes) under root. Git path: `git ls-files`
48
- * (tracked + untracked-unignored → .gitignore honored). Fallback: bounded
49
- * per-dir-sorted DFS skipping dot-entries and node_modules (repomap-files.ts
50
- * walk rules, generic file filter), lstat-based so symlinks/junctions are
51
- * never followed — a directory link back into an ancestor is a cycle, not a
52
- * subtree. Both deterministic, both ≤ cap; `cap` is a test seam only. */
53
- export function listFiles(root: string, cap = SCAN_CAP): FileListing {
54
- const fromGit = gitListFiles(root);
55
- if (fromGit) {
56
- const rel = fromGit.slice(0, cap + 1);
57
- const capped = rel.length > cap;
58
- if (capped) rel.pop();
59
- return { rel, viaGit: true, capped };
60
- }
61
- const rel: string[] = [];
62
- let capped = false;
63
- const walk = (dir: string, prefix: string): void => {
64
- let names: string[];
65
- try { names = readdirSync(dir); } catch { return; }
66
- for (const name of names.sort()) {
67
- if (capped) return;
68
- if (name.startsWith(".") || name === "node_modules") continue;
69
- const full = join(dir, name);
70
- let st;
71
- try { st = lstatSync(full); } catch { continue; }
72
- if (st.isSymbolicLink()) continue; // junction/symlink: never descend (cycle hazard)
73
- if (st.isDirectory()) walk(full, `${prefix}${name}/`);
74
- else if (st.isFile()) {
75
- if (rel.length >= cap) { capped = true; return; }
76
- rel.push(prefix + name);
77
- }
78
- }
79
- };
80
- walk(root, "");
81
- return { rel, viaGit: false, capped };
82
- }
83
-
84
- /** rg --glob semantics (opencode ripgrep.ts:165): a pattern without "/" matches
85
- * file NAMES at any depth; a pattern with "/" matches the root-relative path. */
86
- function makeMatcher(pattern: string): (rel: string) => boolean {
87
- const glob = new Bun.Glob(pattern);
88
- if (!pattern.includes("/")) return (rel) => glob.match(basename(rel));
89
- return (rel) => glob.match(rel);
90
- }
91
-
92
- /** Enumeration-cap marker shared by the glob/grep listings and the ls scan
93
- * cap; exported so the exact string is pinnable without a 10k-file fixture. */
94
- export const scanCapLine = (cap: number): string =>
95
- `(File enumeration capped at ${cap} files; results may be incomplete.)`;
96
- const scanCapNote = (capped: boolean, cap: number): string[] => (capped ? ["", scanCapLine(cap)] : []);
97
-
98
- // ---------- grep helpers ----------
99
-
100
- function isBinary(buf: Buffer): boolean {
101
- return buf.subarray(0, BINARY_SNIFF_BYTES).includes(0);
102
- }
103
-
104
- /** opencode ripgrep.ts:267-270: cap line text, guard a split surrogate pair. */
105
- function capLine(text: string): string {
106
- return text.length > GREP_LINE_CAP
107
- ? text.slice(0, GREP_LINE_CAP).replace(/[\uD800-\uDBFF]$/, "") + "..."
108
- : text;
109
- }
110
-
111
- // ---------- ls helpers ----------
112
-
113
- /** Names inside root that git ignores (git check-ignore --stdin; bounded).
114
- * null = no git / not a repo → caller applies the non-git default excludes. */
115
- function gitIgnoredNames(root: string, names: string[]): Set<string> | null {
116
- try {
117
- const res = spawnSync("git", ["-C", root, "check-ignore", "--stdin", "-z"],
118
- { input: names.join("\0"), timeout: 5000, maxBuffer: 16 * 1024 * 1024, encoding: "utf8", windowsHide: true });
119
- if (res.error || typeof res.stdout !== "string") return null;
120
- if (res.status !== 0 && res.status !== 1) return null; // 0 = some ignored, 1 = none; 128 = not a repo
121
- return new Set(res.stdout.split("\0").filter((s) => s.length > 0));
122
- } catch {
123
- return null;
124
- }
125
- }
126
-
127
- /** Builds the three tools over ONE enumeration cap. `scanCap` is a test seam
128
- * only (repomap.ts `opts.maxFiles` idiom) so the cap note and the bounded ls
129
- * stat sweep are pinnable on 3-file fixtures; every production registration
130
- * uses the module-level globTool/grepTool/lsTool built with SCAN_CAP. */
131
- export function fileTools(scanCap = SCAN_CAP): { globTool: Tool; grepTool: Tool; lsTool: Tool } {
132
- // ---------- glob ----------
133
-
134
- const globTool: Tool = {
135
- schema: {
136
- name: "glob",
137
- description: "Find files by glob pattern (e.g. \"**/*.ts\", \"src/**/*.test.ts\"; a pattern without \"/\" matches file names at any depth). Honors .gitignore in git repos; skips dotfiles and node_modules elsewhere. Returns absolute paths, most recently modified first, at most maxFiles (default 100, cap 1000) with a truncation note when more matched.",
138
- args: {
139
- type: "object",
140
- properties: {
141
- pattern: { type: "string", description: "glob pattern to match files against" },
142
- path: { type: "string", description: "directory to search (default: session cwd)" },
143
- maxFiles: { type: "integer", description: `max paths returned (default ${GLOB_LIMIT_DEFAULT}, cap ${LIMIT_CAP})` },
144
- },
145
- required: ["pattern"],
146
- },
147
- },
148
- kind: "read",
149
- sequential: false,
150
- execute(args, ctx): Promise<ToolOutput> {
151
- const a = args as { pattern: string; path?: string; maxFiles?: number };
152
- const root = resolvePath(ctx.cwd, a.path);
153
- if (!existsSync(root)) return Promise.resolve({ ok: false, output: `directory not found: ${root}` });
154
- if (!statSync(root).isDirectory()) return Promise.resolve({ ok: false, output: `glob path must be a directory: ${root}` });
155
- const limit = clampLimit(a.maxFiles, GLOB_LIMIT_DEFAULT);
156
- let match: (rel: string) => boolean;
157
- try { match = makeMatcher(a.pattern); } catch (e) {
158
- return Promise.resolve({ ok: false, output: `invalid glob pattern: ${e instanceof Error ? e.message : String(e)}` });
159
- }
160
- const listing = listFiles(root, scanCap);
161
- // stat for the recency sort; stat failure = deleted-but-tracked etc. → drop
162
- const hits: { abs: string; mtimeMs: number }[] = [];
163
- for (const rel of listing.rel) {
164
- if (!match(rel)) continue;
165
- const abs = join(root, rel);
166
- try { hits.push({ abs, mtimeMs: statSync(abs).mtimeMs }); } catch { /* skip */ }
167
- }
168
- // gemini-cli glob.ts:47-70: files touched within 24h first (newest→oldest), rest alphabetical
169
- const now = Date.now();
170
- const oneDayMs = 24 * 60 * 60 * 1000;
171
- hits.sort((x, y) => {
172
- const xr = now - x.mtimeMs < oneDayMs, yr = now - y.mtimeMs < oneDayMs;
173
- if (xr && yr) return y.mtimeMs - x.mtimeMs;
174
- if (xr) return -1;
175
- if (yr) return 1;
176
- return x.abs.localeCompare(y.abs);
177
- });
178
- const truncated = hits.length > limit;
179
- const shown = truncated ? hits.slice(0, limit) : hits;
180
- const output: string[] = [];
181
- if (shown.length === 0) output.push("No files found");
182
- else {
183
- output.push(...shown.map((h) => h.abs));
184
- if (truncated) {
185
- output.push("", `(Results are truncated: showing first ${limit} results. Consider using a more specific path or pattern.)`);
186
- }
187
- }
188
- output.push(...scanCapNote(listing.capped, scanCap));
189
- return Promise.resolve({ ok: true, output: output.join("\n"), data: { count: shown.length, truncated } });
190
- },
191
- };
192
-
193
- // ---------- grep ----------
194
-
195
- const grepTool: Tool = {
196
- schema: {
197
- name: "grep",
198
- description: "Search file contents with a JS regular expression (e.g. \"log.*Error\", \"function\\s+\\w+\"). Optional glob filters files (\"*.ts\", \"src/**/*.tsx\"). Honors .gitignore in git repos; skips dotfiles/node_modules elsewhere, plus binary files and files over 1MB. Output is `path:line: text` matches, at most maxMatches (default 100, cap 1000), each line capped at 2000 chars, with truncation notes.",
199
- args: {
200
- type: "object",
201
- properties: {
202
- pattern: { type: "string", description: "regex to search for in file contents" },
203
- path: { type: "string", description: "directory (or single file) to search (default: session cwd)" },
204
- glob: { type: "string", description: "file filter glob, e.g. \"*.ts\" (names at any depth) or \"src/**/*.ts\"" },
205
- maxMatches: { type: "integer", description: `max matches returned (default ${GREP_LIMIT_DEFAULT}, cap ${LIMIT_CAP})` },
206
- },
207
- required: ["pattern"],
208
- },
209
- },
210
- kind: "read",
211
- sequential: false,
212
- async execute(args, ctx): Promise<ToolOutput> {
213
- const a = args as { pattern: string; path?: string; glob?: string; maxMatches?: number };
214
- let rx: RegExp;
215
- try { rx = new RegExp(a.pattern); } catch (e) {
216
- return { ok: false, output: `invalid regex: ${e instanceof Error ? e.message : String(e)}` };
217
- }
218
- const root = resolvePath(ctx.cwd, a.path);
219
- if (!existsSync(root)) return { ok: false, output: `path not found: ${root}` };
220
- const limit = clampLimit(a.maxMatches, GREP_LIMIT_DEFAULT);
221
- let filter: ((rel: string) => boolean) | null = null;
222
- if (a.glob) {
223
- try { filter = makeMatcher(a.glob); } catch (e) {
224
- return { ok: false, output: `invalid glob filter: ${e instanceof Error ? e.message : String(e)}` };
225
- }
226
- }
227
- if (ctx.signal.aborted) return { ok: false, output: "grep aborted" }; // pre-aborted: enumerate nothing
228
- // single-file target scans just that file (opencode grep.ts:62)
229
- const singleFile = statSync(root).isFile();
230
- const listing: FileListing = singleFile
231
- ? { rel: [basename(root)], viaGit: false, capped: false }
232
- : listFiles(root, scanCap);
233
- const base = singleFile ? join(root, "..") : root;
234
- const candidates = listing.rel.filter((rel) => (filter ? filter(rel) : true)).sort();
235
-
236
- const matches: { abs: string; line: number; text: string }[] = [];
237
- let truncated = false;
238
- outer: for (const rel of candidates) {
239
- // reads are awaited, so the loop yields per file and an abort that lands
240
- // MID-scan is observed here — a synchronous scan could never see one
241
- if (ctx.signal.aborted) return { ok: false, output: "grep aborted" };
242
- const abs = singleFile ? root : join(base, rel);
243
- let buf: Buffer;
244
- try {
245
- if ((await stat(abs)).size > GREP_FILE_BYTES_CAP) continue;
246
- buf = await readFile(abs);
247
- } catch { continue; }
248
- if (isBinary(buf)) continue;
249
- const lines = buf.toString("utf8").split(/\r?\n/);
250
- for (let i = 0; i < lines.length; i++) {
251
- if (!rx.test(lines[i]!)) continue;
252
- if (matches.length >= limit) { truncated = true; break outer; }
253
- matches.push({ abs, line: i + 1, text: capLine(lines[i]!) });
254
- }
255
- }
256
- if (matches.length === 0) {
257
- return { ok: true, output: ["No matches found", ...scanCapNote(listing.capped, scanCap)].join("\n"), data: { matches: 0, truncated: false } };
258
- }
259
- const output = [`Found ${matches.length} matches${truncated ? " (more matches available)" : ""}`];
260
- output.push(...matches.map((m) => `${m.abs}:${m.line}: ${m.text}`));
261
- if (truncated) output.push("", "(Results truncated. Consider using a more specific path or pattern.)");
262
- output.push(...scanCapNote(listing.capped, scanCap));
263
- return { ok: true, output: output.join("\n"), data: { matches: matches.length, truncated } };
264
- },
265
- };
266
-
267
- // ---------- ls ----------
268
-
269
- const lsTool: Tool = {
270
- schema: {
271
- name: "ls",
272
- description: "List a directory (non-recursive). Directories first as `[DIR] name`, files as `name (N bytes)`, alphabetical. Gitignored entries are hidden and counted in git repos; dotfiles and node_modules are hidden elsewhere. At most maxEntries rows (default 200, cap 1000) with a truncation note.",
273
- args: {
274
- type: "object",
275
- properties: {
276
- path: { type: "string", description: "directory to list (default: session cwd)" },
277
- maxEntries: { type: "integer", description: `max entries returned (default ${LS_LIMIT_DEFAULT}, cap ${LIMIT_CAP})` },
278
- },
279
- },
280
- },
281
- kind: "read",
282
- sequential: false,
283
- execute(args, ctx): Promise<ToolOutput> {
284
- const a = args as { path?: string; maxEntries?: number };
285
- const root = resolvePath(ctx.cwd, a.path);
286
- if (!existsSync(root)) return Promise.resolve({ ok: false, output: `directory not found: ${root}` });
287
- if (!statSync(root).isDirectory()) return Promise.resolve({ ok: false, output: `Path is not a directory: ${root}` });
288
- const limit = clampLimit(a.maxEntries, LS_LIMIT_DEFAULT);
289
-
290
- const names = readdirSync(root).filter((n) => n !== ".git");
291
- const ignored = gitIgnoredNames(root, names);
292
- let kept: string[];
293
- let ignoredCount: number;
294
- if (ignored) {
295
- kept = names.filter((n) => !ignored.has(n));
296
- ignoredCount = names.length - kept.length;
297
- } else {
298
- // outside git: dotfile/node_modules default excludes (gemini-cli filters via
299
- // its FileDiscoveryService; we approximate with the walk rules)
300
- kept = names.filter((n) => !n.startsWith(".") && n !== "node_modules");
301
- ignoredCount = names.length - kept.length;
302
- }
303
- if (kept.length === 0) {
304
- const note = ignoredCount > 0 ? `\n\n(${ignoredCount} ignored)` : "";
305
- return Promise.resolve({ ok: true, output: `Directory ${root} is empty.${note}`, data: { entries: 0, truncated: false } });
306
- }
307
-
308
- // the per-entry stat sweep is bounded by the scan cap: alphabetical slice
309
- // FIRST, then stat only the survivors (a 30k-entry dir with maxEntries 5
310
- // used to stat all 30k before truncating). Dirs-first ordering therefore
311
- // holds within the scanned slice; the cap note flags what was left out.
312
- const scanCapped = kept.length > scanCap;
313
- const scanned = scanCapped ? kept.sort((x, y) => x.localeCompare(y)).slice(0, scanCap) : kept;
314
- const entries: { name: string; isDir: boolean; size: number }[] = [];
315
- for (const name of scanned) {
316
- try {
317
- const st = statSync(join(root, name));
318
- entries.push({ name, isDir: st.isDirectory(), size: st.isDirectory() ? 0 : st.size });
319
- } catch { /* unreadable entry → drop */ }
320
- }
321
- // gemini-cli ls.ts:253-257: directories first, then alphabetical
322
- entries.sort((x, y) => {
323
- if (x.isDir && !y.isDir) return -1;
324
- if (!x.isDir && y.isDir) return 1;
325
- return x.name.localeCompare(y.name);
326
- });
327
- const truncated = entries.length > limit;
328
- const shown = truncated ? entries.slice(0, limit) : entries;
329
- const body = shown.map((e) => (e.isDir ? `[DIR] ${e.name}` : `${e.name} (${e.size} bytes)`)).join("\n");
330
- let output = `Directory listing for ${root}:\n${body}`;
331
- if (truncated) output += `\n\n(Results truncated: showing first ${limit} of ${entries.length} entries.)`;
332
- if (ignoredCount > 0) output += `\n\n(${ignoredCount} ignored)`;
333
- if (scanCapped) output += `\n\n${scanCapLine(scanCap)}`;
334
- return Promise.resolve({ ok: true, output, data: { entries: shown.length, truncated } });
335
- },
336
- };
337
-
338
- return { globTool, grepTool, lsTool };
339
- }
340
-
341
- export const { globTool, grepTool, lsTool } = fileTools();
@@ -1,274 +0,0 @@
1
- /** Hashline-anchored edits (ADR-006): read emits path#TAG + N#hash|content;
2
- * edit requires LINE#HASH anchors to match, applies in reverse order,
3
- * failures return nearest-match diagnostics (aider did-you-mean).
4
- * Port #28: a rejected edit/write is an ACTIONABLE message (describeEditFailure) — expected vs
5
- * what the file holds NOW (the anchor line's current text + hash, the lines whose hash IS the
6
- * anchor), the line count for out-of-range, the missing directory for write — plus the remedy,
7
- * bounded to MAX_EDIT_MESSAGE_CHARS with the remedy always surviving the clip. Pattern: aider
8
- * @ 5dc9490 aider/coders/editblock_coder.py:84-124 (failed-block report → "Did you mean to match
9
- * some of these actual lines", "The SEARCH section must exactly match…"); no code copied. */
10
-
11
- import { createHash } from "node:crypto";
12
- import { readFileSync, writeFileSync, existsSync } from "node:fs";
13
- import { dirname } from "node:path";
14
- import { toolPath } from "../core/workspace.ts";
15
- import type { Tool, ToolContext, ToolOutput } from "../core/types.ts";
16
-
17
- /** FNV-1a over whitespace-stripped line → 3 base36 chars (phi hash.go). */
18
- export function lineHash(line: string): string {
19
- const stripped = line.replace(/\s/g, "");
20
- let h = 0x811c9dc5;
21
- for (let i = 0; i < stripped.length; i++) {
22
- h ^= stripped.charCodeAt(i);
23
- h = Math.imul(h, 0x01000193) >>> 0;
24
- }
25
- return h.toString(36).padStart(3, "0").slice(-3);
26
- }
27
-
28
- /** 4-hex file TAG from content (phi hash.go). */
29
- export function fileTag(content: string): string {
30
- return createHash("sha1").update(content).digest("hex").slice(0, 4);
31
- }
32
-
33
- export interface AnchoredFile { path: string; tag: string; lines: { n: number; hash: string; text: string }[] }
34
-
35
- export function readAnchored(absPath: string): AnchoredFile {
36
- const content = readFileSync(absPath, "utf8");
37
- const lines = content.split("\n").map((text, i) => ({ n: i + 1, hash: lineHash(text), text }));
38
- return { path: absPath, tag: fileTag(content), lines };
39
- }
40
-
41
- export function renderAnchored(f: AnchoredFile): string {
42
- const header = `${f.path}#${f.tag}`;
43
- const body = f.lines.map((l) => `${l.n}#${l.hash}|${l.text}`).join("\n");
44
- return `${header}\n${body}`;
45
- }
46
-
47
- export interface EditOp {
48
- path: string;
49
- tag: string; // must match current fileTag, else stale-read rejection
50
- anchorLine: number; // 1-based line the edit replaces
51
- anchorHash: string; // must match lineHash at anchorLine
52
- newLines: string[]; // replacement content (empty = delete line)
53
- }
54
-
55
- /** lines listed as "carry your anchor's hash" in a hash-mismatch failure */
56
- const MAX_ANCHOR_MATCHES = 3;
57
-
58
- export type EditFailure =
59
- | { kind: "tag-mismatch"; path: string; expected: string; actual: string }
60
- /** text = the anchor line's CURRENT content (clipped); matches = line numbers whose hash is the anchor (≤ MAX_ANCHOR_MATCHES) */
61
- | { kind: "hash-mismatch"; path: string; line: number; expected: string; actual: string; nearest: string; text: string; matches: number[] }
62
- | { kind: "out-of-range"; path: string; line: number; lineCount: number };
63
-
64
- export type EditResult = { ok: true; newTag: string } | { ok: false; failure: EditFailure };
65
- export type ApplyResult = { ok: true; content: string; newTag: string } | { ok: false; failure: EditFailure };
66
-
67
- /** Pure core of applyEdits over in-memory content: tag check, reverse-order anchored splices
68
- * (later lines first so earlier anchors stay valid — phi hashline.go), nearest-match
69
- * diagnostics. Port #24 previews the exact post-edit content through this without writing. */
70
- export function applyEditsToContent(content: string, edits: EditOp[], path: string): ApplyResult {
71
- const tag = fileTag(content);
72
- const stale = edits.find((e) => e.tag !== tag);
73
- if (stale) return { ok: false, failure: { kind: "tag-mismatch", path, expected: stale.tag, actual: tag } };
74
-
75
- const lines = content.split("\n");
76
- const sorted = [...edits].sort((a, b) => b.anchorLine - a.anchorLine);
77
- for (const e of sorted) {
78
- if (e.anchorLine < 1 || e.anchorLine > lines.length) {
79
- return { ok: false, failure: { kind: "out-of-range", path, line: e.anchorLine, lineCount: lines.length } };
80
- }
81
- const text = lines[e.anchorLine - 1]!;
82
- const actual = lineHash(text);
83
- if (actual !== e.anchorHash) {
84
- // nearest-match diagnostic (aider find_similar_lines): the lines that DO carry the anchor's hash
85
- const matches: number[] = [];
86
- for (let i = 0; i < lines.length && matches.length < MAX_ANCHOR_MATCHES; i++) if (lineHash(lines[i]!) === e.anchorHash) matches.push(i + 1);
87
- const nearest = matches.length > 0
88
- ? `line ${matches[0]} currently holds that hash: ${lines[matches[0]! - 1]!.slice(0, 80)}`
89
- : `no line matches; line ${e.anchorLine} is now: ${text.slice(0, 80)}`;
90
- return { ok: false, failure: { kind: "hash-mismatch", path, line: e.anchorLine, expected: e.anchorHash, actual, nearest, text: text.slice(0, 80), matches } };
91
- }
92
- lines.splice(e.anchorLine - 1, 1, ...e.newLines);
93
- }
94
- const next = lines.join("\n");
95
- return { ok: true, content: next, newTag: fileTag(next) };
96
- }
97
-
98
- /** Applies edits to the file on disk (applyEditsToContent + write); a missing file is out-of-range. */
99
- export function applyEdits(absPath: string, edits: EditOp[]): EditResult {
100
- if (!existsSync(absPath)) return { ok: false, failure: { kind: "out-of-range", path: absPath, line: 0, lineCount: 0 } };
101
- const r = applyEditsToContent(readFileSync(absPath, "utf8"), edits, absPath);
102
- if (!r.ok) return r;
103
- writeFileSync(absPath, r.content);
104
- return { ok: true, newTag: r.newTag };
105
- }
106
-
107
- // ---------- port #28: actionable failure text ----------
108
-
109
- /** hard bound on every rejection message the model sees (the remedy is never the part clipped) */
110
- export const MAX_EDIT_MESSAGE_CHARS = 600;
111
- const EDIT_REMEDY = "Remedy: re-read the file with `read` to get fresh line hashes, then retry the edit.";
112
- /** lint errors listed in a lint-gate rejection before "… and N more" */
113
- const MAX_LINT_LINES = 8;
114
-
115
- /** What went wrong, what the file holds NOW, and what to do — aider's failed-block report shape
116
- * (editblock_coder.py:84-124: the failing block, "Did you mean to match some of these actual
117
- * lines", the exact-match rule) for hashline anchors. Detail is clipped first; the remedy survives. */
118
- export function describeEditFailure(f: EditFailure): string {
119
- const head = "Edit rejected: ";
120
- const room = MAX_EDIT_MESSAGE_CHARS - head.length - EDIT_REMEDY.length - 1;
121
- return `${head}${clip(editFailureDetail(f), room)} ${EDIT_REMEDY}`;
122
- }
123
-
124
- function editFailureDetail(f: EditFailure): string {
125
- switch (f.kind) {
126
- case "tag-mismatch":
127
- return `stale read — ${f.path} changed since you read it (file TAG is now ${f.actual}, your edit carries ${f.expected}).`;
128
- case "hash-mismatch": {
129
- const where = f.matches.length > 0
130
- ? `Lines whose hash matches your anchor: ${f.matches.join(", ")} — did you mean one of those?`
131
- : "No line in the file has that hash now — the content changed since your read.";
132
- return `anchor mismatch at ${f.path}:${f.line} — line ${f.line} now reads ${JSON.stringify(f.text)} (hash ${f.actual}), your anchor expected hash ${f.expected}. ${where}`;
133
- }
134
- case "out-of-range":
135
- return `line ${f.line} is out of range — ${f.path} has ${f.lineCount} lines (valid anchors: 1-${f.lineCount}).`;
136
- }
137
- }
138
-
139
- function clip(s: string, max: number): string { return s.length <= max ? s : s.slice(0, Math.max(0, max - 1)) + "…"; }
140
-
141
- // ---------- Tools ----------
142
-
143
- /** the ladder's own spelling (core/workspace.ts toolPath): `..` collapses lexically BEFORE the open, never through a symlink */
144
- function resolvePath(cwd: string, p: string): string { return toolPath(cwd, p); }
145
-
146
- /** Windowed anchored read: keep absolute line numbers, note the slice bounds. */
147
- function renderWindow(f: AnchoredFile, offset: number, limit: number): string {
148
- const total = f.lines.length;
149
- const start = Math.max(1, Math.floor(offset));
150
- if (start > total) return `${f.path}#${f.tag}\n(showing lines 0-0 of ${total}; offset ${start} is past EOF)`;
151
- const end = Math.min(total, start + Math.floor(limit) - 1);
152
- const slice = f.lines.slice(start - 1, end);
153
- return renderAnchored({ ...f, lines: slice }).replace(/\n$/, "") + `\n(showing lines ${start}-${end} of ${total})`;
154
- }
155
-
156
-
157
-
158
- export const readTool: Tool = {
159
- schema: {
160
- name: "read",
161
- description: "Read a file window. Output is `path#TAG` header plus `N#hash|content` lines; use hashes for edit anchors. Defaults: offset 1, limit 2000 lines. Footer notes `showing lines X-Y of Z`; if Y < Z pass a larger offset to see more.",
162
- args: {
163
- type: "object",
164
- properties: {
165
- path: { type: "string" },
166
- offset: { type: "integer", description: "first line to show (1-based, default 1)" },
167
- limit: { type: "integer", description: "max lines to show (default 2000)" },
168
- },
169
- required: ["path"],
170
- },
171
- },
172
- kind: "read",
173
- sequential: false,
174
- execute(args, ctx): Promise<ToolOutput> {
175
- const a = args as { path: string; offset?: number; limit?: number };
176
- const p = resolvePath(ctx.cwd, a.path);
177
- if (!existsSync(p)) return Promise.resolve({ ok: false, output: `file not found: ${p}` });
178
- const offset = Number.isFinite(a.offset) && a.offset! > 0 ? Math.floor(a.offset!) : 1;
179
- const limit = Number.isFinite(a.limit) && a.limit! > 0 ? Math.floor(a.limit!) : 2000;
180
- return Promise.resolve({ ok: true, output: renderWindow(readAnchored(p), offset, limit) });
181
- },
182
- };
183
-
184
- // ---------- Edit lint-gate ----------
185
-
186
- export type EditLinter = (content: string, path: string) => string[];
187
-
188
- let editLinter: EditLinter | undefined;
189
-
190
- /** Install a linter (e.g. `tsc --noEmit` or biome) consulted after every applied edit.
191
- * When set, an edit that introduces NEW lint errors is reverted and reported. */
192
- export function setEditLinter(fn: EditLinter | undefined): void { editLinter = fn; }
193
-
194
- export const editTool: Tool = {
195
- schema: {
196
- name: "edit",
197
- description: "Anchored edit. Each op replaces the line at anchorLine (whose hash must equal anchorHash) with newLines. tag must match the TAG from your last read.",
198
- args: {
199
- type: "object",
200
- properties: {
201
- path: { type: "string" },
202
- edits: {
203
- type: "array",
204
- items: {
205
- type: "object",
206
- properties: {
207
- tag: { type: "string" }, anchorLine: { type: "integer" },
208
- anchorHash: { type: "string" }, newLines: { type: "array", items: { type: "string" } },
209
- },
210
- required: ["tag", "anchorLine", "anchorHash", "newLines"],
211
- },
212
- },
213
- },
214
- required: ["path", "edits"],
215
- },
216
- },
217
- kind: "write",
218
- sequential: true,
219
- execute(args, ctx): Promise<ToolOutput> {
220
- const a = args as { path: string; edits: EditOp[] };
221
- const p = resolvePath(ctx.cwd, a.path);
222
- if (!existsSync(p)) {
223
- // port #28: a missing file used to surface as "line 0 out of range (file has 0 lines)"
224
- return Promise.resolve({ ok: false, output: clip(`Edit rejected: file not found: ${p} — check the path (relative paths resolve against ${ctx.cwd}) or create the file with \`write\`.`, MAX_EDIT_MESSAGE_CHARS) });
225
- }
226
- const before = readFileSync(p, "utf8");
227
- const r = applyEdits(p, a.edits.map((e) => ({ ...e, path: p })));
228
- if (!r.ok) return Promise.resolve({ ok: false, output: describeEditFailure(r.failure) });
229
- // Lint-gate (SWE-agent revert+requery): if the edit introduced NEW lint errors,
230
- // revert to the pre-edit content and report them so the agent retries.
231
- if (editLinter) {
232
- const errorsBefore = editLinter(before, p);
233
- const errorsAfter = editLinter(readFileSync(p, "utf8"), p);
234
- const before2 = new Set(errorsBefore);
235
- const newErrors = errorsAfter.filter((e) => !before2.has(e));
236
- if (newErrors.length > 0) {
237
- writeFileSync(p, before);
238
- const shown = newErrors.slice(0, MAX_LINT_LINES).join("\n") + (newErrors.length > MAX_LINT_LINES ? `\n… and ${newErrors.length - MAX_LINT_LINES} more` : "");
239
- return Promise.resolve({
240
- ok: false,
241
- output: `Edit applied but lint failed — file reverted to original. New lint errors:\n${shown}\nFix these and retry the edit.`,
242
- });
243
- }
244
- }
245
- return Promise.resolve({ ok: true, output: `applied ${a.edits.length} edit(s); new TAG ${r.newTag}` });
246
- },
247
- };
248
-
249
- export const writeTool: Tool = {
250
- schema: {
251
- name: "write",
252
- description: "Create or overwrite a file with content.",
253
- args: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } }, required: ["path", "content"] },
254
- },
255
- kind: "write",
256
- sequential: true,
257
- execute(args, ctx): Promise<ToolOutput> {
258
- const a = args as { path: string; content: string };
259
- const p = resolvePath(ctx.cwd, a.path);
260
- const dir = dirname(p);
261
- if (!existsSync(dir)) {
262
- // port #28: say WHY instead of leaking the raw ENOENT — the fix is a mkdir, not a different path
263
- return Promise.resolve({ ok: false, output: clip(`Write rejected: directory ${dir} does not exist — create it first (bash: mkdir -p ${JSON.stringify(dir)}) or write into an existing directory.`, MAX_EDIT_MESSAGE_CHARS) });
264
- }
265
- writeFileSync(p, a.content);
266
- return Promise.resolve({ ok: true, output: `wrote ${p} (${a.content.length} bytes, TAG ${fileTag(a.content)})` });
267
- },
268
- };
269
-
270
- // port #55: the bash tool moved to coding/bash.ts when it grew a timeout and a background flag — this
271
- // file is about ANCHORED EDITS (the phi hashline port) and shell execution had drifted into the end of
272
- // it. Re-exported here because ten files import `bashTool` from this path and a move that changes no
273
- // import is a move nobody has to review twice.
274
- export { bashTool, deniedCommand } from "./bash.ts";