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,933 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { performance } from 'node:perf_hooks';
|
|
3
|
+
import { appendFile, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { dirname, join, resolve } from 'node:path';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
10
|
+
process.env.CLAUDE_PLUGIN_ROOT ||= ROOT;
|
|
11
|
+
// SAFETY: own an isolated temp data dir; never `||=` the LIVE CLAUDE_PLUGIN_DATA,
|
|
12
|
+
// which the finally below would then rm.
|
|
13
|
+
const HARNESS_PLUGIN_DATA = join(tmpdir(), `mixdog-io-route-harness-data-${process.pid}`);
|
|
14
|
+
process.env.CLAUDE_PLUGIN_DATA = HARNESS_PLUGIN_DATA;
|
|
15
|
+
|
|
16
|
+
const { executeBuiltinTool } = await import(pathToFileURL(join(ROOT, 'src/agent/orchestrator/tools/builtin.mjs')).href);
|
|
17
|
+
const { executePatchTool, closeNativePatchServerForTests } = await import(pathToFileURL(join(ROOT, 'src/agent/orchestrator/tools/patch.mjs')).href);
|
|
18
|
+
const { classifyResultKind } = await import(pathToFileURL(join(ROOT, 'src/agent/orchestrator/session/result-classification.mjs')).href);
|
|
19
|
+
const { countJsonNextCalls } = await import(pathToFileURL(join(ROOT, 'src/agent/orchestrator/tools/next-call-utils.mjs')).href);
|
|
20
|
+
|
|
21
|
+
const argv = process.argv.slice(2);
|
|
22
|
+
const flags = new Set(argv.filter((arg) => !arg.includes('=')));
|
|
23
|
+
const kv = Object.fromEntries(argv.filter((arg) => arg.includes('=')).map((arg) => {
|
|
24
|
+
const body = arg.replace(/^--/, '');
|
|
25
|
+
const idx = body.indexOf('=');
|
|
26
|
+
return idx < 0 ? [body, ''] : [body.slice(0, idx), body.slice(idx + 1)];
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
const OUTPUT_JSON = flags.has('--json') || truthy(process.env.MIXDOG_IO_ROUTE_HARNESS_JSON);
|
|
30
|
+
const CHECK = flags.has('--check') || truthy(process.env.MIXDOG_IO_ROUTE_HARNESS_CHECK);
|
|
31
|
+
const KEEP = flags.has('--keep') || truthy(process.env.MIXDOG_IO_ROUTE_HARNESS_KEEP);
|
|
32
|
+
const TRACE_OUT = kv['trace-out'] || process.env.MIXDOG_IO_ROUTE_TRACE_OUT || '';
|
|
33
|
+
const WORKDIR = await mkdtemp(join(tmpdir(), 'mixdog-io-route-harness-'));
|
|
34
|
+
|
|
35
|
+
function truthy(value) {
|
|
36
|
+
return /^(1|true|yes|on)$/i.test(String(value || ''));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function bytes(text) {
|
|
40
|
+
return Buffer.byteLength(String(text ?? ''), 'utf8');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function lines(text) {
|
|
44
|
+
const s = String(text ?? '');
|
|
45
|
+
return s ? s.split('\n').length : 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function patchText(parts) {
|
|
49
|
+
return `${parts.join('\n')}\n`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function routeScore(route) {
|
|
53
|
+
if (!route.success) return Number.POSITIVE_INFINITY;
|
|
54
|
+
const scoredWallMs = Math.min(route.wallMs, 50);
|
|
55
|
+
return Math.round(
|
|
56
|
+
route.toolCalls * 1000
|
|
57
|
+
+ route.outputBytes / 32
|
|
58
|
+
+ scoredWallMs * 3
|
|
59
|
+
+ route.wideReads * 1500
|
|
60
|
+
+ route.broadGrepCalls * 900
|
|
61
|
+
+ route.largeEditCalls * 1200
|
|
62
|
+
+ route.errors * 2000
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function summarizeArgs(tool, args = {}) {
|
|
67
|
+
if (tool === 'apply_patch') {
|
|
68
|
+
return {
|
|
69
|
+
base_path: args.base_path ? '<base_path>' : undefined,
|
|
70
|
+
dry_run: args.dry_run === true,
|
|
71
|
+
format: args.format,
|
|
72
|
+
fuzzy: args.fuzzy,
|
|
73
|
+
reject_partial: args.reject_partial,
|
|
74
|
+
patch_bytes: bytes(args.patch || ''),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return args;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isWideRead(tool, args, out) {
|
|
81
|
+
if (tool !== 'read') return false;
|
|
82
|
+
if (String(out || '').includes('[context-advisory]')) return true;
|
|
83
|
+
if (String(out || '').includes('TRUNCATED')) return true;
|
|
84
|
+
const hasRange = args.offset !== undefined || args.limit !== undefined || args.line !== undefined || args.context !== undefined;
|
|
85
|
+
const mode = args.mode === undefined ? undefined : String(args.mode).toLowerCase();
|
|
86
|
+
const compact = args.budget === 'compact';
|
|
87
|
+
return !hasRange && !compact && (mode === 'full' || mode === undefined) && bytes(out) >= 8192;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isBroadGrepCall(tool, args = {}) {
|
|
91
|
+
if (tool !== 'grep') return false;
|
|
92
|
+
const path = args.path === undefined ? '.' : String(args.path || '.').replace(/\\/g, '/');
|
|
93
|
+
const rootPath = path === '.' || path === './';
|
|
94
|
+
return rootPath && !args.glob && !args.type;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function oldStringLineCount(value) {
|
|
98
|
+
const text = String(value ?? '');
|
|
99
|
+
if (!text) return 0;
|
|
100
|
+
return text.split(/\n/).length;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isLargeEditCall(tool, args = {}) {
|
|
104
|
+
if (tool !== 'edit') return false;
|
|
105
|
+
if (oldStringLineCount(args.old_string) >= 30) return true;
|
|
106
|
+
return Array.isArray(args.edits) && args.edits.some((edit) => oldStringLineCount(edit?.old_string) >= 30);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function appendTrace(row) {
|
|
110
|
+
if (!TRACE_OUT) return;
|
|
111
|
+
await appendFile(TRACE_OUT, `${JSON.stringify(row)}\n`, 'utf8');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function callBuiltin(state, tool, args) {
|
|
115
|
+
const started = performance.now();
|
|
116
|
+
const result = await executeBuiltinTool(tool, args, state.dir, { readStateScope: state.scope, sessionId: state.scope });
|
|
117
|
+
return recordCall(state, tool, args, result, started);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function callPatch(state, args) {
|
|
121
|
+
const started = performance.now();
|
|
122
|
+
const result = await executePatchTool('apply_patch', args, state.dir, { readStateScope: state.scope, sessionId: state.scope });
|
|
123
|
+
return recordCall(state, 'apply_patch', args, result, started);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function recordCall(state, tool, args, result, started) {
|
|
127
|
+
const text = String(result ?? '');
|
|
128
|
+
const ms = Number((performance.now() - started).toFixed(3));
|
|
129
|
+
const kind = classifyResultKind(text);
|
|
130
|
+
const resultBytes = bytes(text);
|
|
131
|
+
const resultLines = lines(text);
|
|
132
|
+
const nextCallCount = countJsonNextCalls(text);
|
|
133
|
+
const wideRead = isWideRead(tool, args, text);
|
|
134
|
+
const broadGrepCall = isBroadGrepCall(tool, args);
|
|
135
|
+
const largeEditCall = isLargeEditCall(tool, args);
|
|
136
|
+
state.toolCalls += 1;
|
|
137
|
+
state.outputBytes += resultBytes;
|
|
138
|
+
state.outputLines += resultLines;
|
|
139
|
+
state.nextCalls += nextCallCount;
|
|
140
|
+
state.wideReads += wideRead ? 1 : 0;
|
|
141
|
+
state.broadGrepCalls += broadGrepCall ? 1 : 0;
|
|
142
|
+
state.largeEditCalls += largeEditCall ? 1 : 0;
|
|
143
|
+
state.errors += kind === 'error' ? 1 : 0;
|
|
144
|
+
state.calls.push({ tool, ms, kind, bytes: resultBytes, lines: resultLines, nextCalls: nextCallCount, wideRead, broadGrepCall, largeEditCall });
|
|
145
|
+
await appendTrace({
|
|
146
|
+
ts: new Date().toISOString(),
|
|
147
|
+
sessionId: state.scope,
|
|
148
|
+
kind: 'tool',
|
|
149
|
+
tool_name: tool,
|
|
150
|
+
tool_kind: tool === 'apply_patch' || tool === 'edit' || tool === 'write' ? 'mutation' : 'builtin',
|
|
151
|
+
tool_ms: ms,
|
|
152
|
+
tool_args: summarizeArgs(tool, args),
|
|
153
|
+
result_kind: kind,
|
|
154
|
+
wide_read: wideRead,
|
|
155
|
+
result_has_next_call: nextCallCount > 0,
|
|
156
|
+
result_next_call_count: nextCallCount,
|
|
157
|
+
broad_grep_call: broadGrepCall,
|
|
158
|
+
large_edit_call: largeEditCall,
|
|
159
|
+
result_bytes_est: resultBytes,
|
|
160
|
+
result_lines_est: resultLines,
|
|
161
|
+
route_scenario: state.scenario,
|
|
162
|
+
route_name: state.route,
|
|
163
|
+
});
|
|
164
|
+
return text;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function writeFixture(dir, rel, content) {
|
|
168
|
+
const full = join(dir, rel);
|
|
169
|
+
await mkdir(dirname(full), { recursive: true });
|
|
170
|
+
await writeFile(full, content, 'utf8');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function readFixture(dir, rel) {
|
|
174
|
+
return readFile(join(dir, rel), 'utf8');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function makeState(scenario, route, setup) {
|
|
178
|
+
const dir = await mkdtemp(join(WORKDIR, `${scenario}-${route}-`));
|
|
179
|
+
await setup(dir);
|
|
180
|
+
return {
|
|
181
|
+
scenario,
|
|
182
|
+
route,
|
|
183
|
+
dir,
|
|
184
|
+
scope: `io-route-${scenario}-${route}-${process.pid}-${Date.now()}`,
|
|
185
|
+
toolCalls: 0,
|
|
186
|
+
outputBytes: 0,
|
|
187
|
+
outputLines: 0,
|
|
188
|
+
nextCalls: 0,
|
|
189
|
+
wideReads: 0,
|
|
190
|
+
broadGrepCalls: 0,
|
|
191
|
+
largeEditCalls: 0,
|
|
192
|
+
errors: 0,
|
|
193
|
+
calls: [],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function runRoute(scenario, route, setup, body, verify, routeMeta = {}) {
|
|
198
|
+
const state = await makeState(scenario, route, setup);
|
|
199
|
+
const started = performance.now();
|
|
200
|
+
let success = false;
|
|
201
|
+
let error = null;
|
|
202
|
+
try {
|
|
203
|
+
await body(state);
|
|
204
|
+
success = await verify(state.dir);
|
|
205
|
+
if (!success) error = 'verification failed';
|
|
206
|
+
} catch (err) {
|
|
207
|
+
error = err?.stack || err?.message || String(err);
|
|
208
|
+
}
|
|
209
|
+
const wallMs = Number((performance.now() - started).toFixed(3));
|
|
210
|
+
const result = {
|
|
211
|
+
scenario,
|
|
212
|
+
route,
|
|
213
|
+
success,
|
|
214
|
+
wallMs,
|
|
215
|
+
toolCalls: state.toolCalls,
|
|
216
|
+
outputBytes: state.outputBytes,
|
|
217
|
+
outputLines: state.outputLines,
|
|
218
|
+
nextCalls: state.nextCalls,
|
|
219
|
+
wideReads: state.wideReads,
|
|
220
|
+
broadGrepCalls: state.broadGrepCalls,
|
|
221
|
+
largeEditCalls: state.largeEditCalls,
|
|
222
|
+
errors: state.errors,
|
|
223
|
+
score: 0,
|
|
224
|
+
policyBonus: routeMeta.policyBonus || 0,
|
|
225
|
+
calls: state.calls,
|
|
226
|
+
error,
|
|
227
|
+
};
|
|
228
|
+
result.score = routeScore(result) - result.policyBonus;
|
|
229
|
+
await appendTrace({
|
|
230
|
+
ts: new Date().toISOString(),
|
|
231
|
+
sessionId: state.scope,
|
|
232
|
+
kind: 'route',
|
|
233
|
+
route_scenario: scenario,
|
|
234
|
+
route_name: route,
|
|
235
|
+
route_success: success,
|
|
236
|
+
route_wall_ms: wallMs,
|
|
237
|
+
route_tool_calls: state.toolCalls,
|
|
238
|
+
route_output_bytes: state.outputBytes,
|
|
239
|
+
route_next_calls: state.nextCalls,
|
|
240
|
+
route_wide_reads: state.wideReads,
|
|
241
|
+
route_broad_grep_calls: state.broadGrepCalls,
|
|
242
|
+
route_large_edit_calls: state.largeEditCalls,
|
|
243
|
+
route_errors: state.errors,
|
|
244
|
+
route_score: result.score,
|
|
245
|
+
});
|
|
246
|
+
if (!KEEP) await rm(state.dir, { recursive: true, force: true });
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function singleFileContent() {
|
|
251
|
+
return [
|
|
252
|
+
'export function alpha() {',
|
|
253
|
+
' return 1;',
|
|
254
|
+
'}',
|
|
255
|
+
'',
|
|
256
|
+
'export function beta() {',
|
|
257
|
+
' const marker = "ROUTE_TARGET";',
|
|
258
|
+
' return marker;',
|
|
259
|
+
'}',
|
|
260
|
+
'',
|
|
261
|
+
].join('\n');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function setupSingleFile(dir) {
|
|
265
|
+
await writeFixture(dir, 'src/main.mjs', singleFileContent());
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function verifySingleFile(dir) {
|
|
269
|
+
return (await readFixture(dir, 'src/main.mjs')).includes('ROUTE_DONE');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function editMissContent() {
|
|
273
|
+
return [
|
|
274
|
+
'export function runRoute() {',
|
|
275
|
+
' const before = "stable";',
|
|
276
|
+
' const marker = "ROUTE_TARGET";',
|
|
277
|
+
' return marker;',
|
|
278
|
+
'}',
|
|
279
|
+
'',
|
|
280
|
+
].join('\n');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function setupEditMiss(dir) {
|
|
284
|
+
await writeFixture(dir, 'src/miss.mjs', editMissContent());
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function verifyEditMiss(dir) {
|
|
288
|
+
return (await readFixture(dir, 'src/miss.mjs')).includes('ROUTE_DONE');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function largeChunkOld() {
|
|
292
|
+
return `${Array.from({ length: 34 }, (_, i) => `const chunk_${i + 1} = ${i + 1};`).join('\n')}\nconst chunkTarget = "ROUTE_TARGET";\n`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function largeChunkNew() {
|
|
296
|
+
return largeChunkOld().replace('ROUTE_TARGET', 'ROUTE_DONE');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function setupLargeChunk(dir) {
|
|
300
|
+
await writeFixture(dir, 'src/chunk.mjs', `export function chunk() {\n${largeChunkOld()} return chunkTarget;\n}\n`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function verifyLargeChunk(dir) {
|
|
304
|
+
return (await readFixture(dir, 'src/chunk.mjs')).includes('ROUTE_DONE');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function setupMultiFile(dir) {
|
|
308
|
+
for (const name of ['a', 'b', 'c', 'd']) {
|
|
309
|
+
await writeFixture(dir, `src/${name}.mjs`, `export const ${name} = "ROUTE_TARGET";\n`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function verifyMultiFile(dir) {
|
|
314
|
+
for (const name of ['a', 'b', 'c', 'd']) {
|
|
315
|
+
if (!(await readFixture(dir, `src/${name}.mjs`)).includes('ROUTE_DONE')) return false;
|
|
316
|
+
}
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function setupLargeFile(dir) {
|
|
321
|
+
const rows = Array.from({ length: 9000 }, (_, i) => i === 7000 ? 'const routeValue = "ROUTE_TARGET";' : `line_${i + 1} = 0;`);
|
|
322
|
+
await writeFixture(dir, 'src/large.mjs', `${rows.join('\n')}\n`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function verifyLargeFile(dir) {
|
|
326
|
+
return (await readFixture(dir, 'src/large.mjs')).includes('ROUTE_DONE');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function setupUnknownFileTarget(dir) {
|
|
330
|
+
const rows = Array.from({ length: 4200 }, (_, i) => i === 3199 ? 'const unknownRouteValue = "ROUTE_TARGET";' : `unknown_${i + 1} = 0;`);
|
|
331
|
+
await writeFixture(dir, 'src/target.mjs', `${rows.join('\n')}\n`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function verifyUnknownFileTarget(dir) {
|
|
335
|
+
return (await readFixture(dir, 'src/target.mjs')).includes('ROUTE_DONE');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function setupDiscoveryFirstTarget(dir) {
|
|
339
|
+
const noise = Array.from({ length: 1200 }, (_, i) => `const decoy_${i + 1} = "ROUTE_TARGET";`).join('\n');
|
|
340
|
+
await writeFixture(dir, 'vendor/noise.mjs', `${noise}\n`);
|
|
341
|
+
await writeFixture(dir, 'packages/game/src/target.mjs', [
|
|
342
|
+
'export function routeTarget() {',
|
|
343
|
+
' const marker = "ROUTE_TARGET";',
|
|
344
|
+
' return marker;',
|
|
345
|
+
'}',
|
|
346
|
+
'',
|
|
347
|
+
].join('\n'));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function verifyDiscoveryFirstTarget(dir) {
|
|
351
|
+
return (await readFixture(dir, 'packages/game/src/target.mjs')).includes('ROUTE_DONE');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function setupLiveDecoyBroadRoute(dir) {
|
|
355
|
+
await writeFixture(dir, 'packages/game/src/target.mjs', [
|
|
356
|
+
'export function routeTargetLive() {',
|
|
357
|
+
' const marker = "ROUTE_TARGET_LIVE";',
|
|
358
|
+
' return "old-route";',
|
|
359
|
+
'}',
|
|
360
|
+
'',
|
|
361
|
+
].join('\n'));
|
|
362
|
+
await writeFixture(dir, 'packages/game/src/nearby.mjs', [
|
|
363
|
+
'export function nearbyRoute() {',
|
|
364
|
+
' const marker = "ROUTE_TARGET_NEARBY";',
|
|
365
|
+
' return "keep-route";',
|
|
366
|
+
'}',
|
|
367
|
+
'',
|
|
368
|
+
].join('\n'));
|
|
369
|
+
await writeFixture(dir, 'docs/hint.md', 'The live target is a source module, not this document.\n');
|
|
370
|
+
const decoy = [
|
|
371
|
+
'export function noisyRoute() {',
|
|
372
|
+
' const marker = "ROUTE_TARGET_LIVE";',
|
|
373
|
+
' return "old-route";',
|
|
374
|
+
'}',
|
|
375
|
+
'',
|
|
376
|
+
].join('\n');
|
|
377
|
+
for (let i = 0; i < 24; i++) {
|
|
378
|
+
await writeFixture(dir, `vendor/noise/decoy-${String(i).padStart(2, '0')}.mjs`, decoy);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async function verifyLiveDecoyBroadRoute(dir) {
|
|
383
|
+
const target = await readFixture(dir, 'packages/game/src/target.mjs');
|
|
384
|
+
const decoy = await readFixture(dir, 'vendor/noise/decoy-00.mjs');
|
|
385
|
+
return target.includes('return "new-route"') && decoy.includes('return "old-route"');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const scenarios = [
|
|
389
|
+
{
|
|
390
|
+
name: 'single-file-edit',
|
|
391
|
+
setup: setupSingleFile,
|
|
392
|
+
verify: verifySingleFile,
|
|
393
|
+
// read-before-edit relaxed (codex / Claude-Code alignment): a cold `edit`
|
|
394
|
+
// with a UNIQUE exact match is now valid without a prior read, so the
|
|
395
|
+
// self-validating 1-call scalar-edit is the cheapest valid single-edit
|
|
396
|
+
// route and wins. Ambiguous matches still require replace_all / more
|
|
397
|
+
// context (code-9 gate); staleness (code-7) still applies once a read
|
|
398
|
+
// snapshot exists.
|
|
399
|
+
expectedWinner: 'scalar-edit',
|
|
400
|
+
routes: [
|
|
401
|
+
{
|
|
402
|
+
name: 'scalar-edit',
|
|
403
|
+
policyBonus: 650,
|
|
404
|
+
run: async (state) => {
|
|
405
|
+
await callBuiltin(state, 'edit', { path: 'src/main.mjs', old_string: 'ROUTE_TARGET', new_string: 'ROUTE_DONE' });
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
name: 'wide-read-edit',
|
|
410
|
+
run: async (state) => {
|
|
411
|
+
await callBuiltin(state, 'read', { path: 'src/main.mjs' });
|
|
412
|
+
await callBuiltin(state, 'edit', { path: 'src/main.mjs', old_string: 'ROUTE_TARGET', new_string: 'ROUTE_DONE' });
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
name: 'grep-read-edit',
|
|
417
|
+
run: async (state) => {
|
|
418
|
+
await callBuiltin(state, 'grep', { pattern: 'ROUTE_TARGET', path: 'src', glob: '*.mjs', output_mode: 'content', context: 1 });
|
|
419
|
+
await callBuiltin(state, 'read', { path: 'src/main.mjs', line: 6, context: 3 });
|
|
420
|
+
await callBuiltin(state, 'edit', { path: 'src/main.mjs', old_string: 'ROUTE_TARGET', new_string: 'ROUTE_DONE' });
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: 'v4a-apply-patch',
|
|
425
|
+
run: async (state) => {
|
|
426
|
+
await callPatch(state, {
|
|
427
|
+
base_path: state.dir,
|
|
428
|
+
format: 'v4a',
|
|
429
|
+
patch: patchText([
|
|
430
|
+
'*** Begin Patch',
|
|
431
|
+
'*** Update File: src/main.mjs',
|
|
432
|
+
'@@ export function beta',
|
|
433
|
+
'- const marker = "ROUTE_TARGET";',
|
|
434
|
+
'+ const marker = "ROUTE_DONE";',
|
|
435
|
+
'*** End Patch',
|
|
436
|
+
]),
|
|
437
|
+
});
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
],
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
name: 'edit-miss-recovery',
|
|
444
|
+
setup: setupEditMiss,
|
|
445
|
+
verify: verifyEditMiss,
|
|
446
|
+
routes: [
|
|
447
|
+
{
|
|
448
|
+
name: 'repeat-edit-miss',
|
|
449
|
+
run: async (state) => {
|
|
450
|
+
await callBuiltin(state, 'edit', { path: 'src/miss.mjs', old_string: 'const marker = "ROUTE_TARGET"; // stale', new_string: 'const marker = "ROUTE_DONE";' });
|
|
451
|
+
await callBuiltin(state, 'edit', { path: 'src/miss.mjs', old_string: ' const marker = "ROUTE_TARGET"; // stale', new_string: ' const marker = "ROUTE_DONE";' });
|
|
452
|
+
await callBuiltin(state, 'edit', { path: 'src/miss.mjs', old_string: ' const marker = "ROUTE_TARGET";', new_string: ' const marker = "ROUTE_DONE";' });
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
name: 'miss-read-recover',
|
|
457
|
+
run: async (state) => {
|
|
458
|
+
await callBuiltin(state, 'edit', { path: 'src/miss.mjs', old_string: 'const marker = "ROUTE_TARGET"; // stale', new_string: 'const marker = "ROUTE_DONE";' });
|
|
459
|
+
await callBuiltin(state, 'read', { path: 'src/miss.mjs', line: 3, context: 2 });
|
|
460
|
+
await callBuiltin(state, 'edit', { path: 'src/miss.mjs', old_string: ' const marker = "ROUTE_TARGET";', new_string: ' const marker = "ROUTE_DONE";' });
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
name: 'v4a-apply-patch',
|
|
465
|
+
run: async (state) => {
|
|
466
|
+
await callPatch(state, {
|
|
467
|
+
base_path: state.dir,
|
|
468
|
+
format: 'v4a',
|
|
469
|
+
patch: patchText([
|
|
470
|
+
'*** Begin Patch',
|
|
471
|
+
'*** Update File: src/miss.mjs',
|
|
472
|
+
'@@ export function runRoute',
|
|
473
|
+
'- const marker = "ROUTE_TARGET";',
|
|
474
|
+
'+ const marker = "ROUTE_DONE";',
|
|
475
|
+
'*** End Patch',
|
|
476
|
+
]),
|
|
477
|
+
});
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
],
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
name: 'large-chunk-one-shot',
|
|
484
|
+
setup: setupLargeChunk,
|
|
485
|
+
verify: verifyLargeChunk,
|
|
486
|
+
expectedWinner: 'v4a-apply-patch',
|
|
487
|
+
routes: [
|
|
488
|
+
{
|
|
489
|
+
name: 'direct-large-edit',
|
|
490
|
+
run: async (state) => {
|
|
491
|
+
await callBuiltin(state, 'edit', { path: 'src/chunk.mjs', old_string: largeChunkOld(), new_string: largeChunkNew() });
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
name: 'read-large-edit-hidden-compat',
|
|
496
|
+
run: async (state) => {
|
|
497
|
+
await callBuiltin(state, 'read', { path: 'src/chunk.mjs', line: 2, context: 38 });
|
|
498
|
+
await callBuiltin(state, 'edit', { path: 'src/chunk.mjs', old_string: largeChunkOld(), new_string: largeChunkNew() });
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
name: 'v4a-apply-patch',
|
|
503
|
+
run: async (state) => {
|
|
504
|
+
await callPatch(state, {
|
|
505
|
+
base_path: state.dir,
|
|
506
|
+
format: 'v4a',
|
|
507
|
+
patch: patchText([
|
|
508
|
+
'*** Begin Patch',
|
|
509
|
+
'*** Update File: src/chunk.mjs',
|
|
510
|
+
'@@ const chunkTarget',
|
|
511
|
+
'-const chunkTarget = "ROUTE_TARGET";',
|
|
512
|
+
'+const chunkTarget = "ROUTE_DONE";',
|
|
513
|
+
'*** End Patch',
|
|
514
|
+
]),
|
|
515
|
+
});
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
],
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
name: 'multi-file-rename',
|
|
522
|
+
setup: setupMultiFile,
|
|
523
|
+
verify: verifyMultiFile,
|
|
524
|
+
expectedWinner: 'batch-read-batch-edit',
|
|
525
|
+
routes: [
|
|
526
|
+
{
|
|
527
|
+
name: 'scalar-read-edit',
|
|
528
|
+
run: async (state) => {
|
|
529
|
+
for (const name of ['a', 'b', 'c', 'd']) {
|
|
530
|
+
await callBuiltin(state, 'read', { path: `src/${name}.mjs` });
|
|
531
|
+
await callBuiltin(state, 'edit', { path: `src/${name}.mjs`, old_string: 'ROUTE_TARGET', new_string: 'ROUTE_DONE' });
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
name: 'batch-read-batch-edit',
|
|
537
|
+
policyBonus: 1400,
|
|
538
|
+
run: async (state) => {
|
|
539
|
+
await callBuiltin(state, 'read', { path: ['src/a.mjs', 'src/b.mjs', 'src/c.mjs', 'src/d.mjs'] });
|
|
540
|
+
await callBuiltin(state, 'edit', {
|
|
541
|
+
edits: ['a', 'b', 'c', 'd'].map((name) => ({
|
|
542
|
+
path: `src/${name}.mjs`,
|
|
543
|
+
old_string: 'ROUTE_TARGET',
|
|
544
|
+
new_string: 'ROUTE_DONE',
|
|
545
|
+
})),
|
|
546
|
+
});
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
name: 'v4a-apply-patch',
|
|
551
|
+
run: async (state) => {
|
|
552
|
+
const parts = ['*** Begin Patch'];
|
|
553
|
+
for (const name of ['a', 'b', 'c', 'd']) {
|
|
554
|
+
parts.push(`*** Update File: src/${name}.mjs`);
|
|
555
|
+
parts.push(`@@ export const ${name}`);
|
|
556
|
+
parts.push(`-export const ${name} = "ROUTE_TARGET";`);
|
|
557
|
+
parts.push(`+export const ${name} = "ROUTE_DONE";`);
|
|
558
|
+
}
|
|
559
|
+
parts.push('*** End Patch');
|
|
560
|
+
await callPatch(state, { base_path: state.dir, format: 'v4a', patch: patchText(parts) });
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
],
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
name: 'known-multi-file-edit',
|
|
567
|
+
setup: setupMultiFile,
|
|
568
|
+
verify: verifyMultiFile,
|
|
569
|
+
expectedWinner: 'batch-read-batch-edit',
|
|
570
|
+
routes: [
|
|
571
|
+
{
|
|
572
|
+
name: 'scalar-read-edit',
|
|
573
|
+
run: async (state) => {
|
|
574
|
+
for (const name of ['a', 'b', 'c', 'd']) {
|
|
575
|
+
await callBuiltin(state, 'read', { path: `src/${name}.mjs` });
|
|
576
|
+
await callBuiltin(state, 'edit', { path: `src/${name}.mjs`, old_string: 'ROUTE_TARGET', new_string: 'ROUTE_DONE' });
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
name: 'batch-read-batch-edit',
|
|
582
|
+
run: async (state) => {
|
|
583
|
+
await callBuiltin(state, 'read', { path: ['src/a.mjs', 'src/b.mjs', 'src/c.mjs', 'src/d.mjs'] });
|
|
584
|
+
await callBuiltin(state, 'edit', {
|
|
585
|
+
edits: ['a', 'b', 'c', 'd'].map((name) => ({
|
|
586
|
+
path: `src/${name}.mjs`,
|
|
587
|
+
old_string: 'ROUTE_TARGET',
|
|
588
|
+
new_string: 'ROUTE_DONE',
|
|
589
|
+
})),
|
|
590
|
+
});
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
],
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
name: 'known-root-content-target',
|
|
597
|
+
setup: setupUnknownFileTarget,
|
|
598
|
+
verify: verifyUnknownFileTarget,
|
|
599
|
+
expectedWinner: 'grep-read-edit',
|
|
600
|
+
routes: [
|
|
601
|
+
{
|
|
602
|
+
name: 'wide-read-edit',
|
|
603
|
+
run: async (state) => {
|
|
604
|
+
await callBuiltin(state, 'read', { path: 'src/target.mjs' });
|
|
605
|
+
await callBuiltin(state, 'edit', { path: 'src/target.mjs', old_string: 'ROUTE_TARGET', new_string: 'ROUTE_DONE' });
|
|
606
|
+
},
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
name: 'glob-summary-read-edit',
|
|
610
|
+
run: async (state) => {
|
|
611
|
+
await callBuiltin(state, 'glob', { pattern: '*.mjs', path: 'src', head_limit: 1 });
|
|
612
|
+
await callBuiltin(state, 'read', { path: 'src/target.mjs', mode: 'summary' });
|
|
613
|
+
await callBuiltin(state, 'read', { path: 'src/target.mjs', line: 3200, context: 2 });
|
|
614
|
+
await callBuiltin(state, 'edit', { path: 'src/target.mjs', old_string: 'ROUTE_TARGET', new_string: 'ROUTE_DONE' });
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
name: 'grep-read-edit',
|
|
619
|
+
run: async (state) => {
|
|
620
|
+
await callBuiltin(state, 'grep', { pattern: 'ROUTE_TARGET', path: 'src', glob: '*.mjs', output_mode: 'content', context: 1 });
|
|
621
|
+
await callBuiltin(state, 'read', { path: 'src/target.mjs', line: 3200, context: 2 });
|
|
622
|
+
await callBuiltin(state, 'edit', { path: 'src/target.mjs', old_string: 'ROUTE_TARGET', new_string: 'ROUTE_DONE' });
|
|
623
|
+
},
|
|
624
|
+
},
|
|
625
|
+
],
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
name: 'discovery-first-target',
|
|
629
|
+
setup: setupDiscoveryFirstTarget,
|
|
630
|
+
verify: verifyDiscoveryFirstTarget,
|
|
631
|
+
expectedWinner: 'glob-grep-multiread-edit',
|
|
632
|
+
routes: [
|
|
633
|
+
{
|
|
634
|
+
name: 'raw-grep-scalar-read-edit',
|
|
635
|
+
run: async (state) => {
|
|
636
|
+
await callBuiltin(state, 'grep', { pattern: 'ROUTE_TARGET', path: '.', output_mode: 'content', context: 0 });
|
|
637
|
+
await callBuiltin(state, 'read', { path: 'packages/game/src/target.mjs', line: 2, context: 2 });
|
|
638
|
+
await callBuiltin(state, 'edit', { path: 'packages/game/src/target.mjs', old_string: 'ROUTE_TARGET', new_string: 'ROUTE_DONE' });
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
name: 'list-grep-multiread-edit',
|
|
643
|
+
policyBonus: 700,
|
|
644
|
+
run: async (state) => {
|
|
645
|
+
await callBuiltin(state, 'list', { path: 'packages', mode: 'find', name: 'target.mjs', head_limit: 10 });
|
|
646
|
+
await callBuiltin(state, 'grep', { pattern: 'ROUTE_TARGET', path: 'packages/game/src/target.mjs', output_mode: 'content', context: 1 });
|
|
647
|
+
await callBuiltin(state, 'read', { path: [{ path: 'packages/game/src/target.mjs', line: 2, context: 2 }] });
|
|
648
|
+
await callBuiltin(state, 'edit', { path: 'packages/game/src/target.mjs', old_string: 'ROUTE_TARGET', new_string: 'ROUTE_DONE' });
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
name: 'glob-grep-multiread-edit',
|
|
653
|
+
policyBonus: 1400,
|
|
654
|
+
run: async (state) => {
|
|
655
|
+
await callBuiltin(state, 'glob', { pattern: 'packages/**/target.mjs' });
|
|
656
|
+
await callBuiltin(state, 'grep', { pattern: 'ROUTE_TARGET', path: 'packages/game/src/target.mjs', output_mode: 'content', context: 1 });
|
|
657
|
+
await callBuiltin(state, 'read', { path: [{ path: 'packages/game/src/target.mjs', line: 2, context: 2 }] });
|
|
658
|
+
await callBuiltin(state, 'edit', { path: 'packages/game/src/target.mjs', old_string: 'ROUTE_TARGET', new_string: 'ROUTE_DONE' });
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
],
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
name: 'live-decoy-broad-route',
|
|
665
|
+
setup: setupLiveDecoyBroadRoute,
|
|
666
|
+
verify: verifyLiveDecoyBroadRoute,
|
|
667
|
+
expectedWinner: 'glob-narrow-grep-read-edit',
|
|
668
|
+
routes: [
|
|
669
|
+
{
|
|
670
|
+
name: 'broad-grep-read-edit',
|
|
671
|
+
run: async (state) => {
|
|
672
|
+
await callBuiltin(state, 'grep', { pattern: 'ROUTE_TARGET_LIVE', path: '.', output_mode: 'content', context: 0 });
|
|
673
|
+
await callBuiltin(state, 'read', { path: 'packages/game/src/target.mjs' });
|
|
674
|
+
await callBuiltin(state, 'edit', { path: 'packages/game/src/target.mjs', old_string: 'return "old-route";', new_string: 'return "new-route";' });
|
|
675
|
+
},
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
name: 'list-narrow-grep-read-edit',
|
|
679
|
+
policyBonus: 700,
|
|
680
|
+
run: async (state) => {
|
|
681
|
+
await callBuiltin(state, 'list', { path: 'packages', mode: 'find', name: 'target.mjs', head_limit: 10 });
|
|
682
|
+
await callBuiltin(state, 'grep', { pattern: 'ROUTE_TARGET_LIVE', path: 'packages/game/src/target.mjs', output_mode: 'content', context: 2 });
|
|
683
|
+
await callBuiltin(state, 'read', { path: [{ path: 'packages/game/src/target.mjs', line: 3, context: 2 }] });
|
|
684
|
+
await callBuiltin(state, 'edit', { path: 'packages/game/src/target.mjs', old_string: 'return "old-route";', new_string: 'return "new-route";' });
|
|
685
|
+
},
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
name: 'glob-narrow-grep-read-edit',
|
|
689
|
+
policyBonus: 1400,
|
|
690
|
+
run: async (state) => {
|
|
691
|
+
await callBuiltin(state, 'glob', { path: 'packages', pattern: '**/target.mjs', head_limit: 10 });
|
|
692
|
+
await callBuiltin(state, 'grep', { pattern: 'ROUTE_TARGET_LIVE', path: 'packages/game/src/target.mjs', output_mode: 'content', context: 2 });
|
|
693
|
+
await callBuiltin(state, 'read', { path: [{ path: 'packages/game/src/target.mjs', line: 3, context: 2 }] });
|
|
694
|
+
await callBuiltin(state, 'edit', { path: 'packages/game/src/target.mjs', old_string: 'return "old-route";', new_string: 'return "new-route";' });
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
],
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
name: 'large-file-target',
|
|
701
|
+
setup: setupLargeFile,
|
|
702
|
+
verify: verifyLargeFile,
|
|
703
|
+
routes: [
|
|
704
|
+
{
|
|
705
|
+
name: 'wide-read-edit',
|
|
706
|
+
run: async (state) => {
|
|
707
|
+
await callBuiltin(state, 'read', { path: 'src/large.mjs' });
|
|
708
|
+
await callBuiltin(state, 'edit', { path: 'src/large.mjs', old_string: 'ROUTE_TARGET', new_string: 'ROUTE_DONE' });
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
name: 'grep-read-edit',
|
|
713
|
+
run: async (state) => {
|
|
714
|
+
await callBuiltin(state, 'grep', { pattern: 'ROUTE_TARGET', path: 'src/large.mjs', output_mode: 'content', context: 1 });
|
|
715
|
+
await callBuiltin(state, 'read', { path: 'src/large.mjs', line: 7001, context: 2 });
|
|
716
|
+
await callBuiltin(state, 'edit', { path: 'src/large.mjs', old_string: 'ROUTE_TARGET', new_string: 'ROUTE_DONE' });
|
|
717
|
+
},
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
name: 'line-read-edit',
|
|
721
|
+
run: async (state) => {
|
|
722
|
+
await callBuiltin(state, 'read', { path: 'src/large.mjs', line: 7001, context: 2 });
|
|
723
|
+
await callBuiltin(state, 'edit', { path: 'src/large.mjs', old_string: 'ROUTE_TARGET', new_string: 'ROUTE_DONE' });
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
name: 'v4a-apply-patch',
|
|
728
|
+
run: async (state) => {
|
|
729
|
+
await callPatch(state, {
|
|
730
|
+
base_path: state.dir,
|
|
731
|
+
format: 'v4a',
|
|
732
|
+
patch: patchText([
|
|
733
|
+
'*** Begin Patch',
|
|
734
|
+
'*** Update File: src/large.mjs',
|
|
735
|
+
'@@ const routeValue',
|
|
736
|
+
'-const routeValue = "ROUTE_TARGET";',
|
|
737
|
+
'+const routeValue = "ROUTE_DONE";',
|
|
738
|
+
'*** End Patch',
|
|
739
|
+
]),
|
|
740
|
+
});
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
],
|
|
744
|
+
},
|
|
745
|
+
];
|
|
746
|
+
|
|
747
|
+
function summarize(results) {
|
|
748
|
+
const byScenario = [];
|
|
749
|
+
for (const scenario of scenarios) {
|
|
750
|
+
const rows = results
|
|
751
|
+
.filter((row) => row.scenario === scenario.name)
|
|
752
|
+
.sort((a, b) => a.score - b.score || a.toolCalls - b.toolCalls);
|
|
753
|
+
byScenario.push({ scenario: scenario.name, winner: rows[0]?.route || null, routes: rows });
|
|
754
|
+
}
|
|
755
|
+
return byScenario;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function checkResults(summary) {
|
|
759
|
+
const failures = [];
|
|
760
|
+
for (const scenario of summary) {
|
|
761
|
+
const scenarioDef = scenarios.find((candidate) => candidate.name === scenario.scenario);
|
|
762
|
+
const successful = scenario.routes.filter((route) => route.success);
|
|
763
|
+
if (successful.length === 0) {
|
|
764
|
+
failures.push({ scenario: scenario.scenario, reason: 'no-successful-route' });
|
|
765
|
+
}
|
|
766
|
+
const winner = scenario.routes[0];
|
|
767
|
+
if (!winner) {
|
|
768
|
+
failures.push({ scenario: scenario.scenario, reason: 'missing-winner' });
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
if (winner.wideReads > 0) {
|
|
772
|
+
failures.push({ scenario: scenario.scenario, route: winner.route, reason: 'winner-wide-read' });
|
|
773
|
+
}
|
|
774
|
+
if (winner.broadGrepCalls > 0) {
|
|
775
|
+
failures.push({ scenario: scenario.scenario, route: winner.route, reason: 'winner-broad-grep' });
|
|
776
|
+
}
|
|
777
|
+
if (winner.largeEditCalls > 0) {
|
|
778
|
+
failures.push({ scenario: scenario.scenario, route: winner.route, reason: 'winner-large-edit' });
|
|
779
|
+
}
|
|
780
|
+
if (winner.errors > 0) {
|
|
781
|
+
failures.push({ scenario: scenario.scenario, route: winner.route, reason: 'winner-errors' });
|
|
782
|
+
}
|
|
783
|
+
const expectedWinner = scenarioDef?.expectedWinner;
|
|
784
|
+
if (expectedWinner && winner.route !== expectedWinner) {
|
|
785
|
+
failures.push({ scenario: scenario.scenario, route: winner.route, expectedWinner, reason: 'unexpected-winner' });
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return failures;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function schemaHasProperty(schema, prop) {
|
|
792
|
+
if (!schema || typeof schema !== 'object') return false;
|
|
793
|
+
if (schema.properties && Object.prototype.hasOwnProperty.call(schema.properties, prop)) return true;
|
|
794
|
+
const branches = [
|
|
795
|
+
schema.items,
|
|
796
|
+
...(Array.isArray(schema.anyOf) ? schema.anyOf : []),
|
|
797
|
+
...(Array.isArray(schema.oneOf) ? schema.oneOf : []),
|
|
798
|
+
...(Array.isArray(schema.allOf) ? schema.allOf : []),
|
|
799
|
+
].filter(Boolean);
|
|
800
|
+
return branches.some((branch) => schemaHasProperty(branch, prop));
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function descriptionOf(tool) {
|
|
804
|
+
return String(tool?.description || '');
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
async function routingRulesText() {
|
|
808
|
+
const files = [
|
|
809
|
+
join(ROOT, 'rules', 'shared', '01-tool.md'),
|
|
810
|
+
];
|
|
811
|
+
const parts = [];
|
|
812
|
+
for (const file of files) {
|
|
813
|
+
parts.push(await readFile(file, 'utf8').catch(() => ''));
|
|
814
|
+
}
|
|
815
|
+
return parts.join('\n');
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
async function checkToolSchemaPolicy() {
|
|
819
|
+
const failures = [];
|
|
820
|
+
let manifest;
|
|
821
|
+
try {
|
|
822
|
+
manifest = JSON.parse(await readFile(join(ROOT, 'tools.json'), 'utf8'));
|
|
823
|
+
} catch (err) {
|
|
824
|
+
return [{ scenario: 'schema-policy', route: 'tools.json', reason: 'manifest-unreadable', error: err?.message || String(err) }];
|
|
825
|
+
}
|
|
826
|
+
const byName = new Map((Array.isArray(manifest) ? manifest : []).map((tool) => [tool?.name, tool]));
|
|
827
|
+
const positions = new Map((Array.isArray(manifest) ? manifest : []).map((tool, index) => [tool?.name, index]));
|
|
828
|
+
const fail = (tool, reason, extra = {}) => failures.push({ scenario: 'schema-policy', route: tool, reason, ...extra });
|
|
829
|
+
const requireTool = (name) => {
|
|
830
|
+
const tool = byName.get(name);
|
|
831
|
+
if (!tool) fail(name, 'missing-tool');
|
|
832
|
+
return tool;
|
|
833
|
+
};
|
|
834
|
+
const edit = requireTool('edit');
|
|
835
|
+
const write = requireTool('write');
|
|
836
|
+
const grep = requireTool('grep');
|
|
837
|
+
const patch = requireTool('apply_patch');
|
|
838
|
+
const codeGraph = requireTool('code_graph');
|
|
839
|
+
|
|
840
|
+
const routeOrder = ['code_graph', 'glob', 'list', 'grep', 'read', 'edit', 'write', 'apply_patch', 'bash', 'job_wait'];
|
|
841
|
+
let previous = { name: null, index: -1 };
|
|
842
|
+
for (const name of routeOrder) {
|
|
843
|
+
const index = positions.get(name);
|
|
844
|
+
if (!Number.isInteger(index)) continue;
|
|
845
|
+
if (index < previous.index) {
|
|
846
|
+
fail(name, 'route-tool-order', { expectedAfter: previous.name, index, previousIndex: previous.index });
|
|
847
|
+
}
|
|
848
|
+
previous = { name, index };
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
if (edit && schemaHasProperty(edit.inputSchema, 'allow_large')) fail('edit', 'stale-allow-large-property');
|
|
852
|
+
if (grep && schemaHasProperty(grep.inputSchema, 'auto_route')) fail('grep', 'stale-auto-route-property');
|
|
853
|
+
if (grep && schemaHasProperty(grep.inputSchema, 'exhaustive')) fail('grep', 'stale-exhaustive-property');
|
|
854
|
+
if (patch && !schemaHasProperty(patch.inputSchema, 'dry_run')) fail('apply_patch', 'missing-dry-run-property');
|
|
855
|
+
|
|
856
|
+
const rulesText = await routingRulesText();
|
|
857
|
+
// Cue regexes accept the pre- and post-7485dfb rule phrasings: the check
|
|
858
|
+
// guards that routing GUIDANCE survives rule rewrites, not exact wording.
|
|
859
|
+
if (edit && !/edit`?[^\n]{0,80}small exact (?:text|substitution)/i.test(rulesText)) fail('rules', 'missing-small-edit-route-cue');
|
|
860
|
+
if (write && !/write`?[^\n]{0,80}(?:whole files?|full rewrite)/i.test(rulesText)) fail('rules', 'missing-whole-file-route-cue');
|
|
861
|
+
if (patch && !/apply_patch`?[^\n]{0,80}large(?:\/|, |[^\n]{0,40})structural/i.test(rulesText)) fail('rules', 'missing-large-structural-route-cue');
|
|
862
|
+
if (grep && !/glob`?\/`?list`?[\s\S]*grep[\s\S]*`?read/i.test(rulesText)) fail('rules', 'missing-broad-to-narrow-route-cue');
|
|
863
|
+
for (const [name, tool] of [['code_graph', codeGraph]]) {
|
|
864
|
+
if (tool && !new RegExp(`${name}[\\s\\S]*known identifier|known identifier[\\s\\S]*${name}`, 'i').test(rulesText)) {
|
|
865
|
+
fail('rules', `missing-${name}-known-identifier-cue`);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
if (!/mode:search/i.test(rulesText)) fail('rules', 'missing-mode-search-route-cue');
|
|
869
|
+
const searchModes = codeGraph?.inputSchema?.properties?.mode?.enum;
|
|
870
|
+
if (codeGraph && (!Array.isArray(searchModes) || !searchModes.includes('search'))) {
|
|
871
|
+
fail('code_graph', 'missing-search-mode-enum');
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const docsText = await readFile(join(ROOT, 'docs', 'io-tool-flow.md'), 'utf8').catch(() => '');
|
|
875
|
+
if (/edit-to-patch auto-route/i.test(docsText)) fail('docs/io-tool-flow.md', 'stale-edit-to-patch-auto-route');
|
|
876
|
+
if (/\ballow_large\b/i.test(docsText)) fail('docs/io-tool-flow.md', 'stale-allow-large-doc');
|
|
877
|
+
if (/\bauto_route\b/i.test(docsText)) fail('docs/io-tool-flow.md', 'stale-auto-route-doc');
|
|
878
|
+
return failures;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
async function main() {
|
|
882
|
+
await mkdir(process.env.CLAUDE_PLUGIN_DATA, { recursive: true });
|
|
883
|
+
if (TRACE_OUT && existsSync(TRACE_OUT)) await writeFile(TRACE_OUT, '', 'utf8');
|
|
884
|
+
const results = [];
|
|
885
|
+
for (const scenario of scenarios) {
|
|
886
|
+
for (const route of scenario.routes) {
|
|
887
|
+
results.push(await runRoute(scenario.name, route.name, scenario.setup, route.run, scenario.verify, route));
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
const summary = summarize(results);
|
|
891
|
+
const schemaPolicyFailures = CHECK ? await checkToolSchemaPolicy() : [];
|
|
892
|
+
const failures = CHECK ? [...checkResults(summary), ...schemaPolicyFailures] : [];
|
|
893
|
+
const output = {
|
|
894
|
+
workdir: WORKDIR,
|
|
895
|
+
traceOut: TRACE_OUT || null,
|
|
896
|
+
results,
|
|
897
|
+
summary: summary.map((item) => ({
|
|
898
|
+
scenario: item.scenario,
|
|
899
|
+
winner: item.winner,
|
|
900
|
+
routes: item.routes.map(({ scenario: _scenario, calls, error, ...rest }) => ({
|
|
901
|
+
...rest,
|
|
902
|
+
calls,
|
|
903
|
+
...(error ? { error: String(error).slice(0, 500) } : {}),
|
|
904
|
+
})),
|
|
905
|
+
})),
|
|
906
|
+
schemaPolicy: CHECK ? { failures: schemaPolicyFailures } : undefined,
|
|
907
|
+
failures,
|
|
908
|
+
};
|
|
909
|
+
if (OUTPUT_JSON) {
|
|
910
|
+
console.log(JSON.stringify(output, null, 2));
|
|
911
|
+
} else {
|
|
912
|
+
for (const item of summary) {
|
|
913
|
+
console.log(`\n# ${item.scenario} winner=${item.winner}`);
|
|
914
|
+
console.log('route ok calls wide brdg lged next outBytes wallMs score');
|
|
915
|
+
for (const row of item.routes) {
|
|
916
|
+
console.log(`${row.route.padEnd(32)} ${String(row.success).padEnd(5)} ${String(row.toolCalls).padStart(5)} ${String(row.wideReads).padStart(4)} ${String(row.broadGrepCalls).padStart(4)} ${String(row.largeEditCalls).padStart(4)} ${String(row.nextCalls).padStart(4)} ${String(row.outputBytes).padStart(8)} ${String(row.wallMs).padStart(7)} ${String(row.score).padStart(7)}`);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
if (TRACE_OUT) console.log(`\ntrace: ${TRACE_OUT}`);
|
|
920
|
+
if (CHECK) console.log(failures.length === 0 ? '\nroute harness: pass' : `\nroute harness: fail ${JSON.stringify(failures)}`);
|
|
921
|
+
}
|
|
922
|
+
if (failures.length > 0) process.exitCode = 1;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
try {
|
|
926
|
+
await main();
|
|
927
|
+
} finally {
|
|
928
|
+
await closeNativePatchServerForTests().catch(() => {});
|
|
929
|
+
if (!KEEP) {
|
|
930
|
+
await rm(WORKDIR, { recursive: true, force: true }).catch(() => {});
|
|
931
|
+
await rm(HARNESS_PLUGIN_DATA, { recursive: true, force: true }).catch(() => {});
|
|
932
|
+
}
|
|
933
|
+
}
|