mixdog 0.7.1
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/.claude-plugin/marketplace.json +31 -0
- package/.claude-plugin/plugin.json +20 -0
- package/.gitattributes +34 -0
- package/.mcp.json +14 -0
- package/ARCHITECTURE.md +77 -0
- package/CHANGELOG.md +7 -0
- package/CONTRIBUTING.md +45 -0
- package/DATA-FLOW.md +79 -0
- package/LICENSE +21 -0
- package/README.md +389 -0
- package/SECURITY.md +138 -0
- package/UNINSTALL.md +112 -0
- package/agents/maintenance.md +5 -0
- package/agents/memory-classification.md +30 -0
- package/agents/scheduler-task.md +18 -0
- package/agents/webhook-handler.md +27 -0
- package/agents/worker.md +24 -0
- package/bin/bridge +133 -0
- package/bin/statusline-launcher.mjs +78 -0
- package/bin/statusline-lib.mjs +550 -0
- package/bin/statusline.mjs +607 -0
- package/bun.lock +802 -0
- package/commands/config.md +16 -0
- package/commands/doctor.md +13 -0
- package/commands/setup.md +17 -0
- package/defaults/cycle3-review-prompt.md +90 -0
- package/defaults/hidden-roles.json +65 -0
- package/defaults/memory-chunk-prompt.md +63 -0
- package/defaults/memory-promote-prompt.md +135 -0
- package/defaults/mixdog-config.template.json +27 -0
- package/defaults/user-workflow.json +8 -0
- package/defaults/user-workflow.md +12 -0
- package/hooks/hooks.json +73 -0
- package/hooks/lib/active-instance.cjs +77 -0
- package/hooks/lib/permission-evaluator.cjs +411 -0
- package/hooks/lib/permission-route.cjs +63 -0
- package/hooks/lib/permission-rules.cjs +170 -0
- package/hooks/lib/settings-loader.cjs +116 -0
- package/hooks/post-tool-use.cjs +84 -0
- package/hooks/pre-mcp-sandbox.cjs +158 -0
- package/hooks/pre-tool-subagent.cjs +253 -0
- package/hooks/session-start.cjs +1372 -0
- package/hooks/turn-timer.cjs +82 -0
- package/lib/claude-md-writer.cjs +386 -0
- package/lib/config-cjs.cjs +61 -0
- package/lib/hook-pipe-path.cjs +10 -0
- package/lib/keychain-cjs.cjs +263 -0
- package/lib/plugin-paths.cjs +61 -0
- package/lib/rules-builder.cjs +241 -0
- package/lib/text-utils.cjs +61 -0
- package/native/README.md +117 -0
- package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
- package/package.json +107 -0
- package/prompts/code-review.txt +16 -0
- package/prompts/security-audit.txt +17 -0
- package/rules/bridge/00-common.md +39 -0
- package/rules/bridge/20-skip-protocol.md +18 -0
- package/rules/bridge/30-explorer.md +33 -0
- package/rules/bridge/40-cycle1-agent.md +52 -0
- package/rules/bridge/41-cycle2-agent.md +62 -0
- package/rules/bridge/42-cycle3-agent.md +44 -0
- package/rules/lead/00-tool-lead.md +61 -0
- package/rules/lead/01-general.md +23 -0
- package/rules/lead/02-channels.md +49 -0
- package/rules/lead/03-team.md +27 -0
- package/rules/lead/04-workflow.md +20 -0
- package/rules/shared/00-language.md +14 -0
- package/rules/shared/01-tool.md +138 -0
- package/scripts/bootstrap.mjs +184 -0
- package/scripts/bridge-unify-smoke.mjs +308 -0
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/builtin-utils-smoke.mjs +392 -0
- package/scripts/check-json.mjs +45 -0
- package/scripts/check-syntax-changed.mjs +102 -0
- package/scripts/check-syntax.mjs +58 -0
- package/scripts/code-graph-batch.test.mjs +33 -0
- package/scripts/config-preserve-smoke.mjs +180 -0
- package/scripts/doctor.mjs +484 -0
- package/scripts/edit-normalize-fuzz.mjs +130 -0
- package/scripts/edit-normalize-smoke.mjs +401 -0
- package/scripts/edit-operation-smoke.mjs +369 -0
- package/scripts/edit2-smoke.mjs +63 -0
- package/scripts/fuzzy-e2e.mjs +28 -0
- package/scripts/fuzzy-smoke.mjs +26 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/guard-smoke.mjs +66 -0
- package/scripts/hidden-role-schema-smoke.mjs +162 -0
- package/scripts/hook-routing-smoke.mjs +29 -0
- package/scripts/inject-input.ps1 +204 -0
- package/scripts/io-complex-smoke.mjs +667 -0
- package/scripts/io-explore-bench.mjs +424 -0
- package/scripts/io-guardrails-smoke.mjs +205 -0
- package/scripts/io-mini-bench-baseline.json +11 -0
- package/scripts/io-mini-bench.mjs +216 -0
- package/scripts/io-route-harness.mjs +933 -0
- package/scripts/io-telemetry-report.mjs +691 -0
- package/scripts/mutation-bench.mjs +564 -0
- package/scripts/mutation-io-smoke.mjs +1081 -0
- package/scripts/native-patch-bridge-smoke.mjs +288 -0
- package/scripts/native-patch-smoke.mjs +304 -0
- package/scripts/patch-interior-context-smoke.mjs +49 -0
- package/scripts/patch-newline-utf8-smoke.mjs +157 -0
- package/scripts/perf-hook-smoke.mjs +71 -0
- package/scripts/permission-eval-smoke.mjs +426 -0
- package/scripts/prep-patch.mjs +53 -0
- package/scripts/prep-shim.mjs +96 -0
- package/scripts/provider-cache-smoke.mjs +687 -0
- package/scripts/report-runtime-health.mjs +132 -0
- package/scripts/run-mcp.mjs +1547 -0
- package/scripts/salvage-v4a-shatter.test.mjs +58 -0
- package/scripts/scoped-cache-io-smoke.mjs +103 -0
- package/scripts/shell-policy-round3-smoke.mjs +46 -0
- package/scripts/smoke-runtime-negative.ps1 +100 -0
- package/scripts/smoke-runtime-negative.sh +95 -0
- package/scripts/stall-policy-smoke.mjs +50 -0
- package/scripts/start-memory-worker.mjs +23 -0
- package/scripts/statusline-launcher-smoke.mjs +82 -0
- package/scripts/stress-atomic-write.mjs +1028 -0
- package/scripts/test-config-rmw-restore.mjs +122 -0
- package/scripts/test-fault-inject.mjs +164 -0
- package/scripts/test-large-file.mjs +174 -0
- package/scripts/tool-edge-smoke.mjs +209 -0
- package/scripts/uninstall.mjs +201 -0
- package/scripts/webhook-selfheal-smoke.mjs +29 -0
- package/scripts/write-overwrite-guard-smoke.mjs +56 -0
- package/server-main.mjs +3055 -0
- package/server.mjs +468 -0
- package/setup/config-merge.mjs +254 -0
- package/setup/install.mjs +120 -0
- package/setup/launch-core.mjs +507 -0
- package/setup/launch.mjs +101 -0
- package/setup/setup-server.mjs +3206 -0
- package/setup/setup.html +3693 -0
- package/skills/retro-skill-proposer/SKILL.md +92 -0
- package/skills/schedule-add/SKILL.md +77 -0
- package/skills/setup/SKILL.md +346 -0
- package/skills/webhook-add/SKILL.md +81 -0
- package/src/agent/bridge-stall-watchdog.mjs +337 -0
- package/src/agent/index.mjs +2138 -0
- package/src/agent/orchestrator/activity-bus.mjs +38 -0
- package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +1010 -0
- package/src/agent/orchestrator/bridge-retry.mjs +220 -0
- package/src/agent/orchestrator/bridge-trace.mjs +583 -0
- package/src/agent/orchestrator/cache-mtime.mjs +58 -0
- package/src/agent/orchestrator/config.mjs +358 -0
- package/src/agent/orchestrator/context/collect.mjs +651 -0
- package/src/agent/orchestrator/dispatch-persist.mjs +549 -0
- package/src/agent/orchestrator/drain-registry.mjs +50 -0
- package/src/agent/orchestrator/explore-validator.mjs +8 -0
- package/src/agent/orchestrator/internal-roles.mjs +118 -0
- package/src/agent/orchestrator/internal-tools.mjs +88 -0
- package/src/agent/orchestrator/jobs.mjs +116 -0
- package/src/agent/orchestrator/mcp/client.mjs +364 -0
- package/src/agent/orchestrator/providers/anthropic-betas.mjs +21 -0
- package/src/agent/orchestrator/providers/anthropic-oauth.mjs +1745 -0
- package/src/agent/orchestrator/providers/anthropic.mjs +437 -0
- package/src/agent/orchestrator/providers/gemini.mjs +1175 -0
- package/src/agent/orchestrator/providers/grok-oauth.mjs +782 -0
- package/src/agent/orchestrator/providers/model-catalog.mjs +241 -0
- package/src/agent/orchestrator/providers/openai-compat.mjs +1467 -0
- package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +1890 -0
- package/src/agent/orchestrator/providers/openai-oauth.mjs +1307 -0
- package/src/agent/orchestrator/providers/openai-ws.mjs +104 -0
- package/src/agent/orchestrator/providers/registry.mjs +192 -0
- package/src/agent/orchestrator/providers/retry-classifier.mjs +325 -0
- package/src/agent/orchestrator/session/abort-lookup.mjs +13 -0
- package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
- package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
- package/src/agent/orchestrator/session/cache/read-cache.mjs +319 -0
- package/src/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +11 -0
- package/src/agent/orchestrator/session/cache/scoped-cache.mjs +361 -0
- package/src/agent/orchestrator/session/cache/util.mjs +49 -0
- package/src/agent/orchestrator/session/loop.mjs +1478 -0
- package/src/agent/orchestrator/session/manager.mjs +1975 -0
- package/src/agent/orchestrator/session/read-dedup.mjs +6 -0
- package/src/agent/orchestrator/session/result-classification.mjs +65 -0
- package/src/agent/orchestrator/session/save-session-worker.mjs +18 -0
- package/src/agent/orchestrator/session/store.mjs +624 -0
- package/src/agent/orchestrator/session/stream-watchdog.mjs +130 -0
- package/src/agent/orchestrator/session/tool-result-offload.mjs +166 -0
- package/src/agent/orchestrator/session/trim.mjs +491 -0
- package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +115 -0
- package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +327 -0
- package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +150 -0
- package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +228 -0
- package/src/agent/orchestrator/smart-bridge/index.mjs +215 -0
- package/src/agent/orchestrator/smart-bridge/profiles.mjs +37 -0
- package/src/agent/orchestrator/smart-bridge/registry.mjs +348 -0
- package/src/agent/orchestrator/smart-bridge/session-builder.mjs +116 -0
- package/src/agent/orchestrator/stall-policy.mjs +195 -0
- package/src/agent/orchestrator/tool-loop-guard.mjs +75 -0
- package/src/agent/orchestrator/tools/bash-policy-scan.mjs +77 -0
- package/src/agent/orchestrator/tools/bash-session.mjs +721 -0
- package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
- package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +455 -0
- package/src/agent/orchestrator/tools/builtin/atomic-write.mjs +236 -0
- package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +480 -0
- package/src/agent/orchestrator/tools/builtin/binary-file.mjs +76 -0
- package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +256 -0
- package/src/agent/orchestrator/tools/builtin/cache-layers.mjs +386 -0
- package/src/agent/orchestrator/tools/builtin/cwd-utils.mjs +37 -0
- package/src/agent/orchestrator/tools/builtin/device-paths.mjs +154 -0
- package/src/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +292 -0
- package/src/agent/orchestrator/tools/builtin/diff-utils.mjs +109 -0
- package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +58 -0
- package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +240 -0
- package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +113 -0
- package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +74 -0
- package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +242 -0
- package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +211 -0
- package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +1364 -0
- package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +126 -0
- package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +141 -0
- package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +194 -0
- package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +60 -0
- package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +168 -0
- package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +173 -0
- package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +48 -0
- package/src/agent/orchestrator/tools/builtin/fs-reachability.mjs +48 -0
- package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +99 -0
- package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +170 -0
- package/src/agent/orchestrator/tools/builtin/grep-formatting.mjs +113 -0
- package/src/agent/orchestrator/tools/builtin/hash-utils.mjs +6 -0
- package/src/agent/orchestrator/tools/builtin/list-formatting.mjs +7 -0
- package/src/agent/orchestrator/tools/builtin/list-tool.mjs +593 -0
- package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +89 -0
- package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +300 -0
- package/src/agent/orchestrator/tools/builtin/open-config-tool.mjs +26 -0
- package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +152 -0
- package/src/agent/orchestrator/tools/builtin/path-locks.mjs +35 -0
- package/src/agent/orchestrator/tools/builtin/path-utils.mjs +201 -0
- package/src/agent/orchestrator/tools/builtin/read-args.mjs +103 -0
- package/src/agent/orchestrator/tools/builtin/read-batch.mjs +172 -0
- package/src/agent/orchestrator/tools/builtin/read-constants.mjs +40 -0
- package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
- package/src/agent/orchestrator/tools/builtin/read-image-resize.mjs +189 -0
- package/src/agent/orchestrator/tools/builtin/read-image.mjs +88 -0
- package/src/agent/orchestrator/tools/builtin/read-lines.mjs +12 -0
- package/src/agent/orchestrator/tools/builtin/read-mode-tool.mjs +455 -0
- package/src/agent/orchestrator/tools/builtin/read-open.mjs +190 -0
- package/src/agent/orchestrator/tools/builtin/read-range-index.mjs +271 -0
- package/src/agent/orchestrator/tools/builtin/read-ranges.mjs +26 -0
- package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +728 -0
- package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +173 -0
- package/src/agent/orchestrator/tools/builtin/read-special-files.mjs +268 -0
- package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
- package/src/agent/orchestrator/tools/builtin/read-tool.mjs +530 -0
- package/src/agent/orchestrator/tools/builtin/read-windows.mjs +107 -0
- package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +196 -0
- package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +422 -0
- package/src/agent/orchestrator/tools/builtin/search-builders.mjs +158 -0
- package/src/agent/orchestrator/tools/builtin/search-tool.mjs +869 -0
- package/src/agent/orchestrator/tools/builtin/shell-analysis.mjs +653 -0
- package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +936 -0
- package/src/agent/orchestrator/tools/builtin/shell-output.mjs +36 -0
- package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +214 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +143 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +206 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-validation.mjs +98 -0
- package/src/agent/orchestrator/tools/builtin/text-stats.mjs +69 -0
- package/src/agent/orchestrator/tools/builtin/windows-roots.mjs +23 -0
- package/src/agent/orchestrator/tools/builtin/write-tool.mjs +401 -0
- package/src/agent/orchestrator/tools/builtin.mjs +500 -0
- package/src/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +39 -0
- package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
- package/src/agent/orchestrator/tools/code-graph.mjs +4095 -0
- package/src/agent/orchestrator/tools/cwd-tool.mjs +298 -0
- package/src/agent/orchestrator/tools/destructive-warning.mjs +323 -0
- package/src/agent/orchestrator/tools/edit-normalize.mjs +603 -0
- package/src/agent/orchestrator/tools/env-scrub.mjs +100 -0
- package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +144 -0
- package/src/agent/orchestrator/tools/graph-manifest.json +26 -0
- package/src/agent/orchestrator/tools/host-input.mjs +204 -0
- package/src/agent/orchestrator/tools/mutation-content-cache.mjs +67 -0
- package/src/agent/orchestrator/tools/mutation-planner.mjs +75 -0
- package/src/agent/orchestrator/tools/next-call-utils.mjs +48 -0
- package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +133 -0
- package/src/agent/orchestrator/tools/patch-manifest.json +26 -0
- package/src/agent/orchestrator/tools/patch-tool-defs.mjs +20 -0
- package/src/agent/orchestrator/tools/patch.mjs +2754 -0
- package/src/agent/orchestrator/tools/progress-message.mjs +118 -0
- package/src/agent/orchestrator/tools/result-compression.mjs +279 -0
- package/src/agent/orchestrator/tools/shell-command.mjs +865 -0
- package/src/agent/orchestrator/tools/shell-exec-policy.mjs +89 -0
- package/src/agent/orchestrator/tools/shell-policy-danger-target.mjs +27 -0
- package/src/agent/orchestrator/tools/shell-policy-imports.mjs +7 -0
- package/src/agent/orchestrator/tools/shell-policy.mjs +345 -0
- package/src/agent/orchestrator/tools/shell-snapshot.mjs +313 -0
- package/src/agent/orchestrator/workflow-store.mjs +93 -0
- package/src/agent/tool-defs.mjs +103 -0
- package/src/channels/backends/discord.mjs +784 -0
- package/src/channels/data/voice-runtime-manifest.json +138 -0
- package/src/channels/index.mjs +3229 -0
- package/src/channels/lib/cli-worker-host.mjs +12 -0
- package/src/channels/lib/config-lock.mjs +13 -0
- package/src/channels/lib/config.mjs +292 -0
- package/src/channels/lib/drop-trace.mjs +71 -0
- package/src/channels/lib/event-pipeline.mjs +81 -0
- package/src/channels/lib/event-queue.mjs +345 -0
- package/src/channels/lib/executor.mjs +168 -0
- package/src/channels/lib/format.mjs +188 -0
- package/src/channels/lib/holidays.mjs +138 -0
- package/src/channels/lib/hook-pipe-server.mjs +802 -0
- package/src/channels/lib/interaction-workflows.mjs +184 -0
- package/src/channels/lib/memory-client.mjs +149 -0
- package/src/channels/lib/output-forwarder.mjs +765 -0
- package/src/channels/lib/runtime-paths.mjs +479 -0
- package/src/channels/lib/scheduler.mjs +723 -0
- package/src/channels/lib/session-control.mjs +36 -0
- package/src/channels/lib/session-discovery.mjs +103 -0
- package/src/channels/lib/settings.mjs +11 -0
- package/src/channels/lib/state-file.mjs +68 -0
- package/src/channels/lib/status-snapshot.mjs +219 -0
- package/src/channels/lib/tool-format.mjs +140 -0
- package/src/channels/lib/transcript-discovery.mjs +195 -0
- package/src/channels/lib/voice-runtime-fetcher.mjs +734 -0
- package/src/channels/lib/webhook.mjs +1179 -0
- package/src/channels/lib/whisper-server.mjs +477 -0
- package/src/channels/tool-defs.mjs +170 -0
- package/src/daemon/host.mjs +118 -0
- package/src/daemon/mcp-transport.mjs +47 -0
- package/src/daemon/session.mjs +100 -0
- package/src/daemon/thin-client.mjs +71 -0
- package/src/daemon/transport.mjs +163 -0
- package/src/memory/data/runtime-manifest.json +40 -0
- package/src/memory/index.mjs +3305 -0
- package/src/memory/lib/agent-ipc.mjs +93 -0
- package/src/memory/lib/bridge-trace-queries.mjs +120 -0
- package/src/memory/lib/core-memory-store.mjs +330 -0
- package/src/memory/lib/embedding-provider.mjs +269 -0
- package/src/memory/lib/embedding-worker.mjs +323 -0
- package/src/memory/lib/llm-worker-host.mjs +17 -0
- package/src/memory/lib/memory-cycle.mjs +11 -0
- package/src/memory/lib/memory-cycle1.mjs +641 -0
- package/src/memory/lib/memory-cycle2.mjs +1284 -0
- package/src/memory/lib/memory-cycle3.mjs +540 -0
- package/src/memory/lib/memory-embed.mjs +299 -0
- package/src/memory/lib/memory-extraction.mjs +5 -0
- package/src/memory/lib/memory-maintenance-store.mjs +32 -0
- package/src/memory/lib/memory-ops-policy.mjs +190 -0
- package/src/memory/lib/memory-recall-id-patch.mjs +15 -0
- package/src/memory/lib/memory-recall-read-query.mjs +7 -0
- package/src/memory/lib/memory-recall-scope-filter.mjs +63 -0
- package/src/memory/lib/memory-recall-store.mjs +621 -0
- package/src/memory/lib/memory-retrievers.mjs +112 -0
- package/src/memory/lib/memory-score.mjs +71 -0
- package/src/memory/lib/memory-text-utils.mjs +58 -0
- package/src/memory/lib/memory.mjs +412 -0
- package/src/memory/lib/model-profile.mjs +85 -0
- package/src/memory/lib/pg/adapter.mjs +308 -0
- package/src/memory/lib/pg/process.mjs +360 -0
- package/src/memory/lib/pg/supervisor.mjs +396 -0
- package/src/memory/lib/project-id-resolver.mjs +86 -0
- package/src/memory/lib/runtime-fetcher.mjs +442 -0
- package/src/memory/lib/trace-store.mjs +728 -0
- package/src/memory/tool-defs.mjs +79 -0
- package/src/search/index.mjs +1173 -0
- package/src/search/lib/backends/anthropic-oauth.mjs +98 -0
- package/src/search/lib/backends/exa.mjs +50 -0
- package/src/search/lib/backends/firecrawl.mjs +61 -0
- package/src/search/lib/backends/gemini-api.mjs +83 -0
- package/src/search/lib/backends/grok-oauth.mjs +86 -0
- package/src/search/lib/backends/index.mjs +150 -0
- package/src/search/lib/backends/openai-api.mjs +144 -0
- package/src/search/lib/backends/openai-oauth.mjs +98 -0
- package/src/search/lib/backends/openai-web-search.mjs +76 -0
- package/src/search/lib/backends/tavily.mjs +55 -0
- package/src/search/lib/backends/xai-api.mjs +113 -0
- package/src/search/lib/cache.mjs +131 -0
- package/src/search/lib/config.mjs +192 -0
- package/src/search/lib/formatter.mjs +115 -0
- package/src/search/lib/provider-usage.mjs +67 -0
- package/src/search/lib/providers.mjs +47 -0
- package/src/search/lib/search-intent.mjs +109 -0
- package/src/search/lib/setup-handler.mjs +261 -0
- package/src/search/lib/state.mjs +201 -0
- package/src/search/lib/web-tools.mjs +1207 -0
- package/src/search/tool-defs.mjs +83 -0
- package/src/setup/defender-exclusion.mjs +183 -0
- package/src/shared/abort-controller.mjs +15 -0
- package/src/shared/atomic-file.mjs +420 -0
- package/src/shared/config.mjs +350 -0
- package/src/shared/daemon-recycle.mjs +108 -0
- package/src/shared/disable-claude-builtins.mjs +88 -0
- package/src/shared/err-text.mjs +12 -0
- package/src/shared/llm/cost.mjs +66 -0
- package/src/shared/llm/http-agent.mjs +123 -0
- package/src/shared/llm/index.mjs +41 -0
- package/src/shared/llm/pid-cleanup.mjs +27 -0
- package/src/shared/llm/usage-log.mjs +47 -0
- package/src/shared/plugin-paths.mjs +58 -0
- package/src/shared/schedules-store.mjs +70 -0
- package/src/shared/seed.mjs +119 -0
- package/src/shared/user-cwd.mjs +213 -0
- package/src/shared/user-data-guard.mjs +238 -0
- package/src/status/aggregator.mjs +584 -0
- package/src/status/server.mjs +413 -0
- package/tools.json +1653 -0
|
@@ -0,0 +1,1081 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
9
|
+
process.env.CLAUDE_PLUGIN_ROOT ||= ROOT;
|
|
10
|
+
const DATA_DIR = join(tmpdir(), `mixdog-mutation-io-smoke-data-${process.pid}`);
|
|
11
|
+
process.env.CLAUDE_PLUGIN_DATA = DATA_DIR;
|
|
12
|
+
process.env.MIXDOG_HINT_LEVEL ||= 'normal';
|
|
13
|
+
|
|
14
|
+
const { executeBuiltinTool, recordReadSnapshotForPath } = await import(pathToFileURL(join(ROOT, 'src/agent/orchestrator/tools/builtin.mjs')).href);
|
|
15
|
+
const { executePatchTool } = await import(pathToFileURL(join(ROOT, 'src/agent/orchestrator/tools/patch.mjs')).href);
|
|
16
|
+
|
|
17
|
+
const LINE_SEP = '→';
|
|
18
|
+
const dir = await mkdtemp(join(tmpdir(), 'mixdog-mutation-io-smoke-'));
|
|
19
|
+
|
|
20
|
+
function assertOk(label, out) {
|
|
21
|
+
if (/^Error/.test(String(out))) throw new Error(`${label}: ${out}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function assertError(label, out, wanted = 'Error') {
|
|
25
|
+
if (!String(out).startsWith(wanted)) throw new Error(`${label}: expected ${wanted}, got ${out}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function assertHas(label, text, wanted) {
|
|
29
|
+
if (!String(text).includes(wanted)) throw new Error(`${label}: missing ${JSON.stringify(wanted)} in ${JSON.stringify(String(text).slice(0, 300))}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function assertNotHas(label, text, unwanted) {
|
|
33
|
+
if (String(text).includes(unwanted)) throw new Error(`${label}: unexpected ${JSON.stringify(unwanted)} in ${JSON.stringify(String(text).slice(0, 300))}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function expectFile(path, expected) {
|
|
37
|
+
const got = await readFile(join(dir, path), 'utf8');
|
|
38
|
+
if (got !== expected) throw new Error(`${path}: got ${JSON.stringify(got)}, expected ${JSON.stringify(expected)}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function expectBytes(path, expected) {
|
|
42
|
+
const got = await readFile(join(dir, path));
|
|
43
|
+
if (!got.equals(expected)) throw new Error(`${path}: got ${got.toString('hex')}, expected ${expected.toString('hex')}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function expectNodeCheck(path) {
|
|
47
|
+
const fullPath = join(dir, path);
|
|
48
|
+
const out = spawnSync(process.execPath, ['--check', fullPath], { encoding: 'utf8' });
|
|
49
|
+
if (out.status !== 0) {
|
|
50
|
+
throw new Error(`${path}: node --check failed\n${out.stdout || ''}${out.stderr || ''}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function hashBytes(buf) {
|
|
55
|
+
return createHash('sha256').update(buf).digest('hex');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function patchText(lines) {
|
|
59
|
+
return `${lines.join('\n')}\n`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function patchAndCheck(label, file, patch, expected) {
|
|
63
|
+
const out = await executePatchTool('apply_patch', { patch, base_path: dir }, dir, { readStateScope: `patch-${label}` });
|
|
64
|
+
assertOk(`patch ${label}`, out);
|
|
65
|
+
await expectFile(file, expected);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function warmRead(path, scope, offset = 0, limit = 20) {
|
|
69
|
+
assertOk(`read ${path}`, await executeBuiltinTool('read', { path, offset, limit }, dir, { readStateScope: scope }));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function rmTree(path) {
|
|
73
|
+
await rm(path, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
await writeFile(join(dir, 'patch.txt'), 'a\nb\nc\n', 'utf8');
|
|
78
|
+
await patchAndCheck('replace', 'patch.txt', patchText([
|
|
79
|
+
'--- a/patch.txt',
|
|
80
|
+
'+++ b/patch.txt',
|
|
81
|
+
'@@ -2,1 +2,1 @@',
|
|
82
|
+
'-b',
|
|
83
|
+
'+B',
|
|
84
|
+
]), 'a\nB\nc\n');
|
|
85
|
+
|
|
86
|
+
await writeFile(join(dir, 'delete.txt'), 'a\nb\nc\n', 'utf8');
|
|
87
|
+
await patchAndCheck('delete', 'delete.txt', patchText([
|
|
88
|
+
'--- a/delete.txt',
|
|
89
|
+
'+++ b/delete.txt',
|
|
90
|
+
'@@ -2,1 +2,0 @@',
|
|
91
|
+
'-b',
|
|
92
|
+
]), 'a\nc\n');
|
|
93
|
+
|
|
94
|
+
await writeFile(join(dir, 'crlf.txt'), 'a\r\nb\r\nc\r\n', 'utf8');
|
|
95
|
+
await patchAndCheck('crlf', 'crlf.txt', patchText([
|
|
96
|
+
'--- a/crlf.txt',
|
|
97
|
+
'+++ b/crlf.txt',
|
|
98
|
+
'@@ -2,1 +2,1 @@',
|
|
99
|
+
'-b',
|
|
100
|
+
'+B',
|
|
101
|
+
]), 'a\r\nB\r\nc\r\n');
|
|
102
|
+
|
|
103
|
+
await writeFile(join(dir, 'crlf-delete.txt'), 'a\r\nb\r\nc\r\n', 'utf8');
|
|
104
|
+
await patchAndCheck('crlf-delete', 'crlf-delete.txt', patchText([
|
|
105
|
+
'--- a/crlf-delete.txt',
|
|
106
|
+
'+++ b/crlf-delete.txt',
|
|
107
|
+
'@@ -2,1 +2,0 @@',
|
|
108
|
+
'-b',
|
|
109
|
+
]), 'a\r\nc\r\n');
|
|
110
|
+
|
|
111
|
+
await writeFile(join(dir, 'binary-ish.txt'), Buffer.from([0xff, 0xfe, 0x0a, 0x61, 0x0a, 0x62, 0x0a]));
|
|
112
|
+
const binaryPatch = await executePatchTool('apply_patch', { patch: patchText([
|
|
113
|
+
'--- a/binary-ish.txt',
|
|
114
|
+
'+++ b/binary-ish.txt',
|
|
115
|
+
'@@ -2,1 +2,1 @@',
|
|
116
|
+
'-a',
|
|
117
|
+
'+A',
|
|
118
|
+
]), base_path: dir }, dir, { readStateScope: 'patch-binary-ish' });
|
|
119
|
+
assertOk('patch binary-ish exact byte splice', binaryPatch);
|
|
120
|
+
await expectBytes('binary-ish.txt', Buffer.from([0xff, 0xfe, 0x0a, 0x41, 0x0a, 0x62, 0x0a]));
|
|
121
|
+
|
|
122
|
+
await writeFile(join(dir, 'delete-eof-no-newline.txt'), 'a\nb', 'utf8');
|
|
123
|
+
await patchAndCheck('delete eof no newline', 'delete-eof-no-newline.txt', patchText([
|
|
124
|
+
'--- a/delete-eof-no-newline.txt',
|
|
125
|
+
'+++ b/delete-eof-no-newline.txt',
|
|
126
|
+
'@@ -2,1 +2,0 @@',
|
|
127
|
+
'-b',
|
|
128
|
+
'\',
|
|
129
|
+
]), 'a\n');
|
|
130
|
+
|
|
131
|
+
await writeFile(join(dir, 'insert.txt'), 'a\nc\n', 'utf8');
|
|
132
|
+
await patchAndCheck('insert only', 'insert.txt', patchText([
|
|
133
|
+
'--- a/insert.txt',
|
|
134
|
+
'+++ b/insert.txt',
|
|
135
|
+
'@@ -1,0 +2,1 @@',
|
|
136
|
+
'+b',
|
|
137
|
+
]), 'a\nb\nc\n');
|
|
138
|
+
|
|
139
|
+
await writeFile(join(dir, 'crlf-insert.txt'), 'a\r\nc\r\n', 'utf8');
|
|
140
|
+
await patchAndCheck('crlf insert only', 'crlf-insert.txt', patchText([
|
|
141
|
+
'--- a/crlf-insert.txt',
|
|
142
|
+
'+++ b/crlf-insert.txt',
|
|
143
|
+
'@@ -1,0 +2,1 @@',
|
|
144
|
+
'+b',
|
|
145
|
+
]), 'a\r\nb\r\nc\r\n');
|
|
146
|
+
|
|
147
|
+
await writeFile(join(dir, 'v4a-update.txt'), 'class Foo\n function bar\n const x = 1;\n return x;\n', 'utf8');
|
|
148
|
+
const v4aUpdate = await executePatchTool('apply_patch', { patch: patchText([
|
|
149
|
+
'*** Begin Patch',
|
|
150
|
+
'*** Update File: "v4a-update.txt"\t2026-05-21',
|
|
151
|
+
'@@ class Foo',
|
|
152
|
+
'@@ function bar',
|
|
153
|
+
' const x = 1;',
|
|
154
|
+
'- return x;',
|
|
155
|
+
'+ return x + 1;',
|
|
156
|
+
'*** End Patch',
|
|
157
|
+
]), base_path: dir }, dir, { readStateScope: 'patch-v4a-update' });
|
|
158
|
+
assertOk('patch v4a update', v4aUpdate);
|
|
159
|
+
await expectFile('v4a-update.txt', 'class Foo\n function bar\n const x = 1;\n return x + 1;\n');
|
|
160
|
+
|
|
161
|
+
// V4A real paths under a top-level `a/` or `b/` directory must not be
|
|
162
|
+
// diff-prefix-stripped (those prefixes are a unified-diff convention only).
|
|
163
|
+
// Regression: stripping rewrote `a/foo.txt` -> `foo.txt`, hitting the wrong
|
|
164
|
+
// file (ENOENT on update, phantom create on add).
|
|
165
|
+
await mkdir(join(dir, 'a'), { recursive: true });
|
|
166
|
+
await writeFile(join(dir, 'a', 'v4a-ab.txt'), 'class Foo\n const x = 1;\n return x;\n', 'utf8');
|
|
167
|
+
const v4aTopLevelA = await executePatchTool('apply_patch', { patch: patchText([
|
|
168
|
+
'*** Begin Patch',
|
|
169
|
+
'*** Update File: a/v4a-ab.txt',
|
|
170
|
+
'@@ class Foo',
|
|
171
|
+
' const x = 1;',
|
|
172
|
+
'- return x;',
|
|
173
|
+
'+ return x + 1;',
|
|
174
|
+
'*** End Patch',
|
|
175
|
+
]), base_path: dir }, dir, { readStateScope: 'patch-v4a-toplevel-a' });
|
|
176
|
+
assertOk('patch v4a update under top-level a/', v4aTopLevelA);
|
|
177
|
+
await expectFile(join('a', 'v4a-ab.txt'), 'class Foo\n const x = 1;\n return x + 1;\n');
|
|
178
|
+
assertNotHas('no phantom strip of top-level a/', v4aTopLevelA, 'unreadable');
|
|
179
|
+
await mkdir(join(dir, 'b'), { recursive: true });
|
|
180
|
+
const v4aTopLevelB = await executePatchTool('apply_patch', { patch: patchText([
|
|
181
|
+
'*** Begin Patch',
|
|
182
|
+
'*** Add File: b/v4a-new.txt',
|
|
183
|
+
'+hello',
|
|
184
|
+
'+world',
|
|
185
|
+
'*** End Patch',
|
|
186
|
+
]), base_path: dir }, dir, { readStateScope: 'patch-v4a-toplevel-b' });
|
|
187
|
+
assertOk('patch v4a add under top-level b/', v4aTopLevelB);
|
|
188
|
+
await expectFile(join('b', 'v4a-new.txt'), 'hello\nworld\n');
|
|
189
|
+
|
|
190
|
+
await writeFile(join(dir, 'v4a-tolerant-context.txt'), 'function pick\nconst x = 1;\nreturn x;\n', 'utf8');
|
|
191
|
+
const v4aTolerantContext = await executePatchTool('apply_patch', { patch: patchText([
|
|
192
|
+
'*** Begin Patch',
|
|
193
|
+
'*** Update File: v4a-tolerant-context.txt',
|
|
194
|
+
'@@ function pick @@',
|
|
195
|
+
'const x = 1;',
|
|
196
|
+
'-return x;',
|
|
197
|
+
'+return x + 1;',
|
|
198
|
+
'*** End Patch',
|
|
199
|
+
]), base_path: dir, format: 'v4a' }, dir, { readStateScope: 'patch-v4a-tolerant-context' });
|
|
200
|
+
assertOk('patch v4a accepts trailing anchor marker and bare context', v4aTolerantContext);
|
|
201
|
+
await expectFile('v4a-tolerant-context.txt', 'function pick\nconst x = 1;\nreturn x + 1;\n');
|
|
202
|
+
|
|
203
|
+
await writeFile(join(dir, 'v4a-js-syntax.mjs'), [
|
|
204
|
+
'export function pick(ok) {',
|
|
205
|
+
' if (ok) {',
|
|
206
|
+
' return 1;',
|
|
207
|
+
' }',
|
|
208
|
+
' return 0;',
|
|
209
|
+
'}',
|
|
210
|
+
'',
|
|
211
|
+
].join('\n'), 'utf8');
|
|
212
|
+
const v4aSyntax = await executePatchTool('apply_patch', { patch: patchText([
|
|
213
|
+
'*** Begin Patch',
|
|
214
|
+
'*** Update File: v4a-js-syntax.mjs',
|
|
215
|
+
'@@ function pick',
|
|
216
|
+
' if (ok) {',
|
|
217
|
+
'- return 1;',
|
|
218
|
+
'+ return 2;',
|
|
219
|
+
' }',
|
|
220
|
+
'*** End Patch',
|
|
221
|
+
]), base_path: dir, format: 'v4a' }, dir, { readStateScope: 'patch-v4a-js-syntax' });
|
|
222
|
+
assertOk('patch v4a keeps js syntax', v4aSyntax);
|
|
223
|
+
await expectFile('v4a-js-syntax.mjs', [
|
|
224
|
+
'export function pick(ok) {',
|
|
225
|
+
' if (ok) {',
|
|
226
|
+
' return 2;',
|
|
227
|
+
' }',
|
|
228
|
+
' return 0;',
|
|
229
|
+
'}',
|
|
230
|
+
'',
|
|
231
|
+
].join('\n'));
|
|
232
|
+
expectNodeCheck('v4a-js-syntax.mjs');
|
|
233
|
+
|
|
234
|
+
await writeFile(join(dir, 'v4a-reject-no-partial.mjs'), 'export function f() {\n return 1;\n}\n', 'utf8');
|
|
235
|
+
let v4aReject;
|
|
236
|
+
try {
|
|
237
|
+
v4aReject = await executePatchTool('apply_patch', { patch: patchText([
|
|
238
|
+
'*** Begin Patch',
|
|
239
|
+
'*** Update File: v4a-reject-no-partial.mjs',
|
|
240
|
+
'@@ function f',
|
|
241
|
+
'- return missing;',
|
|
242
|
+
'+ return 2;',
|
|
243
|
+
'*** End Patch',
|
|
244
|
+
]), base_path: dir, format: 'v4a' }, dir, { readStateScope: 'patch-v4a-reject-no-partial' });
|
|
245
|
+
} catch (err) {
|
|
246
|
+
v4aReject = `Error: ${err?.message || String(err)}`;
|
|
247
|
+
}
|
|
248
|
+
assertError('patch v4a rejects missing context', v4aReject);
|
|
249
|
+
assertHas('patch v4a missing context hint includes expected line', v4aReject, 'expected first old line');
|
|
250
|
+
assertHas('patch v4a missing context hint includes nearest line', v4aReject, 'nearest line');
|
|
251
|
+
await expectFile('v4a-reject-no-partial.mjs', 'export function f() {\n return 1;\n}\n');
|
|
252
|
+
expectNodeCheck('v4a-reject-no-partial.mjs');
|
|
253
|
+
|
|
254
|
+
await writeFile(join(dir, 'v4a-insert.txt'), 'class Foo\n function bar\n return x;\n', 'utf8');
|
|
255
|
+
const v4aInsert = await executePatchTool('apply_patch', { patch: patchText([
|
|
256
|
+
'*** Begin Patch',
|
|
257
|
+
'*** Update File: v4a-insert.txt',
|
|
258
|
+
'@@ function bar',
|
|
259
|
+
'+ const y = 2;',
|
|
260
|
+
'*** End Patch',
|
|
261
|
+
]), base_path: dir, format: 'v4a' }, dir, { readStateScope: 'patch-v4a-insert' });
|
|
262
|
+
assertOk('patch v4a insert', v4aInsert);
|
|
263
|
+
await expectFile('v4a-insert.txt', 'class Foo\n function bar\n const y = 2;\n return x;\n');
|
|
264
|
+
|
|
265
|
+
await writeFile(join(dir, 'v4a-missing-anchor-insert.txt'), 'class Foo\n function bar\n return x;\n', 'utf8');
|
|
266
|
+
let v4aMissingAnchorInsert;
|
|
267
|
+
try {
|
|
268
|
+
v4aMissingAnchorInsert = await executePatchTool('apply_patch', { patch: patchText([
|
|
269
|
+
'*** Begin Patch',
|
|
270
|
+
'*** Update File: v4a-missing-anchor-insert.txt',
|
|
271
|
+
'@@ function missing',
|
|
272
|
+
'+ const nope = true;',
|
|
273
|
+
'*** End Patch',
|
|
274
|
+
]), base_path: dir, format: 'v4a' }, dir, { readStateScope: 'patch-v4a-missing-anchor-insert' });
|
|
275
|
+
} catch (err) {
|
|
276
|
+
v4aMissingAnchorInsert = `Error: ${err?.message || String(err)}`;
|
|
277
|
+
}
|
|
278
|
+
assertError('patch v4a missing anchor insert rejects', v4aMissingAnchorInsert);
|
|
279
|
+
assertHas('patch v4a missing anchor insert guidance', v4aMissingAnchorInsert, 'anchor not found');
|
|
280
|
+
assertHas('patch v4a missing anchor nearest guidance', v4aMissingAnchorInsert, 'nearest anchor candidate');
|
|
281
|
+
assertHas('patch v4a missing anchor nearest line', v4aMissingAnchorInsert, 'function bar');
|
|
282
|
+
await expectFile('v4a-missing-anchor-insert.txt', 'class Foo\n function bar\n return x;\n');
|
|
283
|
+
|
|
284
|
+
await writeFile(join(dir, 'bare-unified-anchor.txt'), 'class Foo\n function bar\n return x;\n', 'utf8');
|
|
285
|
+
const bareUnifiedAnchor = await executePatchTool('apply_patch', { patch: patchText([
|
|
286
|
+
'--- a/bare-unified-anchor.txt',
|
|
287
|
+
'+++ b/bare-unified-anchor.txt',
|
|
288
|
+
'@@ function bar',
|
|
289
|
+
'- return x;',
|
|
290
|
+
'+ return x + 2;',
|
|
291
|
+
]), base_path: dir }, dir, { readStateScope: 'patch-bare-unified-anchor' });
|
|
292
|
+
assertOk('patch bare unified anchor', bareUnifiedAnchor);
|
|
293
|
+
await expectFile('bare-unified-anchor.txt', 'class Foo\n function bar\n return x + 2;\n');
|
|
294
|
+
|
|
295
|
+
let strictBareUnified;
|
|
296
|
+
try {
|
|
297
|
+
strictBareUnified = await executePatchTool('apply_patch', { patch: patchText([
|
|
298
|
+
'--- a/bare-unified-anchor.txt',
|
|
299
|
+
'+++ b/bare-unified-anchor.txt',
|
|
300
|
+
'@@ function bar',
|
|
301
|
+
'- return x + 2;',
|
|
302
|
+
'+ return x + 3;',
|
|
303
|
+
]), base_path: dir, format: 'unified' }, dir, { readStateScope: 'patch-strict-bare-unified' });
|
|
304
|
+
} catch (err) {
|
|
305
|
+
strictBareUnified = `Error: ${err?.message || String(err)}`;
|
|
306
|
+
}
|
|
307
|
+
assertError('patch strict unified bare hunk guidance rejects', strictBareUnified);
|
|
308
|
+
assertHas('patch strict unified bare hunk counted hint', strictBareUnified, '@@ -A,B +C,D @@');
|
|
309
|
+
assertNotHas('patch strict unified bare hunk v4a hint removed', strictBareUnified, 'remove format:"unified"');
|
|
310
|
+
await expectFile('bare-unified-anchor.txt', 'class Foo\n function bar\n return x + 2;\n');
|
|
311
|
+
|
|
312
|
+
await writeFile(join(dir, 'unified-nearest-hint.txt'), 'alpha\nbeta current\ngamma\n', 'utf8');
|
|
313
|
+
const unifiedNearestHint = await executePatchTool('apply_patch', { patch: patchText([
|
|
314
|
+
'--- a/unified-nearest-hint.txt',
|
|
315
|
+
'+++ b/unified-nearest-hint.txt',
|
|
316
|
+
'@@ -2,1 +2,1 @@',
|
|
317
|
+
'-beta stale',
|
|
318
|
+
'+beta done',
|
|
319
|
+
]), base_path: dir, reject_partial: false }, dir, { readStateScope: 'patch-unified-nearest-hint' });
|
|
320
|
+
assertError('patch unified hunk nearest hint rejects', unifiedNearestHint);
|
|
321
|
+
assertHas('patch unified hunk nearest expected line', unifiedNearestHint, 'expected first old/context line');
|
|
322
|
+
assertHas('patch unified hunk nearest current line', unifiedNearestHint, 'nearest line 2');
|
|
323
|
+
await expectFile('unified-nearest-hint.txt', 'alpha\nbeta current\ngamma\n');
|
|
324
|
+
|
|
325
|
+
await writeFile(join(dir, 'dry-run-context-preview.txt'), 'actual\n', 'utf8');
|
|
326
|
+
const dryRunContextPreview = await executePatchTool('apply_patch', { patch: patchText([
|
|
327
|
+
'--- a/dry-run-context-preview.txt',
|
|
328
|
+
'+++ b/dry-run-context-preview.txt',
|
|
329
|
+
'@@ -1,1 +1,2 @@',
|
|
330
|
+
' stale context',
|
|
331
|
+
'+inserted',
|
|
332
|
+
]), base_path: dir, dry_run: true, fuzzy: false, reject_partial: false }, dir, { readStateScope: 'patch-dry-run-context-preview' });
|
|
333
|
+
assertError('patch dry-run context mismatch reports without writing', dryRunContextPreview);
|
|
334
|
+
assertHas('patch dry-run context mismatch expected line', dryRunContextPreview, 'expected first old/context line');
|
|
335
|
+
assertHas('patch dry-run context mismatch context line', dryRunContextPreview, 'stale context');
|
|
336
|
+
assertHas('patch dry-run context mismatch current line marker', dryRunContextPreview, 'nearest line 1');
|
|
337
|
+
assertHas('patch dry-run context mismatch current line', dryRunContextPreview, 'actual');
|
|
338
|
+
assertNotHas('patch dry-run context mismatch retry block removed', dryRunContextPreview, '*** Update File: dry-run-context-preview.txt');
|
|
339
|
+
assertNotHas('patch dry-run context mismatch retry hint removed', dryRunContextPreview, 'read({path,line,context})');
|
|
340
|
+
await expectFile('dry-run-context-preview.txt', 'actual\n');
|
|
341
|
+
|
|
342
|
+
const bareUnifiedAdd = await executePatchTool('apply_patch', { patch: patchText([
|
|
343
|
+
'--- /dev/null',
|
|
344
|
+
'+++ b/bare-unified-add.txt',
|
|
345
|
+
'@@',
|
|
346
|
+
'+made',
|
|
347
|
+
]), base_path: dir }, dir, { readStateScope: 'patch-bare-unified-add' });
|
|
348
|
+
assertOk('patch bare unified add', bareUnifiedAdd);
|
|
349
|
+
await expectFile('bare-unified-add.txt', 'made\n');
|
|
350
|
+
|
|
351
|
+
await writeFile(join(dir, 'bare-unified-delete.txt'), 'remove\nme\n', 'utf8');
|
|
352
|
+
const bareUnifiedDelete = await executePatchTool('apply_patch', { patch: patchText([
|
|
353
|
+
'--- a/bare-unified-delete.txt',
|
|
354
|
+
'+++ /dev/null',
|
|
355
|
+
'@@',
|
|
356
|
+
'-remove',
|
|
357
|
+
'-me',
|
|
358
|
+
]), base_path: dir }, dir, { readStateScope: 'patch-bare-unified-delete' });
|
|
359
|
+
assertOk('patch bare unified delete', bareUnifiedDelete);
|
|
360
|
+
try {
|
|
361
|
+
await readFile(join(dir, 'bare-unified-delete.txt'), 'utf8');
|
|
362
|
+
throw new Error('bare unified delete left file on disk');
|
|
363
|
+
} catch (err) {
|
|
364
|
+
if (err?.code !== 'ENOENT') throw err;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const v4aAdd = await executePatchTool('apply_patch', { patch: patchText([
|
|
368
|
+
'*** Begin Patch',
|
|
369
|
+
'*** Add File: v4a-add.txt',
|
|
370
|
+
'+hello',
|
|
371
|
+
'+',
|
|
372
|
+
'+world',
|
|
373
|
+
'*** End Patch',
|
|
374
|
+
]), base_path: dir, format: 'v4a' }, dir, { readStateScope: 'patch-v4a-add' });
|
|
375
|
+
assertOk('patch v4a add', v4aAdd);
|
|
376
|
+
await expectFile('v4a-add.txt', 'hello\n\nworld\n');
|
|
377
|
+
|
|
378
|
+
await writeFile(join(dir, 'v4a-empty-delete.txt'), '', 'utf8');
|
|
379
|
+
const v4aDeleteEmpty = await executePatchTool('apply_patch', { patch: patchText([
|
|
380
|
+
'*** Begin Patch',
|
|
381
|
+
'*** Delete File: v4a-empty-delete.txt',
|
|
382
|
+
'*** End Patch',
|
|
383
|
+
]), base_path: dir, format: 'v4a' }, dir, { readStateScope: 'patch-v4a-delete-empty' });
|
|
384
|
+
assertOk('patch v4a empty delete', v4aDeleteEmpty);
|
|
385
|
+
try {
|
|
386
|
+
await readFile(join(dir, 'v4a-empty-delete.txt'), 'utf8');
|
|
387
|
+
throw new Error('v4a empty delete left file on disk');
|
|
388
|
+
} catch (err) {
|
|
389
|
+
if (err?.code !== 'ENOENT') throw err;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
await writeFile(join(dir, 'unified-empty-delete.txt'), '', 'utf8');
|
|
393
|
+
const unifiedEmptyDelete = await executePatchTool('apply_patch', { patch: patchText([
|
|
394
|
+
'--- a/unified-empty-delete.txt',
|
|
395
|
+
'+++ /dev/null',
|
|
396
|
+
'@@ -1,0 +0,0 @@',
|
|
397
|
+
]), base_path: dir }, dir, { readStateScope: 'patch-unified-empty-delete' });
|
|
398
|
+
assertOk('patch unified zero-length empty delete', unifiedEmptyDelete);
|
|
399
|
+
try {
|
|
400
|
+
await readFile(join(dir, 'unified-empty-delete.txt'), 'utf8');
|
|
401
|
+
throw new Error('unified empty delete left file on disk');
|
|
402
|
+
} catch (err) {
|
|
403
|
+
if (err?.code !== 'ENOENT') throw err;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
await writeFile(join(dir, 'unified-nonempty-zero-delete.txt'), 'still here\n', 'utf8');
|
|
407
|
+
const unifiedNonemptyZeroDelete = await executePatchTool('apply_patch', { patch: patchText([
|
|
408
|
+
'--- a/unified-nonempty-zero-delete.txt',
|
|
409
|
+
'+++ /dev/null',
|
|
410
|
+
'@@ -1,0 +0,0 @@',
|
|
411
|
+
]), base_path: dir }, dir, { readStateScope: 'patch-unified-nonempty-zero-delete' });
|
|
412
|
+
assertError('patch unified zero-length non-empty delete rejects', unifiedNonemptyZeroDelete);
|
|
413
|
+
assertHas('patch unified zero-length non-empty delete residual hint', unifiedNonemptyZeroDelete, 'residual byte');
|
|
414
|
+
await expectFile('unified-nonempty-zero-delete.txt', 'still here\n');
|
|
415
|
+
|
|
416
|
+
await writeFile(join(dir, 'mf-a.txt'), 'a\nb\n', 'utf8');
|
|
417
|
+
await writeFile(join(dir, 'mf-b.txt'), 'x\ny\n', 'utf8');
|
|
418
|
+
const multifileOut = await executePatchTool('apply_patch', { patch: patchText([
|
|
419
|
+
'--- a/mf-a.txt',
|
|
420
|
+
'+++ b/mf-a.txt',
|
|
421
|
+
'@@ -2,1 +2,1 @@',
|
|
422
|
+
'-b',
|
|
423
|
+
'+B',
|
|
424
|
+
'--- a/mf-b.txt',
|
|
425
|
+
'+++ b/mf-b.txt',
|
|
426
|
+
'@@ -2,1 +2,1 @@',
|
|
427
|
+
'-y',
|
|
428
|
+
'+Y',
|
|
429
|
+
]), base_path: dir }, dir, { readStateScope: 'patch-multifile' });
|
|
430
|
+
assertOk('patch multifile', multifileOut);
|
|
431
|
+
await expectFile('mf-a.txt', 'a\nB\n');
|
|
432
|
+
await expectFile('mf-b.txt', 'x\nY\n');
|
|
433
|
+
|
|
434
|
+
await writeFile(join(dir, 'patch-edit-chain.txt'), 'before\nneedle\n', 'utf8');
|
|
435
|
+
const patchEditScope = 'patch-edit-chain';
|
|
436
|
+
assertOk('patch edit chain patch', await executePatchTool('apply_patch', {
|
|
437
|
+
patch: patchText([
|
|
438
|
+
'--- a/patch-edit-chain.txt',
|
|
439
|
+
'+++ b/patch-edit-chain.txt',
|
|
440
|
+
'@@ -1,2 +1,2 @@',
|
|
441
|
+
'-before',
|
|
442
|
+
'+after',
|
|
443
|
+
' needle',
|
|
444
|
+
]),
|
|
445
|
+
base_path: dir,
|
|
446
|
+
reject_partial: false,
|
|
447
|
+
}, dir, { readStateScope: patchEditScope }));
|
|
448
|
+
assertOk('patch edit chain edit', await executeBuiltinTool('edit', {
|
|
449
|
+
path: 'patch-edit-chain.txt',
|
|
450
|
+
old_string: 'needle',
|
|
451
|
+
new_string: 'done',
|
|
452
|
+
}, dir, { readStateScope: patchEditScope }));
|
|
453
|
+
await expectFile('patch-edit-chain.txt', 'after\ndone\n');
|
|
454
|
+
|
|
455
|
+
await writeFile(join(dir, 'delete-snapshot.txt'), 'same\n', 'utf8');
|
|
456
|
+
await warmRead('delete-snapshot.txt', 'patch-delete-snapshot');
|
|
457
|
+
const deleteSnapshotOut = await executePatchTool('apply_patch', { patch: patchText([
|
|
458
|
+
'--- a/delete-snapshot.txt',
|
|
459
|
+
'+++ /dev/null',
|
|
460
|
+
'@@ -1,1 +0,0 @@',
|
|
461
|
+
'-same',
|
|
462
|
+
]), base_path: dir }, dir, { readStateScope: 'patch-delete-snapshot' });
|
|
463
|
+
assertOk('patch delete clears snapshot', deleteSnapshotOut);
|
|
464
|
+
await writeFile(join(dir, 'delete-snapshot.txt'), 'same\n', 'utf8');
|
|
465
|
+
const staleWrite = await executeBuiltinTool('write', {
|
|
466
|
+
path: 'delete-snapshot.txt',
|
|
467
|
+
content: 'clobbered\n',
|
|
468
|
+
}, dir, { readStateScope: 'patch-delete-snapshot' });
|
|
469
|
+
assertError('deleted file snapshot cleared before overwrite', staleWrite, 'Error [code 6]');
|
|
470
|
+
await expectFile('delete-snapshot.txt', 'same\n');
|
|
471
|
+
|
|
472
|
+
assertOk('write seeds edit snapshot/cache', await executeBuiltinTool('write', {
|
|
473
|
+
path: 'write-edit-cache.txt',
|
|
474
|
+
content: 'alpha\nbeta\ngamma\n',
|
|
475
|
+
}, dir, { readStateScope: 'write-edit-cache' }));
|
|
476
|
+
assertOk('edit after write without read', await executeBuiltinTool('edit', {
|
|
477
|
+
path: 'write-edit-cache.txt',
|
|
478
|
+
old_string: 'beta',
|
|
479
|
+
new_string: 'BETA',
|
|
480
|
+
}, dir, { readStateScope: 'write-edit-cache' }));
|
|
481
|
+
await expectFile('write-edit-cache.txt', 'alpha\nBETA\ngamma\n');
|
|
482
|
+
|
|
483
|
+
await writeFile(join(dir, 'read-edit-invalidate.txt'), 'one\ntwo\nthree\n', 'utf8');
|
|
484
|
+
const readEditBefore = await executeBuiltinTool('read', {
|
|
485
|
+
path: 'read-edit-invalidate.txt',
|
|
486
|
+
offset: 1,
|
|
487
|
+
limit: 1,
|
|
488
|
+
}, dir, { readStateScope: 'read-edit-invalidate' });
|
|
489
|
+
assertHas('read before edit cache seed', readEditBefore, `2${LINE_SEP}two`);
|
|
490
|
+
assertOk('edit invalidates same read cache key', await executeBuiltinTool('edit', {
|
|
491
|
+
path: 'read-edit-invalidate.txt',
|
|
492
|
+
old_string: 'two',
|
|
493
|
+
new_string: 'TWO',
|
|
494
|
+
}, dir, { readStateScope: 'read-edit-invalidate' }));
|
|
495
|
+
const readEditAfter = await executeBuiltinTool('read', {
|
|
496
|
+
path: 'read-edit-invalidate.txt',
|
|
497
|
+
offset: 1,
|
|
498
|
+
limit: 1,
|
|
499
|
+
}, dir, { readStateScope: 'read-edit-invalidate' });
|
|
500
|
+
assertHas('read after edit sees fresh content', readEditAfter, `2${LINE_SEP}TWO`);
|
|
501
|
+
if (String(readEditAfter).includes(`2${LINE_SEP}two`)) {
|
|
502
|
+
throw new Error(`read cache returned stale pre-edit content:\n${readEditAfter}`);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
await writeFile(join(dir, 'read-patch-invalidate.txt'), 'one\ntwo\nthree\n', 'utf8');
|
|
506
|
+
const readPatchBefore = await executeBuiltinTool('read', {
|
|
507
|
+
path: 'read-patch-invalidate.txt',
|
|
508
|
+
offset: 1,
|
|
509
|
+
limit: 1,
|
|
510
|
+
}, dir, { readStateScope: 'read-patch-invalidate' });
|
|
511
|
+
assertHas('read before patch cache seed', readPatchBefore, `2${LINE_SEP}two`);
|
|
512
|
+
assertOk('apply_patch invalidates same read cache key', await executePatchTool('apply_patch', {
|
|
513
|
+
patch: patchText([
|
|
514
|
+
'--- a/read-patch-invalidate.txt',
|
|
515
|
+
'+++ b/read-patch-invalidate.txt',
|
|
516
|
+
'@@ -2,1 +2,1 @@',
|
|
517
|
+
'-two',
|
|
518
|
+
'+TWO',
|
|
519
|
+
]),
|
|
520
|
+
base_path: dir,
|
|
521
|
+
}, dir, { readStateScope: 'read-patch-invalidate' }));
|
|
522
|
+
const readPatchAfter = await executeBuiltinTool('read', {
|
|
523
|
+
path: 'read-patch-invalidate.txt',
|
|
524
|
+
offset: 1,
|
|
525
|
+
limit: 1,
|
|
526
|
+
}, dir, { readStateScope: 'read-patch-invalidate' });
|
|
527
|
+
assertHas('read after patch sees fresh content', readPatchAfter, `2${LINE_SEP}TWO`);
|
|
528
|
+
if (String(readPatchAfter).includes(`2${LINE_SEP}two`)) {
|
|
529
|
+
throw new Error(`read cache returned stale pre-patch content:\n${readPatchAfter}`);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
assertOk('batch write seeds edit snapshot/cache', await executeBuiltinTool('write', {
|
|
533
|
+
writes: [
|
|
534
|
+
{ path: 'batch-write-cache-a.txt', content: 'one\ntwo\n' },
|
|
535
|
+
{ path: 'batch-write-cache-b.txt', content: 'red\nblue\n' },
|
|
536
|
+
],
|
|
537
|
+
}, dir, { readStateScope: 'batch-write-cache' }));
|
|
538
|
+
assertOk('edit after batch write without read', await executeBuiltinTool('edit', {
|
|
539
|
+
path: 'batch-write-cache-b.txt',
|
|
540
|
+
old_string: 'blue',
|
|
541
|
+
new_string: 'BLUE',
|
|
542
|
+
}, dir, { readStateScope: 'batch-write-cache' }));
|
|
543
|
+
await expectFile('batch-write-cache-b.txt', 'red\nBLUE\n');
|
|
544
|
+
|
|
545
|
+
await writeFile(join(dir, 'io-cache-stress.txt'), 'alpha\nbeta\ngamma\n', 'utf8');
|
|
546
|
+
const stressScope = 'io-cache-stress';
|
|
547
|
+
const stressRead = await executeBuiltinTool('read', { path: 'io-cache-stress.txt' }, dir, { readStateScope: stressScope });
|
|
548
|
+
assertHas('stress read seeds snapshot', stressRead, `2${LINE_SEP}beta`);
|
|
549
|
+
assertOk('stress edit invalidates cache', await executeBuiltinTool('edit', {
|
|
550
|
+
path: 'io-cache-stress.txt',
|
|
551
|
+
old_string: 'beta',
|
|
552
|
+
new_string: 'BETA',
|
|
553
|
+
}, dir, { readStateScope: stressScope }));
|
|
554
|
+
assertOk('stress patch after edit', await executePatchTool('apply_patch', { patch: patchText([
|
|
555
|
+
'--- a/io-cache-stress.txt',
|
|
556
|
+
'+++ b/io-cache-stress.txt',
|
|
557
|
+
'@@ -3,1 +3,1 @@',
|
|
558
|
+
'-gamma',
|
|
559
|
+
'+GAMMA',
|
|
560
|
+
]), base_path: dir }, dir, { readStateScope: stressScope }));
|
|
561
|
+
const stressAfterPatch = await executeBuiltinTool('read', {
|
|
562
|
+
path: 'io-cache-stress.txt',
|
|
563
|
+
offset: 0,
|
|
564
|
+
limit: 3,
|
|
565
|
+
}, dir, { readStateScope: stressScope });
|
|
566
|
+
assertHas('stress read after patch fresh', stressAfterPatch, `3${LINE_SEP}GAMMA`);
|
|
567
|
+
assertOk('stress write after patch snapshot', await executeBuiltinTool('write', {
|
|
568
|
+
path: 'io-cache-stress.txt',
|
|
569
|
+
content: 'final\n',
|
|
570
|
+
}, dir, { readStateScope: stressScope }));
|
|
571
|
+
const stressFinal = await executeBuiltinTool('read', { path: 'io-cache-stress.txt' }, dir, { readStateScope: stressScope });
|
|
572
|
+
assertHas('stress final read fresh', stressFinal, `1${LINE_SEP}final`);
|
|
573
|
+
|
|
574
|
+
await writeFile(join(dir, 'dry-run-keeps-cache.txt'), 'target\n', 'utf8');
|
|
575
|
+
assertOk('dry-run cache warm read', await executeBuiltinTool('read', { path: 'dry-run-keeps-cache.txt' }, dir, { readStateScope: 'dry-run-keeps-cache' }));
|
|
576
|
+
assertOk('dry-run does not write', await executePatchTool('apply_patch', { patch: patchText([
|
|
577
|
+
'--- a/dry-run-keeps-cache.txt',
|
|
578
|
+
'+++ b/dry-run-keeps-cache.txt',
|
|
579
|
+
'@@ -1,1 +1,1 @@',
|
|
580
|
+
'-target',
|
|
581
|
+
'+DRY-RUN',
|
|
582
|
+
]), base_path: dir, dry_run: true }, dir, { readStateScope: 'dry-run-keeps-cache' }));
|
|
583
|
+
assertOk('edit still works after dry-run', await executeBuiltinTool('edit', {
|
|
584
|
+
path: 'dry-run-keeps-cache.txt',
|
|
585
|
+
old_string: 'target',
|
|
586
|
+
new_string: 'DONE',
|
|
587
|
+
}, dir, { readStateScope: 'dry-run-keeps-cache' }));
|
|
588
|
+
await expectFile('dry-run-keeps-cache.txt', 'DONE\n');
|
|
589
|
+
|
|
590
|
+
const readMissingPath = await executeBuiltinTool('read', {}, dir, { readStateScope: 'read-missing-path' });
|
|
591
|
+
assertError('read missing path concise', readMissingPath);
|
|
592
|
+
assertNotHas('read missing path no call shape', readMissingPath, 'read({path');
|
|
593
|
+
const editMissingPath = await executeBuiltinTool('edit', {
|
|
594
|
+
old_string: 'x',
|
|
595
|
+
new_string: 'y',
|
|
596
|
+
}, dir, { readStateScope: 'edit-missing-path' });
|
|
597
|
+
assertError('edit missing path concise', editMissingPath);
|
|
598
|
+
assertNotHas('edit missing path no call shape', editMissingPath, 'edit({path');
|
|
599
|
+
|
|
600
|
+
await writeFile(join(dir, 'glob-async-stat.js'), 'console.log("ok");\n', 'utf8');
|
|
601
|
+
await writeFile(join(dir, 'glob-async-stat.txt'), 'nope\n', 'utf8');
|
|
602
|
+
const globOut = await executeBuiltinTool('glob', {
|
|
603
|
+
pattern: '*.js',
|
|
604
|
+
path: dir,
|
|
605
|
+
head_limit: 5,
|
|
606
|
+
}, dir, { readStateScope: 'glob-async-stat' });
|
|
607
|
+
assertOk('glob async stat path', globOut);
|
|
608
|
+
assertHas('glob async stat result', globOut, 'glob-async-stat.js');
|
|
609
|
+
|
|
610
|
+
const grepLines = Array.from({ length: 300 }, (_, i) => `needle-${i + 1}`).join('\n') + '\n';
|
|
611
|
+
await writeFile(join(dir, 'grep-window.txt'), grepLines, 'utf8');
|
|
612
|
+
const grepWindow = await executeBuiltinTool('grep', {
|
|
613
|
+
pattern: 'needle',
|
|
614
|
+
path: 'grep-window.txt',
|
|
615
|
+
output_mode: 'content',
|
|
616
|
+
head_limit: 5,
|
|
617
|
+
}, dir, { readStateScope: 'grep-window' });
|
|
618
|
+
assertOk('grep windowed stream', grepWindow);
|
|
619
|
+
assertHas('grep windowed first result', grepWindow, 'needle-1');
|
|
620
|
+
assertHas('grep windowed more-matches truncation', grepWindow, 'more matches exist');
|
|
621
|
+
assertHas('grep windowed count-mode hint', grepWindow, "output_mode:'count'");
|
|
622
|
+
if (String(grepWindow).includes('needle-200')) {
|
|
623
|
+
throw new Error('grep windowed stream collected far beyond the requested window');
|
|
624
|
+
}
|
|
625
|
+
const grepMissingPattern = await executeBuiltinTool('grep', {
|
|
626
|
+
path: dir,
|
|
627
|
+
}, dir, { readStateScope: 'grep-missing-pattern' });
|
|
628
|
+
assertError('grep missing pattern concise', grepMissingPattern, 'Error: grep requires pattern');
|
|
629
|
+
assertNotHas('grep missing pattern no example', grepMissingPattern, 'grep({ pattern');
|
|
630
|
+
const grepMissingPath = await executeBuiltinTool('grep', {
|
|
631
|
+
pattern: 'needle',
|
|
632
|
+
path: 'missing-root',
|
|
633
|
+
}, dir, { readStateScope: 'grep-missing-path' });
|
|
634
|
+
assertError('grep missing path concise', grepMissingPath, 'Error: path does not exist');
|
|
635
|
+
assertNotHas('grep missing path no existing-root hint', grepMissingPath, 'existing search root');
|
|
636
|
+
assertNotHas('grep missing path no next-call example', grepMissingPath, 'grep({pattern:"needle"');
|
|
637
|
+
await writeFile(join(dir, 'grep-quote-minimal.txt'), 'quoteNeedle\nregexNeedle\n', 'utf8');
|
|
638
|
+
const grepQuotedPattern = await executeBuiltinTool('grep', {
|
|
639
|
+
pattern: "'quoteNeedle'",
|
|
640
|
+
path: 'grep-quote-minimal.txt',
|
|
641
|
+
output_mode: 'content',
|
|
642
|
+
}, dir, { readStateScope: 'grep-quoted-pattern' });
|
|
643
|
+
assertOk('grep quoted pattern no-match minimal', grepQuotedPattern);
|
|
644
|
+
assertHas('grep quoted pattern no-match marker', grepQuotedPattern, '(no matches)');
|
|
645
|
+
assertNotHas('grep quoted pattern no hint', grepQuotedPattern, 'without surrounding quotes');
|
|
646
|
+
const grepRegexLiteralPattern = await executeBuiltinTool('grep', {
|
|
647
|
+
pattern: '/regexNeedle/',
|
|
648
|
+
path: 'grep-quote-minimal.txt',
|
|
649
|
+
output_mode: 'content',
|
|
650
|
+
}, dir, { readStateScope: 'grep-regex-literal-pattern' });
|
|
651
|
+
assertOk('grep regex literal no-match minimal', grepRegexLiteralPattern);
|
|
652
|
+
assertNotHas('grep regex literal no hint', grepRegexLiteralPattern, 'without slashes or flags');
|
|
653
|
+
const grepUnknownType = await executeBuiltinTool('grep', {
|
|
654
|
+
pattern: 'quoteNeedle',
|
|
655
|
+
path: 'grep-quote-minimal.txt',
|
|
656
|
+
type: 'definitely-not-a-type',
|
|
657
|
+
}, dir, { readStateScope: 'grep-unknown-type' });
|
|
658
|
+
assertError('grep unknown type concise', grepUnknownType);
|
|
659
|
+
assertNotHas('grep unknown type no glob alternative', grepUnknownType, 'glob:"*.mjs"');
|
|
660
|
+
await mkdir(join(dir, 'scripts'));
|
|
661
|
+
await writeFile(join(dir, 'scripts', 'one.mjs'), 'needle via path glob\n', 'utf8');
|
|
662
|
+
const grepGlobPath = await executeBuiltinTool('grep', {
|
|
663
|
+
pattern: 'needle',
|
|
664
|
+
path: 'scripts/*.mjs',
|
|
665
|
+
output_mode: 'content',
|
|
666
|
+
}, dir, { readStateScope: 'grep-glob-path' });
|
|
667
|
+
assertOk('grep path glob auto-lift', grepGlobPath);
|
|
668
|
+
assertHas('grep path glob auto-lift result', grepGlobPath, 'needle via path glob');
|
|
669
|
+
const grepGlobFallback = await executeBuiltinTool('grep', {
|
|
670
|
+
path: 'scripts/*.mjs',
|
|
671
|
+
}, dir, { readStateScope: 'grep-glob-fallback' });
|
|
672
|
+
assertError('grep path glob without pattern stays strict', grepGlobFallback, 'Error: grep requires pattern');
|
|
673
|
+
const globPathFallback = await executeBuiltinTool('glob', {
|
|
674
|
+
path: 'scripts/*.mjs',
|
|
675
|
+
head_limit: 5,
|
|
676
|
+
}, dir, { readStateScope: 'glob-path-fallback' });
|
|
677
|
+
assertOk('glob path fallback', globPathFallback);
|
|
678
|
+
assertHas('glob path fallback result', globPathFallback, 'scripts/one.mjs');
|
|
679
|
+
const globQuotedPattern = await executeBuiltinTool('glob', {
|
|
680
|
+
pattern: "'*.mjs'",
|
|
681
|
+
path: 'scripts',
|
|
682
|
+
head_limit: 5,
|
|
683
|
+
}, dir, { readStateScope: 'glob-quoted-pattern' });
|
|
684
|
+
assertOk('glob quoted pattern no-files minimal', globQuotedPattern);
|
|
685
|
+
assertHas('glob quoted pattern marker', globQuotedPattern, '(no files found)');
|
|
686
|
+
assertNotHas('glob quoted pattern no hint', globQuotedPattern, 'without surrounding quotes');
|
|
687
|
+
const globCommaPattern = await executeBuiltinTool('glob', {
|
|
688
|
+
pattern: '*.mjs,*.txt',
|
|
689
|
+
path: 'scripts',
|
|
690
|
+
head_limit: 5,
|
|
691
|
+
}, dir, { readStateScope: 'glob-comma-pattern' });
|
|
692
|
+
assertOk('glob comma pattern no-files minimal', globCommaPattern);
|
|
693
|
+
assertNotHas('glob comma pattern no hint', globCommaPattern, 'pattern array or brace glob');
|
|
694
|
+
const globMissingPath = await executeBuiltinTool('glob', {
|
|
695
|
+
pattern: '*.mjs',
|
|
696
|
+
path: 'missing-scripts',
|
|
697
|
+
head_limit: 5,
|
|
698
|
+
}, dir, { readStateScope: 'glob-missing-path' });
|
|
699
|
+
assertError('glob missing path concise', globMissingPath, 'Error: path does not exist');
|
|
700
|
+
assertNotHas('glob missing path no existing-root hint', globMissingPath, 'existing base directory');
|
|
701
|
+
assertNotHas('glob missing path no next-call example', globMissingPath, 'glob({pattern:"*.mjs"');
|
|
702
|
+
const findPathFallback = await executeBuiltinTool('list', {
|
|
703
|
+
mode: 'find',
|
|
704
|
+
path: 'scripts/*.mjs',
|
|
705
|
+
type: 'file',
|
|
706
|
+
head_limit: 5,
|
|
707
|
+
}, dir, { readStateScope: 'find-path-fallback' });
|
|
708
|
+
assertOk('find_files path fallback', findPathFallback);
|
|
709
|
+
assertHas('find_files path fallback result', findPathFallback, 'scripts/one.mjs');
|
|
710
|
+
const listPathFallback = await executeBuiltinTool('list', {
|
|
711
|
+
path: 'scripts/*.mjs',
|
|
712
|
+
type: 'file',
|
|
713
|
+
head_limit: 5,
|
|
714
|
+
}, dir, { readStateScope: 'list-path-fallback' });
|
|
715
|
+
assertOk('list path glob fallback', listPathFallback);
|
|
716
|
+
assertHas('list path glob fallback result', listPathFallback, 'scripts/one.mjs');
|
|
717
|
+
await mkdir(join(dir, 'scripts', 'sub'));
|
|
718
|
+
await writeFile(join(dir, 'scripts', 'sub', 'two.mjs'), 'nested\n', 'utf8');
|
|
719
|
+
const listRecursivePathFallback = await executeBuiltinTool('list', {
|
|
720
|
+
path: 'scripts/**/*.mjs',
|
|
721
|
+
type: 'file',
|
|
722
|
+
head_limit: 10,
|
|
723
|
+
}, dir, { readStateScope: 'list-recursive-path-fallback' });
|
|
724
|
+
assertOk('list recursive path glob fallback', listRecursivePathFallback);
|
|
725
|
+
assertHas('list recursive path glob root result', listRecursivePathFallback, 'scripts/one.mjs');
|
|
726
|
+
assertHas('list recursive path glob nested result', listRecursivePathFallback, 'scripts/sub/two.mjs');
|
|
727
|
+
await Promise.all(Array.from({ length: 20 }, (_, i) =>
|
|
728
|
+
writeFile(join(dir, `grep-count-${String(i + 1).padStart(2, '0')}.txt`), 'countneedle\n', 'utf8')));
|
|
729
|
+
const grepCountWindow = await executeBuiltinTool('grep', {
|
|
730
|
+
pattern: 'countneedle',
|
|
731
|
+
path: dir,
|
|
732
|
+
glob: 'grep-count-*.txt',
|
|
733
|
+
output_mode: 'count',
|
|
734
|
+
head_limit: 5,
|
|
735
|
+
}, dir, { readStateScope: 'grep-count-window' });
|
|
736
|
+
assertOk('grep count windowed stream', grepCountWindow);
|
|
737
|
+
assertHas('grep count more-matches truncation', grepCountWindow, 'more matches exist');
|
|
738
|
+
assertHas('grep count count-mode hint', grepCountWindow, "output_mode:'count'");
|
|
739
|
+
assertHas('grep count displayed summary', grepCountWindow, 'total 5 matches across 5 files');
|
|
740
|
+
|
|
741
|
+
await writeFile(join(dir, 'find-batched-stat-a.txt'), 'a\n', 'utf8');
|
|
742
|
+
await writeFile(join(dir, 'find-batched-stat-b.txt'), 'b\n', 'utf8');
|
|
743
|
+
const findBatched = await executeBuiltinTool('list', {
|
|
744
|
+
mode: 'find',
|
|
745
|
+
path: dir,
|
|
746
|
+
name: 'find-batched-stat-*.txt',
|
|
747
|
+
type: 'file',
|
|
748
|
+
head_limit: 5,
|
|
749
|
+
}, dir, { readStateScope: 'find-batched-stat' });
|
|
750
|
+
assertOk('find_files batched stat path', findBatched);
|
|
751
|
+
assertHas('find_files batched stat result', findBatched, 'find-batched-stat-a.txt');
|
|
752
|
+
|
|
753
|
+
const listStatDir = join(dir, 'list-batched-stat');
|
|
754
|
+
await mkdir(listStatDir);
|
|
755
|
+
await writeFile(join(listStatDir, 'small.txt'), 'x\n', 'utf8');
|
|
756
|
+
await writeFile(join(listStatDir, 'large.txt'), 'x'.repeat(4096), 'utf8');
|
|
757
|
+
const listBatched = await executeBuiltinTool('list', {
|
|
758
|
+
path: listStatDir,
|
|
759
|
+
type: 'file',
|
|
760
|
+
sort: 'size',
|
|
761
|
+
head_limit: 1,
|
|
762
|
+
}, dir, { readStateScope: 'list-batched-stat' });
|
|
763
|
+
assertOk('list batched stat path', listBatched);
|
|
764
|
+
assertHas('list batched stat sorted result', listBatched.split('\n')[0], 'large.txt');
|
|
765
|
+
|
|
766
|
+
await writeFile(join(dir, 'edit.txt'), 'alpha\nbeta\ngamma\n', 'utf8');
|
|
767
|
+
await warmRead('edit.txt', 'edit');
|
|
768
|
+
assertOk('edit byte exact', await executeBuiltinTool('edit', {
|
|
769
|
+
path: 'edit.txt',
|
|
770
|
+
old_string: 'beta',
|
|
771
|
+
new_string: 'BETA',
|
|
772
|
+
}, dir, { readStateScope: 'edit' }));
|
|
773
|
+
await expectFile('edit.txt', 'alpha\nBETA\ngamma\n');
|
|
774
|
+
|
|
775
|
+
// 0xC3 followed by a non-continuation byte: invalid UTF-8 with NO UTF-16
|
|
776
|
+
// BOM, so the generic non-UTF-8 branch (not the BOM-aware one) is hit.
|
|
777
|
+
const editBinaryBytes = Buffer.from([0xc3, 0x28, 0x0a, 0x61, 0x0a, 0x62, 0x0a]);
|
|
778
|
+
await writeFile(join(dir, 'edit-binary-ish.txt'), editBinaryBytes);
|
|
779
|
+
recordReadSnapshotForPath(join(dir, 'edit-binary-ish.txt'), 'edit-binary-ish', {
|
|
780
|
+
source: 'test',
|
|
781
|
+
contentHash: hashBytes(editBinaryBytes),
|
|
782
|
+
});
|
|
783
|
+
assertError('edit binary-ish refuses non-UTF-8', await executeBuiltinTool('edit', {
|
|
784
|
+
path: 'edit-binary-ish.txt',
|
|
785
|
+
old_string: 'a',
|
|
786
|
+
new_string: 'A',
|
|
787
|
+
}, dir, { readStateScope: 'edit-binary-ish' }), 'Error: file appears to be non-UTF-8');
|
|
788
|
+
await expectBytes('edit-binary-ish.txt', editBinaryBytes);
|
|
789
|
+
|
|
790
|
+
const editUtf16Bytes = Buffer.concat([Buffer.from([0xff, 0xfe]), Buffer.from('a\nb\n', 'utf16le')]);
|
|
791
|
+
await writeFile(join(dir, 'edit-utf16.txt'), editUtf16Bytes);
|
|
792
|
+
recordReadSnapshotForPath(join(dir, 'edit-utf16.txt'), 'edit-utf16', {
|
|
793
|
+
source: 'test',
|
|
794
|
+
contentHash: hashBytes(editUtf16Bytes),
|
|
795
|
+
});
|
|
796
|
+
assertError('edit utf16 refuses with BOM-aware message', await executeBuiltinTool('edit', {
|
|
797
|
+
path: 'edit-utf16.txt',
|
|
798
|
+
old_string: 'a',
|
|
799
|
+
new_string: 'A',
|
|
800
|
+
}, dir, { readStateScope: 'edit-utf16' }), 'Error: file is UTF-16LE (BOM FF FE)');
|
|
801
|
+
await expectBytes('edit-utf16.txt', editUtf16Bytes);
|
|
802
|
+
|
|
803
|
+
await writeFile(join(dir, 'edit-replace-all.txt'), 'x\nx\nx\n', 'utf8');
|
|
804
|
+
await warmRead('edit-replace-all.txt', 'edit-replace-all');
|
|
805
|
+
assertOk('edit replace_all byte exact', await executeBuiltinTool('edit', {
|
|
806
|
+
path: 'edit-replace-all.txt',
|
|
807
|
+
old_string: 'x',
|
|
808
|
+
new_string: 'X',
|
|
809
|
+
replace_all: true,
|
|
810
|
+
}, dir, { readStateScope: 'edit-replace-all' }));
|
|
811
|
+
await expectFile('edit-replace-all.txt', 'X\nX\nX\n');
|
|
812
|
+
|
|
813
|
+
await writeFile(join(dir, 'edit-multi.txt'), 'left\nmiddle\nright\n', 'utf8');
|
|
814
|
+
await warmRead('edit-multi.txt', 'edit-multi');
|
|
815
|
+
assertOk('edit multi byte exact', await executeBuiltinTool('edit', {
|
|
816
|
+
path: 'edit-multi.txt',
|
|
817
|
+
edits: [
|
|
818
|
+
{ old_string: 'left', new_string: 'LEFT' },
|
|
819
|
+
{ old_string: 'right', new_string: 'RIGHT' },
|
|
820
|
+
],
|
|
821
|
+
}, dir, { readStateScope: 'edit-multi' }));
|
|
822
|
+
await expectFile('edit-multi.txt', 'LEFT\nmiddle\nRIGHT\n');
|
|
823
|
+
|
|
824
|
+
const multiBinaryBytes = Buffer.from([0xc3, 0x28, 0x0a, 0x6c, 0x65, 0x66, 0x74, 0x0a, 0x72, 0x69, 0x67, 0x68, 0x74, 0x0a]);
|
|
825
|
+
await writeFile(join(dir, 'edit-multi-binary-ish.txt'), multiBinaryBytes);
|
|
826
|
+
recordReadSnapshotForPath(join(dir, 'edit-multi-binary-ish.txt'), 'edit-multi-binary-ish', {
|
|
827
|
+
source: 'test',
|
|
828
|
+
contentHash: hashBytes(multiBinaryBytes),
|
|
829
|
+
});
|
|
830
|
+
assertError('edit multi binary-ish refuses non-UTF-8', await executeBuiltinTool('edit', {
|
|
831
|
+
path: 'edit-multi-binary-ish.txt',
|
|
832
|
+
edits: [
|
|
833
|
+
{ old_string: 'left', new_string: 'LEFT' },
|
|
834
|
+
{ old_string: 'right', new_string: 'RIGHT' },
|
|
835
|
+
],
|
|
836
|
+
}, dir, { readStateScope: 'edit-multi-binary-ish' }), 'Error: file appears to be non-UTF-8');
|
|
837
|
+
await expectBytes('edit-multi-binary-ish.txt', multiBinaryBytes);
|
|
838
|
+
|
|
839
|
+
const multiBinaryMissBytes = Buffer.from([0xc3, 0x28, 0x0a, 0x61, 0x0a]);
|
|
840
|
+
await writeFile(join(dir, 'edit-multi-binary-miss.txt'), multiBinaryMissBytes);
|
|
841
|
+
recordReadSnapshotForPath(join(dir, 'edit-multi-binary-miss.txt'), 'edit-multi-binary-miss', {
|
|
842
|
+
source: 'test',
|
|
843
|
+
contentHash: hashBytes(multiBinaryMissBytes),
|
|
844
|
+
});
|
|
845
|
+
const multiBinaryMiss = await executeBuiltinTool('edit', {
|
|
846
|
+
path: 'edit-multi-binary-miss.txt',
|
|
847
|
+
edits: [
|
|
848
|
+
{ old_string: 'missing', new_string: 'MISSING' },
|
|
849
|
+
],
|
|
850
|
+
}, dir, { readStateScope: 'edit-multi-binary-miss' });
|
|
851
|
+
assertError('edit multi binary-ish fallback reject', multiBinaryMiss, 'Error: file appears to be non-UTF-8');
|
|
852
|
+
await expectBytes('edit-multi-binary-miss.txt', multiBinaryMissBytes);
|
|
853
|
+
|
|
854
|
+
await writeFile(join(dir, 'edit-crlf-multi.txt'), 'a\r\nb\r\nc\r\n', 'utf8');
|
|
855
|
+
await warmRead('edit-crlf-multi.txt', 'edit-crlf-multi');
|
|
856
|
+
assertOk('edit crlf multi byte exact', await executeBuiltinTool('edit', {
|
|
857
|
+
path: 'edit-crlf-multi.txt',
|
|
858
|
+
edits: [
|
|
859
|
+
{ old_string: 'a', new_string: 'A' },
|
|
860
|
+
{ old_string: 'c', new_string: 'C' },
|
|
861
|
+
],
|
|
862
|
+
}, dir, { readStateScope: 'edit-crlf-multi' }));
|
|
863
|
+
await expectFile('edit-crlf-multi.txt', 'A\r\nb\r\nC\r\n');
|
|
864
|
+
|
|
865
|
+
await writeFile(join(dir, 'edit-overlap.txt'), 'abcde\n', 'utf8');
|
|
866
|
+
await warmRead('edit-overlap.txt', 'edit-overlap');
|
|
867
|
+
const overlap = await executeBuiltinTool('edit', {
|
|
868
|
+
path: 'edit-overlap.txt',
|
|
869
|
+
edits: [
|
|
870
|
+
{ old_string: 'abc', new_string: 'XXX' },
|
|
871
|
+
{ old_string: 'bcd', new_string: 'YYY' },
|
|
872
|
+
],
|
|
873
|
+
}, dir, { readStateScope: 'edit-overlap' });
|
|
874
|
+
assertError('edit overlap reject', overlap, 'Error [code 8]');
|
|
875
|
+
await expectFile('edit-overlap.txt', 'abcde\n');
|
|
876
|
+
|
|
877
|
+
await writeFile(join(dir, 'partial-replace-all.txt'), 'TARGET\nseen\nmiddle\nTARGET\n', 'utf8');
|
|
878
|
+
await warmRead('partial-replace-all.txt', 'partial-replace-all', 0, 2);
|
|
879
|
+
const partialReplaceAll = await executeBuiltinTool('edit', {
|
|
880
|
+
path: 'partial-replace-all.txt',
|
|
881
|
+
old_string: 'TARGET',
|
|
882
|
+
new_string: 'DONE',
|
|
883
|
+
replace_all: true,
|
|
884
|
+
}, dir, { readStateScope: 'partial-replace-all' });
|
|
885
|
+
assertOk('partial replace_all now succeeds (read-window gate retired)', partialReplaceAll);
|
|
886
|
+
await expectFile('partial-replace-all.txt', 'DONE\nseen\nmiddle\nDONE\n');
|
|
887
|
+
|
|
888
|
+
await writeFile(join(dir, 'batch-read.txt'), Array.from({ length: 300 }, (_, i) => `line-${i + 1}`).join('\n') + '\n', 'utf8');
|
|
889
|
+
const batchRead = await executeBuiltinTool('read', {
|
|
890
|
+
path: [
|
|
891
|
+
{ path: 'batch-read.txt', offset: 10, limit: 3 },
|
|
892
|
+
{ path: 'batch-read.txt', offset: 40, limit: 2 },
|
|
893
|
+
],
|
|
894
|
+
}, dir, { readStateScope: 'batch-read' });
|
|
895
|
+
assertHas('batch read first slice', batchRead, `11${LINE_SEP}line-11`);
|
|
896
|
+
assertHas('batch read second slice', batchRead, `42${LINE_SEP}line-42`);
|
|
897
|
+
assertOk('batch read snapshot edit', await executeBuiltinTool('edit', {
|
|
898
|
+
path: 'batch-read.txt',
|
|
899
|
+
old_string: 'line-41',
|
|
900
|
+
new_string: 'LINE-41',
|
|
901
|
+
}, dir, { readStateScope: 'batch-read' }));
|
|
902
|
+
const readLineWindow = await executeBuiltinTool('read', {
|
|
903
|
+
path: 'batch-read.txt',
|
|
904
|
+
line: 42,
|
|
905
|
+
context: 1,
|
|
906
|
+
}, dir, { readStateScope: 'read-line-window' });
|
|
907
|
+
assertHas('read line window before', readLineWindow, `41${LINE_SEP}LINE-41`);
|
|
908
|
+
assertHas('read line window center', readLineWindow, `42${LINE_SEP}line-42`);
|
|
909
|
+
assertHas('read line window after', readLineWindow, `43${LINE_SEP}line-43`);
|
|
910
|
+
const readPathLine = await executeBuiltinTool('read', {
|
|
911
|
+
path: 'batch-read.txt:120',
|
|
912
|
+
}, dir, { readStateScope: 'read-path-line' });
|
|
913
|
+
assertHas('read path:line center', readPathLine, `120${LINE_SEP}line-120`);
|
|
914
|
+
const readGrepCoordinate = await executeBuiltinTool('read', {
|
|
915
|
+
path: 'batch-read.txt:121:line-121',
|
|
916
|
+
context: 0,
|
|
917
|
+
}, dir, { readStateScope: 'read-grep-coordinate' });
|
|
918
|
+
assertHas('read grep coordinate exact line', readGrepCoordinate, `121${LINE_SEP}line-121`);
|
|
919
|
+
const batchReadLineSpec = await executeBuiltinTool('read', {
|
|
920
|
+
path: [
|
|
921
|
+
{ path: 'batch-read.txt:122', context: 0 },
|
|
922
|
+
{ path: 'batch-read.txt', line: 123, context: 0 },
|
|
923
|
+
],
|
|
924
|
+
}, dir, { readStateScope: 'batch-read-line-spec' });
|
|
925
|
+
assertHas('batch read path:line spec', batchReadLineSpec, `122${LINE_SEP}line-122`);
|
|
926
|
+
assertHas('batch read line spec', batchReadLineSpec, `123${LINE_SEP}line-123`);
|
|
927
|
+
const readHashLineRange = await executeBuiltinTool('read', {
|
|
928
|
+
path: 'batch-read.txt#L124-L126',
|
|
929
|
+
}, dir, { readStateScope: 'read-hash-line-range' });
|
|
930
|
+
assertHas('read #L range start', readHashLineRange, `124${LINE_SEP}line-124`);
|
|
931
|
+
assertHas('read #L range end', readHashLineRange, `126${LINE_SEP}line-126`);
|
|
932
|
+
if (String(readHashLineRange).includes(`127${LINE_SEP}line-127`)) {
|
|
933
|
+
throw new Error(`read #L range was not exact:\n${readHashLineRange}`);
|
|
934
|
+
}
|
|
935
|
+
const readColonLineRange = await executeBuiltinTool('read', {
|
|
936
|
+
path: 'batch-read.txt:127-128',
|
|
937
|
+
}, dir, { readStateScope: 'read-colon-line-range' });
|
|
938
|
+
assertHas('read colon range start', readColonLineRange, `127${LINE_SEP}line-127`);
|
|
939
|
+
assertHas('read colon range end', readColonLineRange, `128${LINE_SEP}line-128`);
|
|
940
|
+
|
|
941
|
+
await writeFile(join(dir, 'edit-delete.txt'), 'a\nb\nc\n', 'utf8');
|
|
942
|
+
await warmRead('edit-delete.txt', 'edit-delete');
|
|
943
|
+
assertOk('edit delete absorb', await executeBuiltinTool('edit', {
|
|
944
|
+
path: 'edit-delete.txt',
|
|
945
|
+
old_string: 'b',
|
|
946
|
+
new_string: '',
|
|
947
|
+
}, dir, { readStateScope: 'edit-delete' }));
|
|
948
|
+
await expectFile('edit-delete.txt', 'a\nc\n');
|
|
949
|
+
|
|
950
|
+
await writeFile(join(dir, 'edit-coordinate.txt'), 'one\ntwo\nthree\n', 'utf8');
|
|
951
|
+
await warmRead('edit-coordinate.txt', 'edit-coordinate');
|
|
952
|
+
assertOk('edit path:line coordinate absorb', await executeBuiltinTool('edit', {
|
|
953
|
+
path: 'edit-coordinate.txt:2',
|
|
954
|
+
old_string: 'two',
|
|
955
|
+
new_string: 'TWO',
|
|
956
|
+
}, dir, { readStateScope: 'edit-coordinate' }));
|
|
957
|
+
await expectFile('edit-coordinate.txt', 'one\nTWO\nthree\n');
|
|
958
|
+
|
|
959
|
+
await writeFile(join(dir, 'multi-edit-coordinate.txt'), 'alpha\nbeta\ngamma\n', 'utf8');
|
|
960
|
+
await warmRead('multi-edit-coordinate.txt', 'multi-edit-coordinate');
|
|
961
|
+
assertOk('multi edit path:line coordinate absorb', await executeBuiltinTool('edit', {
|
|
962
|
+
edits: [
|
|
963
|
+
{ path: 'multi-edit-coordinate.txt:1', old_string: 'alpha', new_string: 'ALPHA' },
|
|
964
|
+
{ path: 'multi-edit-coordinate.txt:2', old_string: 'beta', new_string: 'BETA' },
|
|
965
|
+
],
|
|
966
|
+
}, dir, { readStateScope: 'multi-edit-coordinate' }));
|
|
967
|
+
await expectFile('multi-edit-coordinate.txt', 'ALPHA\nBETA\ngamma\n');
|
|
968
|
+
|
|
969
|
+
await writeFile(join(dir, 'ambig.txt'), 'x\nx\n', 'utf8');
|
|
970
|
+
await warmRead('ambig.txt', 'ambig');
|
|
971
|
+
const ambig = await executeBuiltinTool('edit', {
|
|
972
|
+
path: 'ambig.txt',
|
|
973
|
+
old_string: 'x',
|
|
974
|
+
new_string: 'X',
|
|
975
|
+
}, dir, { readStateScope: 'ambig' });
|
|
976
|
+
if (!String(ambig).startsWith('Error [code 9]')) throw new Error(`ambiguous edit did not return code 9: ${ambig}`);
|
|
977
|
+
|
|
978
|
+
const lines = Array.from({ length: 12000 }, (_, i) => `line-${i + 1}`).join('\n') + '\n';
|
|
979
|
+
await writeFile(join(dir, 'large.txt'), lines, 'utf8');
|
|
980
|
+
const firstTail = await executeBuiltinTool('read', { path: 'large.txt', offset: 8000, limit: 3 }, dir, { readStateScope: 'read-a' });
|
|
981
|
+
assertHas('read tail first', firstTail, `8001${LINE_SEP}line-8001`);
|
|
982
|
+
const cachedTail = await executeBuiltinTool('read', { path: 'large.txt', offset: 10000, limit: 2 }, dir, { readStateScope: 'read-b' });
|
|
983
|
+
assertHas('read tail cached', cachedTail, `10001${LINE_SEP}line-10001`);
|
|
984
|
+
const compactMediumLines = Array.from({ length: 8000 }, (_, i) => `compact-${i + 1}`).join('\n') + '\n';
|
|
985
|
+
await writeFile(join(dir, 'compact-medium.txt'), compactMediumLines, 'utf8');
|
|
986
|
+
const compactMediumDefault = await executeBuiltinTool('read', { path: 'compact-medium.txt' }, dir, { readStateScope: 'read-compact-medium-default' });
|
|
987
|
+
assertHas('compact medium default marker', compactMediumDefault, 'TRUNCATED');
|
|
988
|
+
assertHas('compact medium default head', compactMediumDefault, `1${LINE_SEP}compact-1`);
|
|
989
|
+
assertHas('compact medium default tail', compactMediumDefault, `8000${LINE_SEP}compact-8000`);
|
|
990
|
+
if (String(compactMediumDefault).includes(`4000${LINE_SEP}compact-4000`)) {
|
|
991
|
+
throw new Error('compact medium default read included a middle line that should have been elided');
|
|
992
|
+
}
|
|
993
|
+
const smartSnapshotRows = Array.from({ length: 700 }, (_, i) => i === 349 ? 'M' : `smart-snapshot-${i + 1}`);
|
|
994
|
+
await writeFile(join(dir, 'smart-snapshot.txt'), smartSnapshotRows.join('\n') + '\n', 'utf8');
|
|
995
|
+
const smartSnapshotRead = await executeBuiltinTool('read', { path: 'smart-snapshot.txt' }, dir, { readStateScope: 'read-smart-snapshot' });
|
|
996
|
+
assertHas('smart snapshot default read marker', smartSnapshotRead, 'TRUNCATED');
|
|
997
|
+
assertNotHas('smart snapshot default read no narrow hint', smartSnapshotRead, 'read({path,line,context})');
|
|
998
|
+
const smartHiddenEdit = await executeBuiltinTool('edit', {
|
|
999
|
+
path: 'smart-snapshot.txt',
|
|
1000
|
+
old_string: 'M',
|
|
1001
|
+
new_string: 'N',
|
|
1002
|
+
}, dir, { readStateScope: 'read-smart-snapshot' });
|
|
1003
|
+
assertOk('smart snapshot hidden middle edit now succeeds (read-window gate retired)', smartHiddenEdit);
|
|
1004
|
+
await expectFile('smart-snapshot.txt', smartSnapshotRows.map((row) => (row === 'M' ? 'N' : row)).join('\n') + '\n');
|
|
1005
|
+
const midRangeLines = Array.from({ length: 20000 }, (_, i) => `midrange-${i + 1}`).join('\n') + '\n';
|
|
1006
|
+
await writeFile(join(dir, 'mid-range.txt'), midRangeLines, 'utf8');
|
|
1007
|
+
const midRange = await executeBuiltinTool('read', { path: 'mid-range.txt', offset: 15000, limit: 2 }, dir, { readStateScope: 'read-mid-range-stream' });
|
|
1008
|
+
assertHas('mid range streamed slice first', midRange, `15001${LINE_SEP}midrange-15001`);
|
|
1009
|
+
assertHas('mid range streamed slice footer', midRange, 'range limit reached; next offset: 15002');
|
|
1010
|
+
const mediumHeadLines = Array.from({ length: 100000 }, (_, i) => `head-${i + 1}`).join('\n') + '\n';
|
|
1011
|
+
await writeFile(join(dir, 'medium-head.txt'), mediumHeadLines, 'utf8');
|
|
1012
|
+
const mediumHead = await executeBuiltinTool('read', { path: 'medium-head.txt', mode: 'head', n: 2 }, dir, { readStateScope: 'read-medium-head' });
|
|
1013
|
+
assertHas('medium head first', mediumHead, `1${LINE_SEP}head-1`);
|
|
1014
|
+
assertHas('medium head second', mediumHead, `2${LINE_SEP}head-2`);
|
|
1015
|
+
const mediumTail = await executeBuiltinTool('read', { path: 'medium-head.txt', mode: 'tail', n: 2 }, dir, { readStateScope: 'read-medium-tail' });
|
|
1016
|
+
assertHas('medium tail first', mediumTail, `99999${LINE_SEP}head-99999`);
|
|
1017
|
+
assertHas('medium tail second', mediumTail, `100000${LINE_SEP}head-100000`);
|
|
1018
|
+
const mediumCount = await executeBuiltinTool('read', { path: 'medium-head.txt', mode: 'count' }, dir, { readStateScope: 'read-medium-count' });
|
|
1019
|
+
assertHas('medium count lines', mediumCount, 'lines\t100000');
|
|
1020
|
+
assertHas('medium count words', mediumCount, 'words\t100000');
|
|
1021
|
+
const mediumDefault = await executeBuiltinTool('read', { path: 'medium-head.txt' }, dir, { readStateScope: 'read-medium-default' });
|
|
1022
|
+
assertHas('medium default first', mediumDefault, `1${LINE_SEP}head-1`);
|
|
1023
|
+
assertHas('medium default marker', mediumDefault, 'TRUNCATED');
|
|
1024
|
+
assertNotHas('medium default no narrow hint', mediumDefault, 'read({path,line,context})');
|
|
1025
|
+
assertHas('medium default tail', mediumDefault, `100000${LINE_SEP}head-100000`);
|
|
1026
|
+
if (String(mediumDefault).includes(`50000${LINE_SEP}head-50000`)) {
|
|
1027
|
+
throw new Error('medium default read streamed a middle line that should have been elided');
|
|
1028
|
+
}
|
|
1029
|
+
assertOk('medium default visible-tail edit', await executeBuiltinTool('edit', {
|
|
1030
|
+
path: 'medium-head.txt',
|
|
1031
|
+
old_string: 'head-99999',
|
|
1032
|
+
new_string: 'HEAD-99999',
|
|
1033
|
+
}, dir, { readStateScope: 'read-medium-default' }));
|
|
1034
|
+
const farBatch = await executeBuiltinTool('read', {
|
|
1035
|
+
path: [
|
|
1036
|
+
{ path: 'large.txt', offset: 10, limit: 2 },
|
|
1037
|
+
{ path: 'large.txt', offset: 10000, limit: 2 },
|
|
1038
|
+
],
|
|
1039
|
+
}, dir, { readStateScope: 'read-far-batch' });
|
|
1040
|
+
assertHas('far batch read head slice', farBatch, `11${LINE_SEP}line-11`);
|
|
1041
|
+
assertHas('far batch read tail slice', farBatch, `10001${LINE_SEP}line-10001`);
|
|
1042
|
+
const reverseFarBatch = await executeBuiltinTool('read', {
|
|
1043
|
+
path: [
|
|
1044
|
+
{ path: 'large.txt', offset: 10000, limit: 1 },
|
|
1045
|
+
{ path: 'large.txt', offset: 10, limit: 1 },
|
|
1046
|
+
],
|
|
1047
|
+
}, dir, { readStateScope: 'read-far-batch-reverse' });
|
|
1048
|
+
const reverseTailIdx = String(reverseFarBatch).indexOf(`10001${LINE_SEP}line-10001`);
|
|
1049
|
+
const reverseHeadIdx = String(reverseFarBatch).indexOf(`11${LINE_SEP}line-11`);
|
|
1050
|
+
if (reverseTailIdx === -1 || reverseHeadIdx === -1 || reverseTailIdx > reverseHeadIdx) {
|
|
1051
|
+
throw new Error(`far batch output order changed unexpectedly:\n${reverseFarBatch}`);
|
|
1052
|
+
}
|
|
1053
|
+
const byteCapRows = Array.from({ length: 1800 }, (_, i) => `bytecap-${i + 1}-${'x'.repeat(54)}`);
|
|
1054
|
+
byteCapRows[9] = `bytecap-repeat-${'x'.repeat(50)}`;
|
|
1055
|
+
byteCapRows[1699] = `bytecap-repeat-${'x'.repeat(50)}`;
|
|
1056
|
+
const byteCapLines = byteCapRows.join('\n') + '\n';
|
|
1057
|
+
await writeFile(join(dir, 'byte-cap-read.txt'), byteCapLines, 'utf8');
|
|
1058
|
+
const byteCapRead = await executeBuiltinTool('read', { path: 'byte-cap-read.txt', offset: 0, limit: 1800 }, dir, { readStateScope: 'read-byte-cap' });
|
|
1059
|
+
assertHas('byte-cap read truncation marker', byteCapRead, 'output truncated');
|
|
1060
|
+
const byteCapHiddenEdit = await executeBuiltinTool('edit', {
|
|
1061
|
+
path: 'byte-cap-read.txt',
|
|
1062
|
+
old_string: `bytecap-repeat-${'x'.repeat(50)}`,
|
|
1063
|
+
new_string: 'BYTECAP-HIDDEN',
|
|
1064
|
+
}, dir, { readStateScope: 'read-byte-cap' });
|
|
1065
|
+
assertError('byte-cap ambiguous hidden tail edit blocked by ambiguity gate (code 9, not read-window)', byteCapHiddenEdit, 'Error [code 9]');
|
|
1066
|
+
await expectFile('byte-cap-read.txt', byteCapLines);
|
|
1067
|
+
|
|
1068
|
+
await writeFile(join(dir, 'large.txt'), lines.replace('line-10001', 'changed-10001'), 'utf8');
|
|
1069
|
+
const invalidated = await executeBuiltinTool('read', { path: 'large.txt', offset: 10000, limit: 1 }, dir, { readStateScope: 'read-c' });
|
|
1070
|
+
assertHas('read cache invalidation', invalidated, `10001${LINE_SEP}changed-10001`);
|
|
1071
|
+
|
|
1072
|
+
console.log('mutation-io smoke passed');
|
|
1073
|
+
} finally {
|
|
1074
|
+
await rmTree(dir);
|
|
1075
|
+
await rmTree(DATA_DIR);
|
|
1076
|
+
}
|
|
1077
|
+
// Imported builtin/read-dedup/offload modules keep a background flush timer
|
|
1078
|
+
// alive, so without an explicit exit the process hangs after all checks pass
|
|
1079
|
+
// (same as guard-smoke / builtin-utils-smoke). A thrown assertion above exits
|
|
1080
|
+
// non-zero before reaching here.
|
|
1081
|
+
process.exit(0);
|