@epoch-ai/cli 2.2.5 → 2.2.7
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/.artifacts/unit/junit.xml +2823 -0
- package/.project-map/backups/20260530_223453/.project-map.json +90101 -0
- package/.project-map/backups/20260530_223507/.project-map.json +90101 -0
- package/.project-map/backups/20260530_223512/.project-map.json +90101 -0
- package/.project-map/backups/20260530_223512/map.toon +666 -0
- package/.project-map/backups/20260530_223516/.project-map.json +90101 -0
- package/.project-map/backups/20260530_223516/map.toon +666 -0
- package/.project-map/backups/20260530_223520/.project-map.json +90101 -0
- package/.project-map/backups/20260530_223520/map.toon +666 -0
- package/AGENTS.md +47 -0
- package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
- package/Dockerfile +18 -0
- package/README.md +15 -0
- package/bunfig.toml +7 -0
- package/drizzle.config.ts +10 -0
- package/git +0 -0
- package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
- package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
- package/migration/20260211171708_add_project_commands/migration.sql +1 -0
- package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
- package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
- package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
- package/migration/20260225215848_workspace/migration.sql +7 -0
- package/migration/20260225215848_workspace/snapshot.json +959 -0
- package/migration/20260227213759_add_session_workspace_id/migration.sql +2 -0
- package/migration/20260227213759_add_session_workspace_id/snapshot.json +983 -0
- package/migration/20260228203230_blue_harpoon/migration.sql +17 -0
- package/migration/20260228203230_blue_harpoon/snapshot.json +1102 -0
- package/migration/20260303231226_add_workspace_fields/migration.sql +5 -0
- package/migration/20260303231226_add_workspace_fields/snapshot.json +1013 -0
- package/migration/20260309230000_move_org_to_state/migration.sql +3 -0
- package/migration/20260309230000_move_org_to_state/snapshot.json +1156 -0
- package/migration/20260312043431_session_message_cursor/migration.sql +4 -0
- package/migration/20260312043431_session_message_cursor/snapshot.json +1168 -0
- package/migration/20260323234822_events/migration.sql +13 -0
- package/migration/20260323234822_events/snapshot.json +1271 -0
- package/migration/20260418092949_add_yolo_to_session/migration.sql +2 -0
- package/migration/20260418092949_add_yolo_to_session/snapshot.json +1199 -0
- package/migration/20260419120000_add_intervention_to_session/migration.sql +2 -0
- package/package.json +179 -18
- package/parsers-config.ts +290 -0
- package/script/build-node.ts +71 -0
- package/script/build.ts +255 -0
- package/script/check-migrations.ts +16 -0
- package/script/fix-node-pty.ts +28 -0
- package/script/publish.ts +182 -0
- package/script/schema.ts +63 -0
- package/script/seed-e2e.ts +60 -0
- package/script/upgrade-opentui.ts +64 -0
- package/specs/effect-migration.md +310 -0
- package/specs/tui-plugins.md +436 -0
- package/specs/v2.md +14 -0
- package/src/account/account.sql.ts +39 -0
- package/src/account/index.ts +465 -0
- package/src/account/repo.ts +163 -0
- package/src/account/schema.ts +91 -0
- package/src/acp/README.md +174 -0
- package/src/acp/agent.ts +1847 -0
- package/src/acp/session.ts +116 -0
- package/src/acp/types.ts +24 -0
- package/src/agent/agent.ts +445 -0
- package/src/agent/generate.txt +75 -0
- package/src/agent/prompt/compaction.txt +15 -0
- package/src/agent/prompt/explore.txt +9 -0
- package/src/agent/prompt/summary.txt +11 -0
- package/src/agent/prompt/title.txt +44 -0
- package/src/auth/index.ts +110 -0
- package/src/bus/bus-event.ts +40 -0
- package/src/bus/global.ts +10 -0
- package/src/bus/index.ts +232 -0
- package/src/cli/bootstrap.ts +17 -0
- package/src/cli/cmd/account.ts +257 -0
- package/src/cli/cmd/acp.ts +70 -0
- package/src/cli/cmd/agent.ts +245 -0
- package/src/cli/cmd/cmd.ts +7 -0
- package/src/cli/cmd/db.ts +119 -0
- package/src/cli/cmd/debug/agent.ts +167 -0
- package/src/cli/cmd/debug/config.ts +16 -0
- package/src/cli/cmd/debug/file.ts +97 -0
- package/src/cli/cmd/debug/index.ts +48 -0
- package/src/cli/cmd/debug/lsp.ts +53 -0
- package/src/cli/cmd/debug/ripgrep.ts +87 -0
- package/src/cli/cmd/debug/scrap.ts +16 -0
- package/src/cli/cmd/debug/skill.ts +16 -0
- package/src/cli/cmd/debug/snapshot.ts +52 -0
- package/src/cli/cmd/export.ts +89 -0
- package/src/cli/cmd/generate.ts +38 -0
- package/src/cli/cmd/github.ts +1639 -0
- package/src/cli/cmd/import.ts +169 -0
- package/src/cli/cmd/mcp.ts +754 -0
- package/src/cli/cmd/models.ts +78 -0
- package/src/cli/cmd/plug.ts +233 -0
- package/src/cli/cmd/pr.ts +127 -0
- package/src/cli/cmd/providers.ts +478 -0
- package/src/cli/cmd/run.ts +681 -0
- package/src/cli/cmd/serve.ts +24 -0
- package/src/cli/cmd/session.ts +159 -0
- package/src/cli/cmd/stats.ts +410 -0
- package/src/cli/cmd/tui/app.tsx +945 -0
- package/src/cli/cmd/tui/attach.ts +88 -0
- package/src/cli/cmd/tui/component/border.tsx +21 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-command.tsx +171 -0
- package/src/cli/cmd/tui/component/dialog-console-org.tsx +103 -0
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-model.tsx +190 -0
- package/src/cli/cmd/tui/component/dialog-provider.tsx +364 -0
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +108 -0
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
- package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
- package/src/cli/cmd/tui/component/dialog-status.tsx +168 -0
- package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
- package/src/cli/cmd/tui/component/dialog-variant.tsx +39 -0
- package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +320 -0
- package/src/cli/cmd/tui/component/error-component.tsx +92 -0
- package/src/cli/cmd/tui/component/logo.tsx +85 -0
- package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +672 -0
- package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
- package/src/cli/cmd/tui/component/prompt/history.tsx +109 -0
- package/src/cli/cmd/tui/component/prompt/index.tsx +1348 -0
- package/src/cli/cmd/tui/component/prompt/part.ts +16 -0
- package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
- package/src/cli/cmd/tui/component/spinner.tsx +24 -0
- package/src/cli/cmd/tui/component/startup-loading.tsx +63 -0
- package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
- package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
- package/src/cli/cmd/tui/component/workspace/dialog-session-list.tsx +151 -0
- package/src/cli/cmd/tui/context/args.tsx +15 -0
- package/src/cli/cmd/tui/context/directory.ts +13 -0
- package/src/cli/cmd/tui/context/exit.tsx +60 -0
- package/src/cli/cmd/tui/context/helper.tsx +25 -0
- package/src/cli/cmd/tui/context/keybind.tsx +105 -0
- package/src/cli/cmd/tui/context/kv.tsx +52 -0
- package/src/cli/cmd/tui/context/local.tsx +456 -0
- package/src/cli/cmd/tui/context/plugin-keybinds.ts +41 -0
- package/src/cli/cmd/tui/context/prompt.tsx +18 -0
- package/src/cli/cmd/tui/context/route.tsx +52 -0
- package/src/cli/cmd/tui/context/sdk.tsx +115 -0
- package/src/cli/cmd/tui/context/sync.tsx +516 -0
- package/src/cli/cmd/tui/context/theme/aura.json +69 -0
- package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
- package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
- package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
- package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
- package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
- package/src/cli/cmd/tui/context/theme/epochcli.json +245 -0
- package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
- package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
- package/src/cli/cmd/tui/context/theme/github.json +233 -0
- package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
- package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
- package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
- package/src/cli/cmd/tui/context/theme/material.json +235 -0
- package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
- package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
- package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
- package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
- package/src/cli/cmd/tui/context/theme/nord.json +223 -0
- package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
- package/src/cli/cmd/tui/context/theme/orng.json +249 -0
- package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
- package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
- package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
- package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
- package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
- package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
- package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
- package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
- package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
- package/src/cli/cmd/tui/context/theme.tsx +1236 -0
- package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
- package/src/cli/cmd/tui/event.ts +48 -0
- package/src/cli/cmd/tui/feature-plugins/home/footer.tsx +93 -0
- package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +145 -0
- package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +50 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +82 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +62 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +93 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +66 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +96 -0
- package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +48 -0
- package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +270 -0
- package/src/cli/cmd/tui/plugin/api.tsx +397 -0
- package/src/cli/cmd/tui/plugin/index.ts +3 -0
- package/src/cli/cmd/tui/plugin/internal.ts +27 -0
- package/src/cli/cmd/tui/plugin/runtime.ts +1031 -0
- package/src/cli/cmd/tui/plugin/slots.tsx +60 -0
- package/src/cli/cmd/tui/routes/home.tsx +94 -0
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +65 -0
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +110 -0
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
- package/src/cli/cmd/tui/routes/session/index.tsx +2161 -0
- package/src/cli/cmd/tui/routes/session/permission.tsx +691 -0
- package/src/cli/cmd/tui/routes/session/question.tsx +468 -0
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +70 -0
- package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +131 -0
- package/src/cli/cmd/tui/thread.ts +241 -0
- package/src/cli/cmd/tui/ui/dialog-alert.tsx +59 -0
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +89 -0
- package/src/cli/cmd/tui/ui/dialog-export-options.tsx +211 -0
- package/src/cli/cmd/tui/ui/dialog-help.tsx +40 -0
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +115 -0
- package/src/cli/cmd/tui/ui/dialog-select.tsx +417 -0
- package/src/cli/cmd/tui/ui/dialog.tsx +192 -0
- package/src/cli/cmd/tui/ui/link.tsx +28 -0
- package/src/cli/cmd/tui/ui/spinner.ts +368 -0
- package/src/cli/cmd/tui/ui/toast.tsx +100 -0
- package/src/cli/cmd/tui/util/clipboard.ts +192 -0
- package/src/cli/cmd/tui/util/editor.ts +37 -0
- package/src/cli/cmd/tui/util/model.ts +23 -0
- package/src/cli/cmd/tui/util/provider-origin.ts +20 -0
- package/src/cli/cmd/tui/util/scroll.ts +23 -0
- package/src/cli/cmd/tui/util/selection.ts +25 -0
- package/src/cli/cmd/tui/util/signal.ts +7 -0
- package/src/cli/cmd/tui/util/terminal.ts +114 -0
- package/src/cli/cmd/tui/util/transcript.ts +112 -0
- package/src/cli/cmd/tui/win32.ts +129 -0
- package/src/cli/cmd/tui/worker.ts +195 -0
- package/src/cli/cmd/uninstall.ts +353 -0
- package/src/cli/cmd/upgrade.ts +73 -0
- package/src/cli/cmd/web.ts +81 -0
- package/src/cli/effect/prompt.ts +25 -0
- package/src/cli/error.ts +46 -0
- package/src/cli/heap.ts +59 -0
- package/src/cli/logo.ts +6 -0
- package/src/cli/network.ts +60 -0
- package/src/cli/ui.ts +133 -0
- package/src/cli/upgrade.ts +31 -0
- package/src/command/index.ts +197 -0
- package/src/command/template/initialize.txt +66 -0
- package/src/command/template/review.txt +101 -0
- package/src/config/config.ts +1610 -0
- package/src/config/console-state.ts +15 -0
- package/src/config/markdown.ts +99 -0
- package/src/config/paths.ts +167 -0
- package/src/config/tui-migrate.ts +155 -0
- package/src/config/tui-schema.ts +37 -0
- package/src/config/tui.ts +179 -0
- package/src/config/validator.ts +52 -0
- package/src/control-plane/adaptors/index.ts +20 -0
- package/src/control-plane/adaptors/worktree.ts +42 -0
- package/src/control-plane/schema.ts +17 -0
- package/src/control-plane/sse.ts +66 -0
- package/src/control-plane/types.ts +32 -0
- package/src/control-plane/workspace.sql.ts +17 -0
- package/src/control-plane/workspace.ts +168 -0
- package/src/effect/cross-spawn-spawner.ts +502 -0
- package/src/effect/instance-ref.ts +6 -0
- package/src/effect/instance-registry.ts +12 -0
- package/src/effect/instance-state.ts +82 -0
- package/src/effect/run-service.ts +33 -0
- package/src/effect/runner.ts +216 -0
- package/src/env/index.ts +28 -0
- package/src/file/ignore.ts +82 -0
- package/src/file/index.ts +686 -0
- package/src/file/protected.ts +59 -0
- package/src/file/ripgrep.ts +376 -0
- package/src/file/time.ts +133 -0
- package/src/file/watcher.ts +172 -0
- package/src/filesystem/index.ts +236 -0
- package/src/flag/flag.ts +157 -0
- package/src/format/formatter.ts +413 -0
- package/src/format/index.ts +203 -0
- package/src/git/index.ts +303 -0
- package/src/global/index.ts +54 -0
- package/src/id/id.ts +85 -0
- package/src/ide/index.ts +74 -0
- package/src/index.ts +253 -0
- package/src/installation/index.ts +355 -0
- package/src/installation/meta.ts +7 -0
- package/src/lsp/client.ts +256 -0
- package/src/lsp/index.ts +558 -0
- package/src/lsp/language.ts +120 -0
- package/src/lsp/launch.ts +21 -0
- package/src/lsp/server.ts +1968 -0
- package/src/mcp/auth.ts +173 -0
- package/src/mcp/index.ts +1250 -0
- package/src/mcp/oauth-callback.ts +216 -0
- package/src/mcp/oauth-provider.ts +185 -0
- package/src/mcp/schema-loader.ts +82 -0
- package/src/node.ts +1 -0
- package/src/npm/index.ts +188 -0
- package/src/patch/index.ts +680 -0
- package/src/permission/arity.ts +163 -0
- package/src/permission/evaluate.ts +15 -0
- package/src/permission/index.ts +323 -0
- package/src/permission/schema.ts +17 -0
- package/src/plugin/cloudflare.ts +67 -0
- package/src/plugin/codex.ts +608 -0
- package/src/plugin/github-copilot/copilot.ts +361 -0
- package/src/plugin/github-copilot/models.ts +144 -0
- package/src/plugin/index.ts +288 -0
- package/src/plugin/install.ts +439 -0
- package/src/plugin/loader.ts +174 -0
- package/src/plugin/meta.ts +188 -0
- package/src/plugin/shared.ts +323 -0
- package/src/project/bootstrap.ts +35 -0
- package/src/project/init-files.ts +328 -0
- package/src/project/instance.ts +175 -0
- package/src/project/project.sql.ts +16 -0
- package/src/project/project.ts +519 -0
- package/src/project/schema.ts +16 -0
- package/src/project/state.ts +70 -0
- package/src/project/vcs.ts +240 -0
- package/src/provider/auth.ts +253 -0
- package/src/provider/error.ts +297 -0
- package/src/provider/models.ts +162 -0
- package/src/provider/provider.ts +1776 -0
- package/src/provider/schema.ts +38 -0
- package/src/provider/sdk/copilot/README.md +5 -0
- package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
- package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
- package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
- package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
- package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +814 -0
- package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
- package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
- package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +83 -0
- package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
- package/src/provider/sdk/copilot/index.ts +2 -0
- package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
- package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +335 -0
- package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
- package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
- package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
- package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +214 -0
- package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1769 -0
- package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +173 -0
- package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
- package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +87 -0
- package/src/provider/sdk/copilot/responses/tool/file-search.ts +127 -0
- package/src/provider/sdk/copilot/responses/tool/image-generation.ts +114 -0
- package/src/provider/sdk/copilot/responses/tool/local-shell.ts +64 -0
- package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +103 -0
- package/src/provider/sdk/copilot/responses/tool/web-search.ts +102 -0
- package/src/provider/transform.ts +1124 -0
- package/src/pty/index.ts +397 -0
- package/src/pty/pty.bun.ts +26 -0
- package/src/pty/pty.node.ts +27 -0
- package/src/pty/pty.ts +25 -0
- package/src/pty/schema.ts +17 -0
- package/src/question/index.ts +224 -0
- package/src/question/schema.ts +17 -0
- package/src/server/error.ts +36 -0
- package/src/server/event.ts +7 -0
- package/src/server/instance.ts +315 -0
- package/src/server/mdns.ts +60 -0
- package/src/server/middleware.ts +33 -0
- package/src/server/projectors.ts +28 -0
- package/src/server/proxy.ts +130 -0
- package/src/server/router.ts +105 -0
- package/src/server/routes/config.ts +92 -0
- package/src/server/routes/event.ts +83 -0
- package/src/server/routes/experimental.ts +374 -0
- package/src/server/routes/file.ts +197 -0
- package/src/server/routes/global.ts +312 -0
- package/src/server/routes/mcp.ts +225 -0
- package/src/server/routes/permission.ts +69 -0
- package/src/server/routes/project.ts +118 -0
- package/src/server/routes/provider.ts +171 -0
- package/src/server/routes/pty.ts +210 -0
- package/src/server/routes/question.ts +99 -0
- package/src/server/routes/session.ts +984 -0
- package/src/server/routes/tui.ts +378 -0
- package/src/server/routes/workspace.ts +94 -0
- package/src/server/server.ts +353 -0
- package/src/session/compaction.ts +86 -0
- package/src/session/index.ts +904 -0
- package/src/session/instruction.ts +261 -0
- package/src/session/llm/monitor.ts +87 -0
- package/src/session/llm.ts +1676 -0
- package/src/session/message-v2.ts +1082 -0
- package/src/session/message.ts +191 -0
- package/src/session/overflow.ts +34 -0
- package/src/session/processor.ts +635 -0
- package/src/session/projectors.ts +136 -0
- package/src/session/prompt/build-switch.txt +5 -0
- package/src/session/prompt/builder.ts +135 -0
- package/src/session/prompt/default.txt +11 -0
- package/src/session/prompt/engine.ts +1072 -0
- package/src/session/prompt/gemma4.txt +1 -0
- package/src/session/prompt/max-steps.txt +16 -0
- package/src/session/prompt/orchestrator.ts +426 -0
- package/src/session/prompt/plan.txt +28 -0
- package/src/session/prompt/qwen.txt +19 -0
- package/src/session/prompt/resolver.ts +670 -0
- package/src/session/prompt/router.ts +197 -0
- package/src/session/prompt/state.ts +96 -0
- package/src/session/prompt/types.ts +115 -0
- package/src/session/prompt/utils.ts +15 -0
- package/src/session/prompt.ts +362 -0
- package/src/session/retry.ts +106 -0
- package/src/session/revert.ts +176 -0
- package/src/session/sanitizer.ts +125 -0
- package/src/session/schema.ts +38 -0
- package/src/session/session.sql.ts +106 -0
- package/src/session/status.ts +102 -0
- package/src/session/summary.ts +183 -0
- package/src/session/system.ts +79 -0
- package/src/session/todo.ts +166 -0
- package/src/session/worker.ts +382 -0
- package/src/shell/shell.ts +110 -0
- package/src/skill/discovery.ts +116 -0
- package/src/skill/index.ts +287 -0
- package/src/snapshot/index.ts +726 -0
- package/src/sql.d.ts +4 -0
- package/src/storage/db.bun.ts +8 -0
- package/src/storage/db.node.ts +8 -0
- package/src/storage/db.ts +174 -0
- package/src/storage/json-migration.ts +387 -0
- package/src/storage/schema.sql.ts +10 -0
- package/src/storage/schema.ts +4 -0
- package/src/storage/storage.ts +353 -0
- package/src/sync/README.md +179 -0
- package/src/sync/event.sql.ts +16 -0
- package/src/sync/index.ts +263 -0
- package/src/sync/schema.ts +14 -0
- package/src/tool/apply_patch.ts +281 -0
- package/src/tool/apply_patch.txt +1 -0
- package/src/tool/arbitration.txt +5 -0
- package/src/tool/bash.ts +494 -0
- package/src/tool/bash.txt +2 -0
- package/src/tool/batch.ts +183 -0
- package/src/tool/batch.txt +1 -0
- package/src/tool/codesearch.ts +132 -0
- package/src/tool/codesearch.txt +1 -0
- package/src/tool/edit.ts +734 -0
- package/src/tool/edit.txt +1 -0
- package/src/tool/external-directory.ts +46 -0
- package/src/tool/glob.ts +73 -0
- package/src/tool/glob.txt +2 -0
- package/src/tool/grep.ts +156 -0
- package/src/tool/grep.txt +2 -0
- package/src/tool/invalid.ts +20 -0
- package/src/tool/ls.ts +121 -0
- package/src/tool/ls.txt +1 -0
- package/src/tool/lsp.ts +97 -0
- package/src/tool/lsp.txt +1 -0
- package/src/tool/multiedit.ts +46 -0
- package/src/tool/multiedit.txt +1 -0
- package/src/tool/plan-enter.txt +14 -0
- package/src/tool/plan-exit.txt +13 -0
- package/src/tool/plan.ts +131 -0
- package/src/tool/question.ts +46 -0
- package/src/tool/question.txt +10 -0
- package/src/tool/read.ts +332 -0
- package/src/tool/read.txt +1 -0
- package/src/tool/registry.ts +288 -0
- package/src/tool/revert.ts +37 -0
- package/src/tool/schema.ts +17 -0
- package/src/tool/skill.ts +105 -0
- package/src/tool/task.ts +150 -0
- package/src/tool/task.txt +3 -0
- package/src/tool/task_complete.ts +21 -0
- package/src/tool/tool.ts +112 -0
- package/src/tool/truncate.ts +144 -0
- package/src/tool/truncation-dir.ts +4 -0
- package/src/tool/webfetch.ts +206 -0
- package/src/tool/webfetch.txt +1 -0
- package/src/tool/websearch.ts +150 -0
- package/src/tool/websearch.txt +1 -0
- package/src/tool/write.ts +101 -0
- package/src/tool/write.txt +1 -0
- package/src/util/abort.ts +35 -0
- package/src/util/ai-sdk.ts +59 -0
- package/src/util/archive.ts +17 -0
- package/src/util/color.ts +19 -0
- package/src/util/context.ts +25 -0
- package/src/util/data-url.ts +9 -0
- package/src/util/defer.ts +12 -0
- package/src/util/effect-http-client.ts +11 -0
- package/src/util/effect-zod.ts +98 -0
- package/src/util/error.ts +77 -0
- package/src/util/filesystem.ts +245 -0
- package/src/util/flock.ts +333 -0
- package/src/util/fn.ts +21 -0
- package/src/util/format.ts +20 -0
- package/src/util/glob.ts +34 -0
- package/src/util/hash.ts +7 -0
- package/src/util/iife.ts +3 -0
- package/src/util/keybind.ts +103 -0
- package/src/util/lazy.ts +23 -0
- package/src/util/locale.ts +81 -0
- package/src/util/lock.ts +98 -0
- package/src/util/log-parser.ts +114 -0
- package/src/util/log.ts +250 -0
- package/src/util/network.ts +23 -0
- package/src/util/process.ts +176 -0
- package/src/util/queue.ts +32 -0
- package/src/util/record.ts +3 -0
- package/src/util/rpc.ts +66 -0
- package/src/util/schema.ts +53 -0
- package/src/util/scrap.ts +10 -0
- package/src/util/session-analyzer.ts +331 -0
- package/src/util/session-telemetry.ts +91 -0
- package/src/util/signal.ts +12 -0
- package/src/util/timeout.ts +14 -0
- package/src/util/token.ts +7 -0
- package/src/util/tokenizer.ts +50 -0
- package/src/util/toon.ts +45 -0
- package/src/util/update-schema.ts +13 -0
- package/src/util/which.ts +14 -0
- package/src/util/wildcard.ts +59 -0
- package/src/worktree/index.ts +612 -0
- package/sst-env.d.ts +10 -0
- package/test/AGENTS.md +81 -0
- package/test/account/repo.test.ts +326 -0
- package/test/account/service.test.ts +393 -0
- package/test/acp/agent-interface.test.ts +51 -0
- package/test/acp/event-subscription.test.ts +685 -0
- package/test/agent/agent.test.ts +716 -0
- package/test/auth/auth.test.ts +58 -0
- package/test/bus/bus-effect.test.ts +164 -0
- package/test/bus/bus-integration.test.ts +87 -0
- package/test/bus/bus.test.ts +219 -0
- package/test/cli/account.test.ts +26 -0
- package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
- package/test/cli/github-action.test.ts +198 -0
- package/test/cli/github-remote.test.ts +80 -0
- package/test/cli/plugin-auth-picker.test.ts +120 -0
- package/test/cli/tui/keybind-plugin.test.ts +90 -0
- package/test/cli/tui/plugin-add.test.ts +107 -0
- package/test/cli/tui/plugin-install.test.ts +89 -0
- package/test/cli/tui/plugin-lifecycle.test.ts +225 -0
- package/test/cli/tui/plugin-loader-entrypoint.test.ts +492 -0
- package/test/cli/tui/plugin-loader-pure.test.ts +72 -0
- package/test/cli/tui/plugin-loader.test.ts +752 -0
- package/test/cli/tui/plugin-toggle.test.ts +159 -0
- package/test/cli/tui/slot-replace.test.tsx +47 -0
- package/test/cli/tui/theme-store.test.ts +51 -0
- package/test/cli/tui/thread.test.ts +128 -0
- package/test/cli/tui/transcript.test.ts +426 -0
- package/test/config/agent-color.test.ts +71 -0
- package/test/config/config.test.ts +2337 -0
- package/test/config/fixtures/empty-frontmatter.md +4 -0
- package/test/config/fixtures/frontmatter.md +28 -0
- package/test/config/fixtures/markdown-header.md +11 -0
- package/test/config/fixtures/no-frontmatter.md +1 -0
- package/test/config/fixtures/weird-model-id.md +13 -0
- package/test/config/markdown.test.ts +228 -0
- package/test/config/tui.test.ts +800 -0
- package/test/control-plane/sse.test.ts +56 -0
- package/test/effect/cross-spawn-spawner.test.ts +412 -0
- package/test/effect/instance-state.test.ts +482 -0
- package/test/effect/run-service.test.ts +46 -0
- package/test/effect/runner.test.ts +523 -0
- package/test/fake/provider.ts +82 -0
- package/test/file/fsmonitor.test.ts +62 -0
- package/test/file/ignore.test.ts +10 -0
- package/test/file/index.test.ts +946 -0
- package/test/file/path-traversal.test.ts +198 -0
- package/test/file/ripgrep.test.ts +54 -0
- package/test/file/time.test.ts +445 -0
- package/test/file/watcher.test.ts +247 -0
- package/test/filesystem/filesystem.test.ts +319 -0
- package/test/fixture/db.ts +11 -0
- package/test/fixture/fixture.test.ts +26 -0
- package/test/fixture/fixture.ts +172 -0
- package/test/fixture/flock-worker.ts +72 -0
- package/test/fixture/lsp/fake-lsp-server.js +77 -0
- package/test/fixture/plug-worker.ts +93 -0
- package/test/fixture/plugin-meta-worker.ts +26 -0
- package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
- package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
- package/test/fixture/skills/cloudflare/SKILL.md +211 -0
- package/test/fixture/skills/index.json +6 -0
- package/test/fixture/tui-plugin.ts +328 -0
- package/test/fixture/tui-runtime.ts +27 -0
- package/test/format/format.test.ts +171 -0
- package/test/git/git.test.ts +128 -0
- package/test/ide/ide.test.ts +82 -0
- package/test/installation/installation.test.ts +152 -0
- package/test/keybind.test.ts +421 -0
- package/test/lib/effect.ts +53 -0
- package/test/lib/filesystem.ts +10 -0
- package/test/lib/llm-server.ts +794 -0
- package/test/lsp/client.test.ts +95 -0
- package/test/lsp/index.test.ts +133 -0
- package/test/lsp/launch.test.ts +22 -0
- package/test/lsp/lifecycle.test.ts +147 -0
- package/test/mcp/headers.test.ts +153 -0
- package/test/mcp/lifecycle.test.ts +750 -0
- package/test/mcp/oauth-auto-connect.test.ts +199 -0
- package/test/mcp/oauth-browser.test.ts +249 -0
- package/test/mcp/sc-approve-validator.test.ts +431 -0
- package/test/memory/abort-leak.test.ts +137 -0
- package/test/npm.test.ts +18 -0
- package/test/patch/patch.test.ts +348 -0
- package/test/permission/arity.test.ts +33 -0
- package/test/permission/next.test.ts +1123 -0
- package/test/permission-task.test.ts +323 -0
- package/test/plugin/auth-override.test.ts +74 -0
- package/test/plugin/codex.test.ts +123 -0
- package/test/plugin/github-copilot-models.test.ts +117 -0
- package/test/plugin/install-concurrency.test.ts +140 -0
- package/test/plugin/install.test.ts +570 -0
- package/test/plugin/loader-shared.test.ts +1136 -0
- package/test/plugin/meta.test.ts +137 -0
- package/test/plugin/shared.test.ts +88 -0
- package/test/plugin/trigger.test.ts +111 -0
- package/test/preload.ts +90 -0
- package/test/project/migrate-global.test.ts +140 -0
- package/test/project/project.test.ts +459 -0
- package/test/project/state.test.ts +115 -0
- package/test/project/vcs.test.ts +228 -0
- package/test/project/worktree-remove.test.ts +96 -0
- package/test/project/worktree.test.ts +173 -0
- package/test/provider/amazon-bedrock.test.ts +447 -0
- package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
- package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
- package/test/provider/error.test.ts +49 -0
- package/test/provider/gitlab-duo.test.ts +412 -0
- package/test/provider/provider.test.ts +2494 -0
- package/test/provider/transform.test.ts +2944 -0
- package/test/pty/pty-output-isolation.test.ts +141 -0
- package/test/pty/pty-session.test.ts +92 -0
- package/test/pty/pty-shell.test.ts +59 -0
- package/test/question/question.test.ts +453 -0
- package/test/server/global-session-list.test.ts +89 -0
- package/test/server/project-init-git.test.ts +121 -0
- package/test/server/session-actions.test.ts +83 -0
- package/test/server/session-list.test.ts +98 -0
- package/test/server/session-messages.test.ts +159 -0
- package/test/server/session-select.test.ts +84 -0
- package/test/session/compaction.test.ts +683 -0
- package/test/session/continuity-handover.test.ts +620 -0
- package/test/session/deterministic-handover.test.ts +328 -0
- package/test/session/doom-protection.test.ts +247 -0
- package/test/session/hard-reset.test.ts +179 -0
- package/test/session/instruction.test.ts +286 -0
- package/test/session/llm/monitor.test.ts +53 -0
- package/test/session/llm-sanitizer.test.ts +90 -0
- package/test/session/llm-zones-e2e.test.ts +61 -0
- package/test/session/llm.test.ts +1308 -0
- package/test/session/mcpx-normalization.test.ts +86 -0
- package/test/session/mcpx-syntax-recovery.test.ts +28 -0
- package/test/session/message-v2.test.ts +957 -0
- package/test/session/messages-pagination.test.ts +885 -0
- package/test/session/processor-effect.test.ts +805 -0
- package/test/session/prompt/builder.test.ts +71 -0
- package/test/session/prompt/engine-loop.test.ts +80 -0
- package/test/session/prompt/orchestrator.test.ts +108 -0
- package/test/session/prompt/resolver.test.ts +211 -0
- package/test/session/prompt/router.test.ts +84 -0
- package/test/session/prompt/state.test.ts +57 -0
- package/test/session/prompt-effect.test.ts +1241 -0
- package/test/session/prompt.test.ts +522 -0
- package/test/session/refactor-system-zones.test.ts +241 -0
- package/test/session/retry.test.ts +232 -0
- package/test/session/revert-compact.test.ts +621 -0
- package/test/session/sanitizer.test.ts +61 -0
- package/test/session/session.test.ts +142 -0
- package/test/session/snapshot-tool-race.test.ts +242 -0
- package/test/session/structured-output-integration.test.ts +233 -0
- package/test/session/structured-output.test.ts +391 -0
- package/test/session/system.test.ts +59 -0
- package/test/session/telemetry.test.ts +35 -0
- package/test/shell/shell.test.ts +73 -0
- package/test/skill/discovery.test.ts +116 -0
- package/test/skill/skill.test.ts +392 -0
- package/test/snapshot/snapshot.test.ts +1404 -0
- package/test/storage/db.test.ts +14 -0
- package/test/storage/json-migration.test.ts +791 -0
- package/test/storage/storage.test.ts +295 -0
- package/test/sync/index.test.ts +191 -0
- package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
- package/test/tool/apply_patch.test.ts +567 -0
- package/test/tool/bash.test.ts +1099 -0
- package/test/tool/edit.test.ts +681 -0
- package/test/tool/external-directory.test.ts +198 -0
- package/test/tool/fixtures/large-image.png +0 -0
- package/test/tool/fixtures/models-api.json +65179 -0
- package/test/tool/grep.test.ts +111 -0
- package/test/tool/question.test.ts +126 -0
- package/test/tool/read.test.ts +468 -0
- package/test/tool/registry.test.ts +126 -0
- package/test/tool/skill.test.ts +167 -0
- package/test/tool/task.test.ts +49 -0
- package/test/tool/tool-define.test.ts +101 -0
- package/test/tool/truncation.test.ts +161 -0
- package/test/tool/webfetch.test.ts +101 -0
- package/test/tool/write.test.ts +354 -0
- package/test/util/data-url.test.ts +14 -0
- package/test/util/effect-zod.test.ts +61 -0
- package/test/util/error.test.ts +38 -0
- package/test/util/filesystem.test.ts +656 -0
- package/test/util/flock.test.ts +383 -0
- package/test/util/format.test.ts +59 -0
- package/test/util/glob.test.ts +164 -0
- package/test/util/iife.test.ts +36 -0
- package/test/util/lazy.test.ts +50 -0
- package/test/util/lock.test.ts +72 -0
- package/test/util/log-parser.test.ts +61 -0
- package/test/util/module.test.ts +59 -0
- package/test/util/process.test.ts +128 -0
- package/test/util/telemetry-integration.test.ts +104 -0
- package/test/util/timeout.test.ts +21 -0
- package/test/util/which.test.ts +100 -0
- package/test/util/wildcard.test.ts +90 -0
- package/test-regex.js +50 -0
- package/tsconfig.json +23 -0
- package/LICENSE +0 -21
- /package/{postinstall.mjs → script/postinstall.mjs} +0 -0
package/src/tool/edit.ts
ADDED
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
// the approaches in this edit tool are sourced from
|
|
2
|
+
// https://github.com/cline/cline/blob/main/evals/diff-edits/diff-apply/diff-06-23-25.ts
|
|
3
|
+
// https://github.com/google-gemini/gemini-cli/blob/main/packages/core/src/utils/editCorrector.ts
|
|
4
|
+
// https://github.com/cline/cline/blob/main/evals/diff-edits/diff-apply/diff-06-26-25.ts
|
|
5
|
+
|
|
6
|
+
import z from "zod"
|
|
7
|
+
import * as path from "path"
|
|
8
|
+
import { Tool } from "./tool"
|
|
9
|
+
import { LSP } from "../lsp"
|
|
10
|
+
import { createTwoFilesPatch, diffLines } from "diff"
|
|
11
|
+
import DESCRIPTION from "./edit.txt"
|
|
12
|
+
import { File } from "../file"
|
|
13
|
+
import { FileWatcher } from "../file/watcher"
|
|
14
|
+
import { Bus } from "../bus"
|
|
15
|
+
import { Format } from "../format"
|
|
16
|
+
import { FileTime } from "../file/time"
|
|
17
|
+
import { Filesystem } from "../util/filesystem"
|
|
18
|
+
import { Instance } from "../project/instance"
|
|
19
|
+
import { Snapshot } from "@/snapshot"
|
|
20
|
+
import { assertExternalDirectory } from "./external-directory"
|
|
21
|
+
import { generateText } from "@/util/ai-sdk"
|
|
22
|
+
import { Provider } from "../provider/provider"
|
|
23
|
+
|
|
24
|
+
const MAX_DIAGNOSTICS_PER_FILE = 20
|
|
25
|
+
|
|
26
|
+
async function analyzeEditFailure(content: string, oldString: string, newString: string): Promise<string | null> {
|
|
27
|
+
try {
|
|
28
|
+
const sideLanguage = await Provider.getLanguage(
|
|
29
|
+
await Provider.getModel("local-side" as any, "nemotron-3-nano-4b" as any),
|
|
30
|
+
)
|
|
31
|
+
if (!sideLanguage) return null
|
|
32
|
+
|
|
33
|
+
const prompt = `You are a pair programmer assisting an AI agent. The agent tried to edit a file using a search-and-replace tool, but its 'oldString' was not found.
|
|
34
|
+
Analyze the actual file contents against what the agent tried to replace, and return a CONCISE, 1-2 sentence explanation of why it failed so the agent can fix it.
|
|
35
|
+
DO NOT return the raw code. ONLY return the explanation (e.g. "The signature changed to include ctx", "The function was deleted").
|
|
36
|
+
|
|
37
|
+
Intended oldString:
|
|
38
|
+
${oldString}
|
|
39
|
+
|
|
40
|
+
Intended newString:
|
|
41
|
+
${newString}
|
|
42
|
+
|
|
43
|
+
Actual File Content:
|
|
44
|
+
${content.substring(0, 8000)}`
|
|
45
|
+
|
|
46
|
+
const response = await generateText({
|
|
47
|
+
model: sideLanguage,
|
|
48
|
+
prompt,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return response.text
|
|
52
|
+
} catch (error) {
|
|
53
|
+
// Silently fallback if the side model is offline or fails
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizeLineEndings(text: string): string {
|
|
59
|
+
return text.replaceAll("\r\n", "\n")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function detectLineEnding(text: string): "\n" | "\r\n" {
|
|
63
|
+
return text.includes("\r\n") ? "\r\n" : "\n"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function convertToLineEnding(text: string, ending: "\n" | "\r\n"): string {
|
|
67
|
+
if (ending === "\n") return text
|
|
68
|
+
return text.replaceAll("\n", "\r\n")
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const EditTool = Tool.define("edit", {
|
|
72
|
+
description: DESCRIPTION,
|
|
73
|
+
parameters: z.object({
|
|
74
|
+
filePath: z.string().describe("The absolute path to modify"),
|
|
75
|
+
oldString: z.string().describe("The text to replace"),
|
|
76
|
+
newString: z.string().describe("The text to replace it with"),
|
|
77
|
+
replaceAll: z.boolean().optional().describe("Replace all occurrences (default false)"),
|
|
78
|
+
}),
|
|
79
|
+
async execute(params, ctx) {
|
|
80
|
+
if (!params.filePath) {
|
|
81
|
+
throw new Error("filePath is required")
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (params.oldString === params.newString) {
|
|
85
|
+
throw new Error("No changes to apply: oldString and newString are identical.")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
|
|
89
|
+
await assertExternalDirectory(ctx, filePath)
|
|
90
|
+
|
|
91
|
+
let diff = ""
|
|
92
|
+
let contentOld = ""
|
|
93
|
+
let contentNew = ""
|
|
94
|
+
await FileTime.withLock(filePath, async () => {
|
|
95
|
+
if (params.oldString === "") {
|
|
96
|
+
const existed = await Filesystem.exists(filePath)
|
|
97
|
+
contentNew = params.newString
|
|
98
|
+
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
|
|
99
|
+
await ctx.ask({
|
|
100
|
+
permission: "edit",
|
|
101
|
+
patterns: [path.relative(Instance.worktree, filePath)],
|
|
102
|
+
always: ["*"],
|
|
103
|
+
metadata: {
|
|
104
|
+
filepath: filePath,
|
|
105
|
+
diff,
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
await Filesystem.write(filePath, params.newString)
|
|
109
|
+
await Format.file(filePath)
|
|
110
|
+
Bus.publish(File.Event.Edited, { file: filePath })
|
|
111
|
+
await Bus.publish(FileWatcher.Event.Updated, {
|
|
112
|
+
file: filePath,
|
|
113
|
+
event: existed ? "change" : "add",
|
|
114
|
+
})
|
|
115
|
+
await FileTime.read(ctx.sessionID, filePath)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const stats = Filesystem.stat(filePath)
|
|
120
|
+
if (!stats) throw new Error(`File ${filePath} not found`)
|
|
121
|
+
if (stats.isDirectory()) throw new Error(`Path is a directory, not a file: ${filePath}`)
|
|
122
|
+
try {
|
|
123
|
+
await FileTime.assert(ctx.sessionID, filePath)
|
|
124
|
+
} catch (error: any) {
|
|
125
|
+
if (error.message.includes("You must read file")) {
|
|
126
|
+
const content = await Filesystem.readText(filePath)
|
|
127
|
+
await FileTime.read(ctx.sessionID, filePath)
|
|
128
|
+
const maxLines = 2000
|
|
129
|
+
const lines = content.split("\n")
|
|
130
|
+
const truncated = lines.length > maxLines
|
|
131
|
+
const displayContent = lines.slice(0, maxLines).join("\n")
|
|
132
|
+
throw new Error(
|
|
133
|
+
`ERROR: Stale Write Protection. You cannot overwrite a file you have not read.\n\n[SYSTEM AUTO-READ]: To save you a turn, the system has automatically loaded the file content for you:\n\n--- CONTENT OF ${filePath} ---\n${displayContent}\n${truncated ? `\n... (truncated to ${maxLines} lines)` : ""}\n--------------------------------\n\nPlease review the contents above and issue your write/replace command again.`,
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
throw error
|
|
137
|
+
}
|
|
138
|
+
contentOld = await Filesystem.readText(filePath)
|
|
139
|
+
|
|
140
|
+
const ending = detectLineEnding(contentOld)
|
|
141
|
+
const old = convertToLineEnding(normalizeLineEndings(params.oldString), ending)
|
|
142
|
+
const next = convertToLineEnding(normalizeLineEndings(params.newString), ending)
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
contentNew = replace(contentOld, old, next, params.replaceAll)
|
|
146
|
+
} catch (error: any) {
|
|
147
|
+
if (error.message.includes("Could not find oldString")) {
|
|
148
|
+
const helperMsg = await analyzeEditFailure(contentOld, old, next)
|
|
149
|
+
if (helperMsg) {
|
|
150
|
+
throw new Error(`${error.message}\n\n[Clerk Pair Programmer Analysis]:\n${helperMsg}`)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
throw error
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
diff = trimDiff(
|
|
157
|
+
createTwoFilesPatch(filePath, filePath, normalizeLineEndings(contentOld), normalizeLineEndings(contentNew)),
|
|
158
|
+
)
|
|
159
|
+
await ctx.ask({
|
|
160
|
+
permission: "edit",
|
|
161
|
+
patterns: [path.relative(Instance.worktree, filePath)],
|
|
162
|
+
always: ["*"],
|
|
163
|
+
metadata: {
|
|
164
|
+
filepath: filePath,
|
|
165
|
+
diff,
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// Create a safety net backup file
|
|
170
|
+
await Filesystem.write(filePath + ".bak", contentOld)
|
|
171
|
+
|
|
172
|
+
await Filesystem.write(filePath, contentNew)
|
|
173
|
+
await Format.file(filePath)
|
|
174
|
+
Bus.publish(File.Event.Edited, { file: filePath })
|
|
175
|
+
await Bus.publish(FileWatcher.Event.Updated, {
|
|
176
|
+
file: filePath,
|
|
177
|
+
event: "change",
|
|
178
|
+
})
|
|
179
|
+
contentNew = await Filesystem.readText(filePath)
|
|
180
|
+
diff = trimDiff(
|
|
181
|
+
createTwoFilesPatch(filePath, filePath, normalizeLineEndings(contentOld), normalizeLineEndings(contentNew)),
|
|
182
|
+
)
|
|
183
|
+
await FileTime.read(ctx.sessionID, filePath)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
const filediff: Snapshot.FileDiff = {
|
|
187
|
+
file: filePath,
|
|
188
|
+
before: contentOld,
|
|
189
|
+
after: contentNew,
|
|
190
|
+
additions: 0,
|
|
191
|
+
deletions: 0,
|
|
192
|
+
}
|
|
193
|
+
for (const change of diffLines(contentOld, contentNew)) {
|
|
194
|
+
if (change.added) filediff.additions += change.count || 0
|
|
195
|
+
if (change.removed) filediff.deletions += change.count || 0
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
ctx.metadata({
|
|
199
|
+
metadata: {
|
|
200
|
+
diff,
|
|
201
|
+
filediff,
|
|
202
|
+
diagnostics: {},
|
|
203
|
+
},
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
let output = "Edit applied successfully."
|
|
207
|
+
await LSP.touchFile(filePath, true)
|
|
208
|
+
const diagnostics = await LSP.diagnostics()
|
|
209
|
+
const normalizedFilePath = Filesystem.normalizePath(filePath)
|
|
210
|
+
const issues = diagnostics[normalizedFilePath] ?? []
|
|
211
|
+
const errors = issues.filter((item) => item.severity === 1)
|
|
212
|
+
if (errors.length > 0) {
|
|
213
|
+
const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
|
|
214
|
+
const suffix =
|
|
215
|
+
errors.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
|
|
216
|
+
output += `\n\nLSP errors detected in this file, please fix:\n<diagnostics file="${filePath}">\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</diagnostics>`
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
metadata: {
|
|
221
|
+
diagnostics,
|
|
222
|
+
diff,
|
|
223
|
+
filediff,
|
|
224
|
+
},
|
|
225
|
+
title: `${path.relative(Instance.worktree, filePath)}`,
|
|
226
|
+
output,
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
export type Replacer = (content: string, find: string) => Generator<string, void, unknown>
|
|
232
|
+
|
|
233
|
+
// Similarity thresholds for block anchor fallback matching
|
|
234
|
+
const SINGLE_CANDIDATE_SIMILARITY_THRESHOLD = 0.0
|
|
235
|
+
const MULTIPLE_CANDIDATES_SIMILARITY_THRESHOLD = 0.3
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Levenshtein distance algorithm implementation
|
|
239
|
+
*/
|
|
240
|
+
function levenshtein(a: string, b: string): number {
|
|
241
|
+
// Handle empty strings
|
|
242
|
+
if (a === "" || b === "") {
|
|
243
|
+
return Math.max(a.length, b.length)
|
|
244
|
+
}
|
|
245
|
+
const matrix = Array.from({ length: a.length + 1 }, (_, i) =>
|
|
246
|
+
Array.from({ length: b.length + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
for (let i = 1; i <= a.length; i++) {
|
|
250
|
+
for (let j = 1; j <= b.length; j++) {
|
|
251
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1
|
|
252
|
+
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return matrix[a.length][b.length]
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export const SimpleReplacer: Replacer = function* (_content, find) {
|
|
259
|
+
yield find
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export const LineTrimmedReplacer: Replacer = function* (content, find) {
|
|
263
|
+
const originalLines = content.split("\n")
|
|
264
|
+
const searchLines = find.split("\n")
|
|
265
|
+
|
|
266
|
+
if (searchLines[searchLines.length - 1] === "") {
|
|
267
|
+
searchLines.pop()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for (let i = 0; i <= originalLines.length - searchLines.length; i++) {
|
|
271
|
+
let matches = true
|
|
272
|
+
|
|
273
|
+
for (let j = 0; j < searchLines.length; j++) {
|
|
274
|
+
const originalTrimmed = originalLines[i + j].trim()
|
|
275
|
+
const searchTrimmed = searchLines[j].trim()
|
|
276
|
+
|
|
277
|
+
if (originalTrimmed !== searchTrimmed) {
|
|
278
|
+
matches = false
|
|
279
|
+
break
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (matches) {
|
|
284
|
+
let matchStartIndex = 0
|
|
285
|
+
for (let k = 0; k < i; k++) {
|
|
286
|
+
matchStartIndex += originalLines[k].length + 1
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
let matchEndIndex = matchStartIndex
|
|
290
|
+
for (let k = 0; k < searchLines.length; k++) {
|
|
291
|
+
matchEndIndex += originalLines[i + k].length
|
|
292
|
+
if (k < searchLines.length - 1) {
|
|
293
|
+
matchEndIndex += 1 // Add newline character except for the last line
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
yield content.substring(matchStartIndex, matchEndIndex)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export const BlockAnchorReplacer: Replacer = function* (content, find) {
|
|
303
|
+
const originalLines = content.split("\n")
|
|
304
|
+
const searchLines = find.split("\n")
|
|
305
|
+
|
|
306
|
+
if (searchLines.length < 3) {
|
|
307
|
+
return
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (searchLines[searchLines.length - 1] === "") {
|
|
311
|
+
searchLines.pop()
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const firstLineSearch = searchLines[0].trim()
|
|
315
|
+
const lastLineSearch = searchLines[searchLines.length - 1].trim()
|
|
316
|
+
const searchBlockSize = searchLines.length
|
|
317
|
+
|
|
318
|
+
// Collect all candidate positions where both anchors match
|
|
319
|
+
const candidates: Array<{ startLine: number; endLine: number }> = []
|
|
320
|
+
for (let i = 0; i < originalLines.length; i++) {
|
|
321
|
+
if (originalLines[i].trim() !== firstLineSearch) {
|
|
322
|
+
continue
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Look for the matching last line after this first line
|
|
326
|
+
for (let j = i + 2; j < originalLines.length; j++) {
|
|
327
|
+
if (originalLines[j].trim() === lastLineSearch) {
|
|
328
|
+
candidates.push({ startLine: i, endLine: j })
|
|
329
|
+
break // Only match the first occurrence of the last line
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Return immediately if no candidates
|
|
335
|
+
if (candidates.length === 0) {
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Handle single candidate scenario (using relaxed threshold)
|
|
340
|
+
if (candidates.length === 1) {
|
|
341
|
+
const { startLine, endLine } = candidates[0]
|
|
342
|
+
const actualBlockSize = endLine - startLine + 1
|
|
343
|
+
|
|
344
|
+
let similarity = 0
|
|
345
|
+
let linesToCheck = Math.min(searchBlockSize - 2, actualBlockSize - 2) // Middle lines only
|
|
346
|
+
|
|
347
|
+
if (linesToCheck > 0) {
|
|
348
|
+
for (let j = 1; j < searchBlockSize - 1 && j < actualBlockSize - 1; j++) {
|
|
349
|
+
const originalLine = originalLines[startLine + j].trim()
|
|
350
|
+
const searchLine = searchLines[j].trim()
|
|
351
|
+
const maxLen = Math.max(originalLine.length, searchLine.length)
|
|
352
|
+
if (maxLen === 0) {
|
|
353
|
+
continue
|
|
354
|
+
}
|
|
355
|
+
const distance = levenshtein(originalLine, searchLine)
|
|
356
|
+
similarity += (1 - distance / maxLen) / linesToCheck
|
|
357
|
+
|
|
358
|
+
// Exit early when threshold is reached
|
|
359
|
+
if (similarity >= SINGLE_CANDIDATE_SIMILARITY_THRESHOLD) {
|
|
360
|
+
break
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
// No middle lines to compare, just accept based on anchors
|
|
365
|
+
similarity = 1.0
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (similarity >= SINGLE_CANDIDATE_SIMILARITY_THRESHOLD) {
|
|
369
|
+
let matchStartIndex = 0
|
|
370
|
+
for (let k = 0; k < startLine; k++) {
|
|
371
|
+
matchStartIndex += originalLines[k].length + 1
|
|
372
|
+
}
|
|
373
|
+
let matchEndIndex = matchStartIndex
|
|
374
|
+
for (let k = startLine; k <= endLine; k++) {
|
|
375
|
+
matchEndIndex += originalLines[k].length
|
|
376
|
+
if (k < endLine) {
|
|
377
|
+
matchEndIndex += 1 // Add newline character except for the last line
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
yield content.substring(matchStartIndex, matchEndIndex)
|
|
381
|
+
}
|
|
382
|
+
return
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Calculate similarity for multiple candidates
|
|
386
|
+
let bestMatch: { startLine: number; endLine: number } | null = null
|
|
387
|
+
let maxSimilarity = -1
|
|
388
|
+
|
|
389
|
+
for (const candidate of candidates) {
|
|
390
|
+
const { startLine, endLine } = candidate
|
|
391
|
+
const actualBlockSize = endLine - startLine + 1
|
|
392
|
+
|
|
393
|
+
let similarity = 0
|
|
394
|
+
let linesToCheck = Math.min(searchBlockSize - 2, actualBlockSize - 2) // Middle lines only
|
|
395
|
+
|
|
396
|
+
if (linesToCheck > 0) {
|
|
397
|
+
for (let j = 1; j < searchBlockSize - 1 && j < actualBlockSize - 1; j++) {
|
|
398
|
+
const originalLine = originalLines[startLine + j].trim()
|
|
399
|
+
const searchLine = searchLines[j].trim()
|
|
400
|
+
const maxLen = Math.max(originalLine.length, searchLine.length)
|
|
401
|
+
if (maxLen === 0) {
|
|
402
|
+
continue
|
|
403
|
+
}
|
|
404
|
+
const distance = levenshtein(originalLine, searchLine)
|
|
405
|
+
similarity += 1 - distance / maxLen
|
|
406
|
+
}
|
|
407
|
+
similarity /= linesToCheck // Average similarity
|
|
408
|
+
} else {
|
|
409
|
+
// No middle lines to compare, just accept based on anchors
|
|
410
|
+
similarity = 1.0
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (similarity > maxSimilarity) {
|
|
414
|
+
maxSimilarity = similarity
|
|
415
|
+
bestMatch = candidate
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Threshold judgment
|
|
420
|
+
if (maxSimilarity >= MULTIPLE_CANDIDATES_SIMILARITY_THRESHOLD && bestMatch) {
|
|
421
|
+
const { startLine, endLine } = bestMatch
|
|
422
|
+
let matchStartIndex = 0
|
|
423
|
+
for (let k = 0; k < startLine; k++) {
|
|
424
|
+
matchStartIndex += originalLines[k].length + 1
|
|
425
|
+
}
|
|
426
|
+
let matchEndIndex = matchStartIndex
|
|
427
|
+
for (let k = startLine; k <= endLine; k++) {
|
|
428
|
+
matchEndIndex += originalLines[k].length
|
|
429
|
+
if (k < endLine) {
|
|
430
|
+
matchEndIndex += 1
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
yield content.substring(matchStartIndex, matchEndIndex)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export const WhitespaceNormalizedReplacer: Replacer = function* (content, find) {
|
|
438
|
+
const normalizeWhitespace = (text: string) => text.replace(/\s+/g, " ").trim()
|
|
439
|
+
const normalizedFind = normalizeWhitespace(find)
|
|
440
|
+
|
|
441
|
+
// Handle single line matches
|
|
442
|
+
const lines = content.split("\n")
|
|
443
|
+
for (let i = 0; i < lines.length; i++) {
|
|
444
|
+
const line = lines[i]
|
|
445
|
+
if (normalizeWhitespace(line) === normalizedFind) {
|
|
446
|
+
yield line
|
|
447
|
+
} else {
|
|
448
|
+
// Only check for substring matches if the full line doesn't match
|
|
449
|
+
const normalizedLine = normalizeWhitespace(line)
|
|
450
|
+
if (normalizedLine.includes(normalizedFind)) {
|
|
451
|
+
// Find the actual substring in the original line that matches
|
|
452
|
+
const words = find.trim().split(/\s+/)
|
|
453
|
+
if (words.length > 0) {
|
|
454
|
+
const pattern = words.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s+")
|
|
455
|
+
try {
|
|
456
|
+
const regex = new RegExp(pattern)
|
|
457
|
+
const match = line.match(regex)
|
|
458
|
+
if (match) {
|
|
459
|
+
yield match[0]
|
|
460
|
+
}
|
|
461
|
+
} catch (e) {
|
|
462
|
+
// Invalid regex pattern, skip
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Handle multi-line matches
|
|
470
|
+
const findLines = find.split("\n")
|
|
471
|
+
if (findLines.length > 1) {
|
|
472
|
+
for (let i = 0; i <= lines.length - findLines.length; i++) {
|
|
473
|
+
const block = lines.slice(i, i + findLines.length)
|
|
474
|
+
if (normalizeWhitespace(block.join("\n")) === normalizedFind) {
|
|
475
|
+
yield block.join("\n")
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export const IndentationFlexibleReplacer: Replacer = function* (content, find) {
|
|
482
|
+
const removeIndentation = (text: string) => {
|
|
483
|
+
const lines = text.split("\n")
|
|
484
|
+
const nonEmptyLines = lines.filter((line) => line.trim().length > 0)
|
|
485
|
+
if (nonEmptyLines.length === 0) return text
|
|
486
|
+
|
|
487
|
+
const minIndent = Math.min(
|
|
488
|
+
...nonEmptyLines.map((line) => {
|
|
489
|
+
const match = line.match(/^(\s*)/)
|
|
490
|
+
return match ? match[1].length : 0
|
|
491
|
+
}),
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
return lines.map((line) => (line.trim().length === 0 ? line : line.slice(minIndent))).join("\n")
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const normalizedFind = removeIndentation(find)
|
|
498
|
+
const contentLines = content.split("\n")
|
|
499
|
+
const findLines = find.split("\n")
|
|
500
|
+
|
|
501
|
+
for (let i = 0; i <= contentLines.length - findLines.length; i++) {
|
|
502
|
+
const block = contentLines.slice(i, i + findLines.length).join("\n")
|
|
503
|
+
if (removeIndentation(block) === normalizedFind) {
|
|
504
|
+
yield block
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export const EscapeNormalizedReplacer: Replacer = function* (content, find) {
|
|
510
|
+
const unescapeString = (str: string): string => {
|
|
511
|
+
return str.replace(/\\(n|t|r|'|"|`|\\|\n|\$)/g, (match, capturedChar) => {
|
|
512
|
+
switch (capturedChar) {
|
|
513
|
+
case "n":
|
|
514
|
+
return "\n"
|
|
515
|
+
case "t":
|
|
516
|
+
return "\t"
|
|
517
|
+
case "r":
|
|
518
|
+
return "\r"
|
|
519
|
+
case "'":
|
|
520
|
+
return "'"
|
|
521
|
+
case '"':
|
|
522
|
+
return '"'
|
|
523
|
+
case "`":
|
|
524
|
+
return "`"
|
|
525
|
+
case "\\":
|
|
526
|
+
return "\\"
|
|
527
|
+
case "\n":
|
|
528
|
+
return "\n"
|
|
529
|
+
case "$":
|
|
530
|
+
return "$"
|
|
531
|
+
default:
|
|
532
|
+
return match
|
|
533
|
+
}
|
|
534
|
+
})
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const unescapedFind = unescapeString(find)
|
|
538
|
+
|
|
539
|
+
// Try direct match with unescaped find string
|
|
540
|
+
if (content.includes(unescapedFind)) {
|
|
541
|
+
yield unescapedFind
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Also try finding escaped versions in content that match unescaped find
|
|
545
|
+
const lines = content.split("\n")
|
|
546
|
+
const findLines = unescapedFind.split("\n")
|
|
547
|
+
|
|
548
|
+
for (let i = 0; i <= lines.length - findLines.length; i++) {
|
|
549
|
+
const block = lines.slice(i, i + findLines.length).join("\n")
|
|
550
|
+
const unescapedBlock = unescapeString(block)
|
|
551
|
+
|
|
552
|
+
if (unescapedBlock === unescapedFind) {
|
|
553
|
+
yield block
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export const MultiOccurrenceReplacer: Replacer = function* (content, find) {
|
|
559
|
+
// This replacer yields all exact matches, allowing the replace function
|
|
560
|
+
// to handle multiple occurrences based on replaceAll parameter
|
|
561
|
+
let startIndex = 0
|
|
562
|
+
|
|
563
|
+
while (true) {
|
|
564
|
+
const index = content.indexOf(find, startIndex)
|
|
565
|
+
if (index === -1) break
|
|
566
|
+
|
|
567
|
+
yield find
|
|
568
|
+
startIndex = index + find.length
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export const TrimmedBoundaryReplacer: Replacer = function* (content, find) {
|
|
573
|
+
const trimmedFind = find.trim()
|
|
574
|
+
|
|
575
|
+
if (trimmedFind === find) {
|
|
576
|
+
// Already trimmed, no point in trying
|
|
577
|
+
return
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Try to find the trimmed version
|
|
581
|
+
if (content.includes(trimmedFind)) {
|
|
582
|
+
yield trimmedFind
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Also try finding blocks where trimmed content matches
|
|
586
|
+
const lines = content.split("\n")
|
|
587
|
+
const findLines = find.split("\n")
|
|
588
|
+
|
|
589
|
+
for (let i = 0; i <= lines.length - findLines.length; i++) {
|
|
590
|
+
const block = lines.slice(i, i + findLines.length).join("\n")
|
|
591
|
+
|
|
592
|
+
if (block.trim() === trimmedFind) {
|
|
593
|
+
yield block
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
export const ContextAwareReplacer: Replacer = function* (content, find) {
|
|
599
|
+
const findLines = find.split("\n")
|
|
600
|
+
if (findLines.length < 3) {
|
|
601
|
+
// Need at least 3 lines to have meaningful context
|
|
602
|
+
return
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Remove trailing empty line if present
|
|
606
|
+
if (findLines[findLines.length - 1] === "") {
|
|
607
|
+
findLines.pop()
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const contentLines = content.split("\n")
|
|
611
|
+
|
|
612
|
+
// Extract first and last lines as context anchors
|
|
613
|
+
const firstLine = findLines[0].trim()
|
|
614
|
+
const lastLine = findLines[findLines.length - 1].trim()
|
|
615
|
+
|
|
616
|
+
// Find blocks that start and end with the context anchors
|
|
617
|
+
for (let i = 0; i < contentLines.length; i++) {
|
|
618
|
+
if (contentLines[i].trim() !== firstLine) continue
|
|
619
|
+
|
|
620
|
+
// Look for the matching last line
|
|
621
|
+
for (let j = i + 2; j < contentLines.length; j++) {
|
|
622
|
+
if (contentLines[j].trim() === lastLine) {
|
|
623
|
+
// Found a potential context block
|
|
624
|
+
const blockLines = contentLines.slice(i, j + 1)
|
|
625
|
+
const block = blockLines.join("\n")
|
|
626
|
+
|
|
627
|
+
// Check if the middle content has reasonable similarity
|
|
628
|
+
// (simple heuristic: at least 50% of non-empty lines should match when trimmed)
|
|
629
|
+
if (blockLines.length === findLines.length) {
|
|
630
|
+
let matchingLines = 0
|
|
631
|
+
let totalNonEmptyLines = 0
|
|
632
|
+
|
|
633
|
+
for (let k = 1; k < blockLines.length - 1; k++) {
|
|
634
|
+
const blockLine = blockLines[k].trim()
|
|
635
|
+
const findLine = findLines[k].trim()
|
|
636
|
+
|
|
637
|
+
if (blockLine.length > 0 || findLine.length > 0) {
|
|
638
|
+
totalNonEmptyLines++
|
|
639
|
+
if (blockLine === findLine) {
|
|
640
|
+
matchingLines++
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (totalNonEmptyLines === 0 || matchingLines / totalNonEmptyLines >= 0.5) {
|
|
646
|
+
yield block
|
|
647
|
+
break // Only match the first occurrence
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
break
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
export function trimDiff(diff: string): string {
|
|
657
|
+
const lines = diff.split("\n")
|
|
658
|
+
const contentLines = lines.filter(
|
|
659
|
+
(line) =>
|
|
660
|
+
(line.startsWith("+") || line.startsWith("-") || line.startsWith(" ")) &&
|
|
661
|
+
!line.startsWith("---") &&
|
|
662
|
+
!line.startsWith("+++"),
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
if (contentLines.length === 0) return diff
|
|
666
|
+
|
|
667
|
+
let min = Infinity
|
|
668
|
+
for (const line of contentLines) {
|
|
669
|
+
const content = line.slice(1)
|
|
670
|
+
if (content.trim().length > 0) {
|
|
671
|
+
const match = content.match(/^(\s*)/)
|
|
672
|
+
if (match) min = Math.min(min, match[1].length)
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (min === Infinity || min === 0) return diff
|
|
676
|
+
const trimmedLines = lines.map((line) => {
|
|
677
|
+
if (
|
|
678
|
+
(line.startsWith("+") || line.startsWith("-") || line.startsWith(" ")) &&
|
|
679
|
+
!line.startsWith("---") &&
|
|
680
|
+
!line.startsWith("+++")
|
|
681
|
+
) {
|
|
682
|
+
const prefix = line[0]
|
|
683
|
+
const content = line.slice(1)
|
|
684
|
+
return prefix + content.slice(min)
|
|
685
|
+
}
|
|
686
|
+
return line
|
|
687
|
+
})
|
|
688
|
+
|
|
689
|
+
return trimmedLines.join("\n")
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
export function replace(content: string, oldString: string, newString: string, replaceAll = false): string {
|
|
693
|
+
if (oldString === newString) {
|
|
694
|
+
throw new Error("No changes to apply: oldString and newString are identical.")
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
let notFound = true
|
|
698
|
+
|
|
699
|
+
for (const replacer of [
|
|
700
|
+
SimpleReplacer,
|
|
701
|
+
LineTrimmedReplacer,
|
|
702
|
+
BlockAnchorReplacer,
|
|
703
|
+
WhitespaceNormalizedReplacer,
|
|
704
|
+
IndentationFlexibleReplacer,
|
|
705
|
+
EscapeNormalizedReplacer,
|
|
706
|
+
TrimmedBoundaryReplacer,
|
|
707
|
+
ContextAwareReplacer,
|
|
708
|
+
MultiOccurrenceReplacer,
|
|
709
|
+
]) {
|
|
710
|
+
for (const search of replacer(content, oldString)) {
|
|
711
|
+
const index = content.indexOf(search)
|
|
712
|
+
if (index === -1) continue
|
|
713
|
+
notFound = false
|
|
714
|
+
if (replaceAll) {
|
|
715
|
+
return content.replaceAll(search, newString)
|
|
716
|
+
}
|
|
717
|
+
const lastIndex = content.lastIndexOf(search)
|
|
718
|
+
if (index !== lastIndex) continue
|
|
719
|
+
return content.substring(0, index) + newString + content.substring(index + search.length)
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (notFound) {
|
|
724
|
+
const preview = content.split("\\n").slice(0, 100).join("\\n")
|
|
725
|
+
const previewMessage =
|
|
726
|
+
content.split("\\n").length > 100
|
|
727
|
+
? `\\n\\nFirst 100 lines of current file:\\n${preview}\\n...`
|
|
728
|
+
: `\\n\\nCurrent file contents:\\n${preview}`
|
|
729
|
+
throw new Error(
|
|
730
|
+
`Could not find oldString in the file. It must match exactly, including whitespace, indentation, and line endings.${previewMessage}`,
|
|
731
|
+
)
|
|
732
|
+
}
|
|
733
|
+
throw new Error("Found multiple matches for oldString. Provide more surrounding context to make the match unique.")
|
|
734
|
+
}
|