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.
- package/CHANGELOG.md +527 -0
- package/README.md +46 -48
- package/bin/rovecode.js +24 -0
- package/dist/cli/app-j6gn14w3.js +2 -0
- package/dist/cli/ask-user-cwstt8fz.js +2 -0
- package/dist/cli/auth-login-9bbp9915.js +2 -0
- package/dist/cli/auth-m8p9grty.js +2 -0
- package/dist/cli/bench-16zqdms5.js +9 -0
- package/dist/cli/catalog-1xchffa4.js +2 -0
- package/dist/cli/cli-1n1zb64f.js +2 -0
- package/dist/cli/client-2t9gjkck.js +2 -0
- package/dist/cli/commands-exafvm2b.js +2 -0
- package/dist/cli/connect-6zde0kn3.js +2 -0
- package/dist/cli/context-cmd-5t43wgqt.js +2 -0
- package/dist/cli/context-report-kt01pw8y.js +2 -0
- package/dist/cli/count-remote-ap7x3vh6.js +2 -0
- package/dist/cli/design-ne5zszyh.js +2 -0
- package/dist/cli/dispatch-2r5myxye.js +2 -0
- package/dist/cli/doctor-ws4fh4tn.js +3 -0
- package/dist/cli/executor-bdrjn634.js +2 -0
- package/dist/cli/export-1mxb9g5p.js +2 -0
- package/dist/cli/files-g104xghh.js +2 -0
- package/dist/cli/gauntlet-07xrjpj7.js +2 -0
- package/dist/cli/gauntlet-runner-xvy64436.js +10 -0
- package/dist/cli/gauntlet-wave3-jm91yt5w.js +5 -0
- package/dist/cli/gauntlet-wave4-r13py7p1.js +14 -0
- package/dist/cli/hashline-znvrat11.js +2 -0
- package/dist/cli/http-xafw6fsh.js +143 -0
- package/dist/cli/index-1sgjm25y.js +2 -0
- package/dist/cli/init-g2m0tn4m.js +51 -0
- package/dist/cli/install-avaqjjqq.js +2 -0
- package/dist/cli/loop-mmpfft01.js +2 -0
- package/dist/cli/main-0904f6ps.js +5 -0
- package/dist/cli/main-0ab9fc26.js +9 -0
- package/dist/cli/main-0jys2ccn.js +3 -0
- package/dist/cli/main-0mtcdbs7.js +3 -0
- package/dist/cli/main-0z1w2zsg.js +3 -0
- package/dist/cli/main-1dchs7xv.js +18 -0
- package/dist/cli/main-1ereejm1.js +3 -0
- package/dist/cli/main-1k1kw6b5.js +3 -0
- package/dist/cli/main-27y4sm2k.js +38 -0
- package/dist/cli/main-2wwjex5j.js +58 -0
- package/dist/cli/main-2yeveeve.js +6 -0
- package/dist/cli/main-2yfck9b5.js +3 -0
- package/dist/cli/main-2zmzgkwh.js +3 -0
- package/dist/cli/main-351pz3z7.js +7 -0
- package/dist/cli/main-3gjqfh7a.js +6 -0
- package/dist/cli/main-3nf3kgve.js +3 -0
- package/dist/cli/main-3pjrb2hd.js +3 -0
- package/dist/cli/main-3rxcvgna.js +19 -0
- package/dist/cli/main-4b3jgy66.js +19 -0
- package/dist/cli/main-4wndhjdc.js +7 -0
- package/dist/cli/main-4xcmvxnk.js +3 -0
- package/dist/cli/main-5tbz0wbz.js +4 -0
- package/dist/cli/main-5ywnwthm.js +3 -0
- package/dist/cli/main-6b62vkz0.js +14 -0
- package/dist/cli/main-6dnk69vp.js +3 -0
- package/dist/cli/main-6genrmhs.js +136 -0
- package/dist/cli/main-73g7eff4.js +15 -0
- package/dist/cli/main-7c5thhjd.js +5 -0
- package/dist/cli/main-7rn6bqje.js +3 -0
- package/dist/cli/main-80haw7qk.js +4 -0
- package/dist/cli/main-875s60s2.js +4 -0
- package/dist/cli/main-8kjxbpw4.js +8 -0
- package/dist/cli/main-90ds1z4e.js +10 -0
- package/dist/cli/main-9etavkew.js +3 -0
- package/dist/cli/main-a9njrkk1.js +3 -0
- package/dist/cli/main-aecrjq2d.js +12 -0
- package/dist/cli/main-ck9asesq.js +9 -0
- package/dist/cli/main-cta9racd.js +4 -0
- package/dist/cli/main-ddv7j2ag.js +3 -0
- package/dist/cli/main-dfreez27.js +10 -0
- package/dist/cli/main-f7rw7des.js +3 -0
- package/dist/cli/main-ggcn7rd7.js +5 -0
- package/dist/cli/main-gzkmycnv.js +3 -0
- package/dist/cli/main-hq51jg8v.js +18 -0
- package/dist/cli/main-jft389w9.js +8 -0
- package/dist/cli/main-k1eqkg83.js +3 -0
- package/dist/cli/main-k2y8a2aw.js +9 -0
- package/dist/cli/main-kcpbykxz.js +4 -0
- package/dist/cli/main-kd488vje.js +22 -0
- package/dist/cli/main-kh32yvgk.js +5 -0
- package/dist/cli/main-kqxnqjnv.js +25 -0
- package/dist/cli/main-kyn0xnsg.js +3 -0
- package/dist/cli/main-m1kk6fp5.js +21 -0
- package/dist/cli/main-mv40pcr2.js +4 -0
- package/dist/cli/main-n0t3973w.js +3 -0
- package/dist/cli/main-nqveez48.js +4 -0
- package/dist/cli/main-pknhvrmj.js +3 -0
- package/dist/cli/main-pn1w7a7j.js +3 -0
- package/dist/cli/main-prxxs70n.js +4 -0
- package/dist/cli/main-q3vsesf9.js +3 -0
- package/dist/cli/main-qsevpgsv.js +3 -0
- package/dist/cli/main-rdgdw24b.js +25 -0
- package/dist/cli/main-rfth4tbm.js +16 -0
- package/dist/cli/main-rg0wn0xf.js +5 -0
- package/dist/cli/main-sdmxhtv8.js +4 -0
- package/dist/cli/main-skbp13js.js +18 -0
- package/dist/cli/main-t4xnd213.js +7 -0
- package/dist/cli/main-vqak588n.js +4 -0
- package/dist/cli/main-w2n1303f.js +9 -0
- package/dist/cli/main-wbrdspr2.js +5 -0
- package/dist/cli/main-wsrg79c1.js +7 -0
- package/dist/cli/main-x4r0fne4.js +5 -0
- package/dist/cli/main-xea2f3tn.js +6 -0
- package/dist/cli/main-xg704a3c.js +3 -0
- package/dist/cli/main-xvnrabfp.js +16 -0
- package/dist/cli/main-xy53xf0r.js +4 -0
- package/dist/cli/main-y1fqy60y.js +3 -0
- package/dist/cli/main-yn8cd281.js +34 -0
- package/dist/cli/main-yr0ksc0h.js +4 -0
- package/dist/cli/main-z2ex2vyf.js +4 -0
- package/dist/cli/main-z3aayzvq.js +3 -0
- package/dist/cli/main-zaqh35jg.js +3 -0
- package/dist/cli/main-zc2e8e46.js +4 -0
- package/dist/cli/main-zzrfw6cf.js +13 -0
- package/dist/cli/main.js +280 -0
- package/dist/cli/market-cmd-e14kmx9n.js +5 -0
- package/dist/cli/mcp-login-wq7ktdek.js +2 -0
- package/dist/cli/mcp-market-cmd-9mg3jecy.js +2 -0
- package/dist/cli/notify-b7qc0cjb.js +2 -0
- package/dist/cli/oauth-z8whcgfx.js +2 -0
- package/dist/cli/output-b3ewj3ps.js +16 -0
- package/dist/cli/profiles-6mr5he5e.js +2 -0
- package/dist/cli/provider-config-g7j42q8x.js +2 -0
- package/dist/cli/provider-jr1y8vvm.js +2 -0
- package/dist/cli/registry-s8yk86g0.js +2 -0
- package/dist/cli/registry-t6p8d4mn.js +2 -0
- package/dist/cli/repl-bajwe1mh.js +11 -0
- package/dist/cli/resume-rwn9nz7y.js +2 -0
- package/dist/cli/run-flags-nah7ndpt.js +2 -0
- package/dist/cli/runtime-n7gafzhb.js +2 -0
- package/dist/cli/sandbox-config-emdy18x4.js +2 -0
- package/dist/cli/server-b0nvs2bn.js +5 -0
- package/dist/cli/session-arg-y75wd4kj.js +2 -0
- package/dist/cli/session-j62evmjq.js +2 -0
- package/dist/cli/sessions-cmd-tsnwz0ns.js +7 -0
- package/dist/cli/settings-df10wfez.js +2 -0
- package/dist/cli/setup-jzvv72fg.js +2 -0
- package/dist/cli/sextant-smoke-37m81ke6.js +5 -0
- package/dist/cli/skills-cmd-gjxnxnhx.js +2 -0
- package/dist/cli/smoke-p7748apt.js +8 -0
- package/dist/cli/start-chat-s4st3mm0.js +12 -0
- package/dist/cli/stream-gmeyewds.js +2 -0
- package/dist/cli/task-gh0kkp3n.js +2 -0
- package/dist/cli/tasks-z1kfpe8e.js +2 -0
- package/dist/cli/thinking-0eqkrz6t.js +2 -0
- package/dist/cli/todo-5brcrt9m.js +2 -0
- package/dist/cli/tools-7pzm0vj9.js +2 -0
- package/dist/cli/tools-s635p6s8.js +2 -0
- package/dist/cli/trust-cmd-cjav8zgm.js +2 -0
- package/dist/cli/update-check-pt31bm2f.js +2 -0
- package/dist/cli/update-cmd-tk131s9t.js +2 -0
- package/dist/cli/voice-56nabd8d.js +2 -0
- package/dist/cli/webfetch-xd8q596m.js +2 -0
- package/dist/cli/websearch-5hkf98k1.js +2 -0
- package/dist/cli/workflow-cmd-cy3cvzjp.js +4 -0
- package/dist/cli/workspace-q10g5z3e.js +2 -0
- package/dist/lib/index.js +62 -0
- package/dist/lib/models-index.json +1 -0
- package/dist/lib/plugins.js +55 -0
- package/dist/lib/providers.js +17 -0
- package/dist/lib/public-api.js +20 -0
- package/dist/lib/sdk.js +360 -0
- package/package.json +84 -72
- package/bin/rovecode.ts +0 -21
- package/src/acp/server.ts +0 -374
- package/src/cli/auth-login.ts +0 -122
- package/src/cli/connect.ts +0 -244
- package/src/cli/context-cmd.ts +0 -199
- package/src/cli/dispatch.ts +0 -82
- package/src/cli/doctor.ts +0 -362
- package/src/cli/export.ts +0 -276
- package/src/cli/help.ts +0 -293
- package/src/cli/is-tui-invocation.ts +0 -8
- package/src/cli/main.ts +0 -583
- package/src/cli/market-cmd.ts +0 -658
- package/src/cli/mcp-login.ts +0 -141
- package/src/cli/mcp-market-cmd.ts +0 -302
- package/src/cli/output.ts +0 -382
- package/src/cli/repl.ts +0 -250
- package/src/cli/repomap-root.ts +0 -14
- package/src/cli/resume.ts +0 -57
- package/src/cli/run-flags.ts +0 -43
- package/src/cli/run-limits.ts +0 -78
- package/src/cli/runtime.ts +0 -931
- package/src/cli/session-arg.ts +0 -30
- package/src/cli/sessions-cmd.ts +0 -145
- package/src/cli/setup.ts +0 -153
- package/src/cli/skills-cmd.ts +0 -194
- package/src/cli/start-chat.ts +0 -65
- package/src/cli/trust-cmd.ts +0 -52
- package/src/coding/bash.ts +0 -148
- package/src/coding/checkpoints.ts +0 -327
- package/src/coding/diff.ts +0 -138
- package/src/coding/files.ts +0 -341
- package/src/coding/hashline.ts +0 -274
- package/src/coding/lsp-gate.ts +0 -254
- package/src/coding/lsp-servers.ts +0 -147
- package/src/coding/lsp.ts +0 -283
- package/src/coding/repomap-cache.ts +0 -99
- package/src/coding/repomap-files.ts +0 -192
- package/src/coding/repomap.ts +0 -481
- package/src/core/agents.ts +0 -255
- package/src/core/compaction.ts +0 -259
- package/src/core/config.ts +0 -289
- package/src/core/context-report.ts +0 -228
- package/src/core/context.ts +0 -60
- package/src/core/count-remote.ts +0 -107
- package/src/core/execpolicy-rules.ts +0 -196
- package/src/core/execpolicy.ts +0 -385
- package/src/core/executor.ts +0 -454
- package/src/core/guardrails.ts +0 -400
- package/src/core/hooks.ts +0 -411
- package/src/core/images.ts +0 -230
- package/src/core/intro.ts +0 -266
- package/src/core/loop.ts +0 -567
- package/src/core/modes.ts +0 -372
- package/src/core/orchestrator.ts +0 -245
- package/src/core/proc-group.ts +0 -48
- package/src/core/project-trust.ts +0 -98
- package/src/core/reflection.ts +0 -165
- package/src/core/sandbox-config.ts +0 -186
- package/src/core/session-id.ts +0 -24
- package/src/core/session-images.ts +0 -73
- package/src/core/session-ops.ts +0 -183
- package/src/core/session-text.ts +0 -29
- package/src/core/session.ts +0 -469
- package/src/core/settings.ts +0 -170
- package/src/core/tasks.ts +0 -646
- package/src/core/token-scale.ts +0 -108
- package/src/core/tools.ts +0 -309
- package/src/core/trust.ts +0 -104
- package/src/core/types.ts +0 -330
- package/src/core/update-check.ts +0 -171
- package/src/core/usage.ts +0 -204
- package/src/core/validate.ts +0 -121
- package/src/core/verify-gate.ts +0 -159
- package/src/core/verify.ts +0 -236
- package/src/core/voice.ts +0 -158
- package/src/core/win-job.ts +0 -183
- package/src/core/workspace.ts +0 -184
- package/src/design/audit.ts +0 -797
- package/src/design/direction.ts +0 -190
- package/src/design/rules.ts +0 -157
- package/src/eval/bench.ts +0 -150
- package/src/eval/gauntlet-runner.ts +0 -215
- package/src/eval/gauntlet-support.ts +0 -84
- package/src/eval/gauntlet-wave3.ts +0 -269
- package/src/eval/gauntlet-wave4.ts +0 -217
- package/src/eval/gauntlet.ts +0 -253
- package/src/index.ts +0 -17
- package/src/lanes/agy.ts +0 -95
- package/src/lanes/approval.ts +0 -24
- package/src/lanes/claude.ts +0 -129
- package/src/lanes/codex.ts +0 -127
- package/src/lanes/events.ts +0 -130
- package/src/lanes/job.ts +0 -142
- package/src/lanes/opencode.ts +0 -122
- package/src/lanes/process.ts +0 -184
- package/src/lanes/progress.ts +0 -183
- package/src/lanes/registry.ts +0 -178
- package/src/lanes/runner.ts +0 -124
- package/src/lanes/types.ts +0 -112
- package/src/market/catalogs/mcp-docs.json +0 -111
- package/src/market/catalogs/plugins.json +0 -111
- package/src/market/catalogs/skills.json +0 -478
- package/src/market/clone.ts +0 -72
- package/src/market/context-cost.ts +0 -121
- package/src/market/digest.ts +0 -106
- package/src/market/index.ts +0 -22
- package/src/market/install.ts +0 -578
- package/src/market/manifest.ts +0 -187
- package/src/market/prereq.ts +0 -145
- package/src/market/registry.ts +0 -363
- package/src/market/resolve.ts +0 -111
- package/src/market/types.ts +0 -236
- package/src/market/validate.ts +0 -227
- package/src/mcp/client.ts +0 -449
- package/src/mcp/config.ts +0 -252
- package/src/mcp/local-package.ts +0 -211
- package/src/mcp/market-catalog.ts +0 -84
- package/src/mcp/market-install.ts +0 -289
- package/src/mcp/market.ts +0 -362
- package/src/mcp/oauth.ts +0 -251
- package/src/mcp/prompts-resources.ts +0 -249
- package/src/mcp/shared.ts +0 -149
- package/src/mcp/status.ts +0 -67
- package/src/mcp/tools.ts +0 -275
- package/src/mcp/transport.ts +0 -122
- package/src/mcp/trust.ts +0 -25
- package/src/memory/blocks.ts +0 -278
- package/src/memory/recall.ts +0 -355
- package/src/memory/scope.ts +0 -182
- package/src/memory/store.ts +0 -105
- package/src/memory/tools.ts +0 -99
- package/src/plugins/cli.ts +0 -119
- package/src/plugins/discover.ts +0 -108
- package/src/plugins/index.ts +0 -50
- package/src/plugins/install.ts +0 -184
- package/src/plugins/load.ts +0 -124
- package/src/plugins/manifest.ts +0 -92
- package/src/plugins/state.ts +0 -83
- package/src/providers/auth.ts +0 -408
- package/src/providers/cache.ts +0 -223
- package/src/providers/catalog-local.ts +0 -160
- package/src/providers/catalog.ts +0 -421
- package/src/providers/middleware-context.ts +0 -86
- package/src/providers/middleware.ts +0 -373
- package/src/providers/model-list.ts +0 -23
- package/src/providers/oauth/common.ts +0 -105
- package/src/providers/oauth/device-code.ts +0 -107
- package/src/providers/oauth/github-copilot.ts +0 -146
- package/src/providers/oauth/loopback.ts +0 -158
- package/src/providers/oauth/openai.ts +0 -163
- package/src/providers/oauth/openrouter.ts +0 -89
- package/src/providers/oauth/pkce.ts +0 -45
- package/src/providers/oauth/registry.ts +0 -39
- package/src/providers/oauth/seam.ts +0 -89
- package/src/providers/profile-glm53.ts +0 -111
- package/src/providers/profile-sonnet5-persona.ts +0 -65
- package/src/providers/profile-sonnet5-voice.ts +0 -23
- package/src/providers/profiles.ts +0 -156
- package/src/providers/provider-config.ts +0 -311
- package/src/providers/registry.ts +0 -333
- package/src/providers/responses.ts +0 -209
- package/src/providers/retry.ts +0 -234
- package/src/providers/router.ts +0 -294
- package/src/providers/sse.ts +0 -26
- package/src/providers/stream-errors.ts +0 -117
- package/src/providers/stream.ts +0 -566
- package/src/providers/thinking.ts +0 -189
- package/src/providers/wire-messages.ts +0 -129
- package/src/providers/wire-responses.ts +0 -79
- package/src/providers/wire-select.ts +0 -53
- package/src/server/http.ts +0 -291
- package/src/server/openapi.ts +0 -246
- package/src/sextant/card-hits.ts +0 -102
- package/src/sextant/card-keys.ts +0 -55
- package/src/sextant/context-source.ts +0 -157
- package/src/sextant/crew-cards.ts +0 -350
- package/src/sextant/draw-agents.ts +0 -273
- package/src/sextant/draw-code.ts +0 -388
- package/src/sextant/draw-context.ts +0 -222
- package/src/sextant/draw-frame.ts +0 -164
- package/src/sextant/draw-market.ts +0 -573
- package/src/sextant/draw-messages.ts +0 -386
- package/src/sextant/draw-pet.ts +0 -230
- package/src/sextant/draw-plan.ts +0 -187
- package/src/sextant/draw-tabs.ts +0 -85
- package/src/sextant/draw-util.ts +0 -65
- package/src/sextant/draw-wizard.ts +0 -378
- package/src/sextant/engine.ts +0 -230
- package/src/sextant/frame-hits.ts +0 -25
- package/src/sextant/frame.ts +0 -101
- package/src/sextant/git-status.ts +0 -197
- package/src/sextant/grid.ts +0 -59
- package/src/sextant/input.ts +0 -119
- package/src/sextant/keys.ts +0 -521
- package/src/sextant/layout.ts +0 -86
- package/src/sextant/local-commands.ts +0 -169
- package/src/sextant/market-source.ts +0 -287
- package/src/sextant/mentions.ts +0 -200
- package/src/sextant/message-hits.ts +0 -26
- package/src/sextant/model.ts +0 -387
- package/src/sextant/overlays.ts +0 -456
- package/src/sextant/panel-hits.ts +0 -38
- package/src/sextant/pet.ts +0 -399
- package/src/sextant/screen.ts +0 -324
- package/src/sextant/scroll-hits.ts +0 -66
- package/src/sextant/scrollbar.ts +0 -82
- package/src/sextant/sextant-bridge.ts +0 -174
- package/src/sextant/sextant-cards.ts +0 -142
- package/src/sextant/sextant-diff-base.ts +0 -63
- package/src/sextant/sextant-files.ts +0 -154
- package/src/sextant/sextant-frame-loop.ts +0 -335
- package/src/sextant/sextant-renderer.ts +0 -574
- package/src/sextant/sextant-repo.ts +0 -140
- package/src/sextant/theme.ts +0 -66
- package/src/sextant/tool-rows.ts +0 -189
- package/src/sextant/types.ts +0 -493
- package/src/skills/index.ts +0 -387
- package/src/skills/pack.ts +0 -220
- package/src/skills/spec.ts +0 -162
- package/src/skills/tools.ts +0 -69
- package/src/skills/versioned.ts +0 -227
- package/src/telemetry/otel-export.ts +0 -122
- package/src/telemetry/otel-lanes.ts +0 -89
- package/src/telemetry/otel-logs.ts +0 -131
- package/src/telemetry/otel-metrics.ts +0 -136
- package/src/telemetry/otel.ts +0 -397
- package/src/telemetry/otlp.ts +0 -76
- package/src/tools/ask-user.ts +0 -156
- package/src/tools/bash-bg.ts +0 -94
- package/src/tools/bash-jobs.ts +0 -237
- package/src/tools/design.ts +0 -151
- package/src/tools/evalcell.ts +0 -338
- package/src/tools/html-text.ts +0 -139
- package/src/tools/provider.ts +0 -149
- package/src/tools/task.ts +0 -250
- package/src/tools/todo.ts +0 -320
- package/src/tools/webfetch.ts +0 -332
- package/src/tools/websearch.ts +0 -359
- package/src/tui/agents-cmd.ts +0 -41
- package/src/tui/app.ts +0 -749
- package/src/tui/attach.ts +0 -127
- package/src/tui/boot-notes.ts +0 -41
- package/src/tui/builtin-prompts.ts +0 -59
- package/src/tui/checkpoints-cmd.ts +0 -70
- package/src/tui/clipboard-image.ts +0 -81
- package/src/tui/clipboard.ts +0 -78
- package/src/tui/commands.ts +0 -283
- package/src/tui/config-view.ts +0 -53
- package/src/tui/context-cmds.ts +0 -282
- package/src/tui/cost.ts +0 -108
- package/src/tui/crash-guard.ts +0 -173
- package/src/tui/focus-terminal.ts +0 -34
- package/src/tui/git-cmds.ts +0 -273
- package/src/tui/git-plain.ts +0 -58
- package/src/tui/info-cmd.ts +0 -150
- package/src/tui/input-plain.ts +0 -76
- package/src/tui/mcp-cmd.ts +0 -128
- package/src/tui/memory-note.ts +0 -77
- package/src/tui/modes-cmd.ts +0 -45
- package/src/tui/notify-seq.ts +0 -100
- package/src/tui/notify.ts +0 -318
- package/src/tui/overlays.ts +0 -97
- package/src/tui/pi-renderer.ts +0 -428
- package/src/tui/providers-cmd.ts +0 -377
- package/src/tui/reasoning-view.ts +0 -56
- package/src/tui/renderer.ts +0 -128
- package/src/tui/replay-marker.ts +0 -29
- package/src/tui/session-cmd.ts +0 -148
- package/src/tui/session-manage.ts +0 -95
- package/src/tui/sextant-attach.ts +0 -102
- package/src/tui/sextant-io.ts +0 -202
- package/src/tui/sextant-smoke.ts +0 -110
- package/src/tui/shell-cmd.ts +0 -158
- package/src/tui/smoke.ts +0 -72
- package/src/tui/staged-terminal.ts +0 -50
- package/src/tui/startup.ts +0 -12
- package/src/tui/theme.ts +0 -59
- package/src/tui/todo-label.ts +0 -7
- package/src/tui/trust-card.ts +0 -107
- package/src/tui/tui-commands.ts +0 -87
- package/tsconfig.json +0 -30
- package/vendor/pi-tui/LICENSE +0 -21
- package/vendor/pi-tui/PATCHES.md +0 -12
- package/vendor/pi-tui/PROVENANCE.md +0 -12
- package/vendor/pi-tui/README.upstream.md +0 -854
- package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/vendor/pi-tui/src/alt-screen-search.ts +0 -158
- package/vendor/pi-tui/src/autocomplete.ts +0 -827
- package/vendor/pi-tui/src/components/alt-screen-flash.ts +0 -52
- package/vendor/pi-tui/src/components/box.ts +0 -138
- package/vendor/pi-tui/src/components/cancellable-loader.ts +0 -41
- package/vendor/pi-tui/src/components/editor.ts +0 -2364
- package/vendor/pi-tui/src/components/h-stack.ts +0 -45
- package/vendor/pi-tui/src/components/image.ts +0 -128
- package/vendor/pi-tui/src/components/input.ts +0 -448
- package/vendor/pi-tui/src/components/loader.ts +0 -93
- package/vendor/pi-tui/src/components/markdown.ts +0 -1016
- package/vendor/pi-tui/src/components/scroll-view.ts +0 -217
- package/vendor/pi-tui/src/components/select-list.ts +0 -230
- package/vendor/pi-tui/src/components/settings-list.ts +0 -277
- package/vendor/pi-tui/src/components/spacer.ts +0 -29
- package/vendor/pi-tui/src/components/stack.ts +0 -155
- package/vendor/pi-tui/src/components/text.ts +0 -108
- package/vendor/pi-tui/src/components/truncated-text.ts +0 -66
- package/vendor/pi-tui/src/components/v-stack.ts +0 -34
- package/vendor/pi-tui/src/editor-component.ts +0 -75
- package/vendor/pi-tui/src/fuzzy.ts +0 -138
- package/vendor/pi-tui/src/index.ts +0 -149
- package/vendor/pi-tui/src/keybindings.ts +0 -321
- package/vendor/pi-tui/src/keys.ts +0 -1402
- package/vendor/pi-tui/src/kill-ring.ts +0 -47
- package/vendor/pi-tui/src/latex.ts +0 -1381
- package/vendor/pi-tui/src/layout-node.ts +0 -52
- package/vendor/pi-tui/src/layout.ts +0 -411
- package/vendor/pi-tui/src/native-modifiers.ts +0 -60
- package/vendor/pi-tui/src/native-module-path.ts +0 -32
- package/vendor/pi-tui/src/stdin-buffer.ts +0 -445
- package/vendor/pi-tui/src/terminal-colors.ts +0 -74
- package/vendor/pi-tui/src/terminal-image.ts +0 -701
- package/vendor/pi-tui/src/terminal.ts +0 -554
- package/vendor/pi-tui/src/tui-alt-screen.ts +0 -1379
- package/vendor/pi-tui/src/tui-main-screen.ts +0 -655
- package/vendor/pi-tui/src/tui.ts +0 -1264
- package/vendor/pi-tui/src/undo-stack.ts +0 -29
- package/vendor/pi-tui/src/utils.ts +0 -1327
- package/vendor/pi-tui/src/word-navigation.ts +0 -118
- package/vendor/pi-tui/test/test-themes.ts +0 -39
- package/vendor/pi-tui/test/virtual-terminal.ts +0 -219
- /package/{src/providers → dist/cli}/models-index.json +0 -0
package/src/coding/files.ts
DELETED
|
@@ -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();
|
package/src/coding/hashline.ts
DELETED
|
@@ -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";
|