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,240 @@
|
|
|
1
|
+
import { stripTrailingWhitespaceForEdit } from '../edit-normalize.mjs';
|
|
2
|
+
import {
|
|
3
|
+
bufferWithTrailingLf,
|
|
4
|
+
concatByteReplacements,
|
|
5
|
+
hashBytesWithReplacements,
|
|
6
|
+
} from './edit-byte-utils.mjs';
|
|
7
|
+
import {
|
|
8
|
+
countLfInString,
|
|
9
|
+
maybeAutoStripLineNumberPrefixes,
|
|
10
|
+
} from './edit-context-utils.mjs';
|
|
11
|
+
import { replacementForOriginalSlice } from './edit-match-utils.mjs';
|
|
12
|
+
|
|
13
|
+
export function tryBuildExactEditBuffer(rawBuf, oldStr, newStr, replaceAll, snapshot, filePath) {
|
|
14
|
+
if (!Buffer.isBuffer(rawBuf) || typeof oldStr !== 'string' || oldStr.length === 0 || typeof newStr !== 'string') return null;
|
|
15
|
+
const oldBytes = Buffer.from(oldStr, 'utf-8');
|
|
16
|
+
if (oldBytes.length === 0) return null;
|
|
17
|
+
let eOldStr = oldStr;
|
|
18
|
+
let eOldBytes = oldBytes;
|
|
19
|
+
const _pureDeletion = newStr === '' && !oldStr.endsWith('\n') && !oldStr.endsWith('\r');
|
|
20
|
+
// Single-occurrence pure deletion: probe globally so the unique
|
|
21
|
+
// match also covers its trailing line terminator (CRLF first to
|
|
22
|
+
// avoid leaving a stray \r on CRLF files, then LF, then lone-CR).
|
|
23
|
+
// Replace-all pure deletion CANNOT use a single global rewrite of
|
|
24
|
+
// eOldBytes — when occurrences are mixed (some followed by \n, some
|
|
25
|
+
// bare / at EOF), an eOldBytes that includes the terminator would
|
|
26
|
+
// silently skip every bare occurrence. Per-occurrence absorption
|
|
27
|
+
// below extends each span over its own trailing \r\n / \n / \r so
|
|
28
|
+
// every match is removed regardless of what follows it.
|
|
29
|
+
if (_pureDeletion && !replaceAll) {
|
|
30
|
+
// Judge ambiguity on the BARE oldBytes BEFORE newline absorption.
|
|
31
|
+
// Absorbing a trailing terminator first can narrow a >1-occurrence
|
|
32
|
+
// bare match down to a unique extended span (e.g. 'X' present as
|
|
33
|
+
// 'X\r\n...X'), silently single-deleting instead of surfacing the
|
|
34
|
+
// ambiguous-match error. If the bare oldBytes occurs more than once
|
|
35
|
+
// return null — the same signal the spans.length>1 path emits (→ the
|
|
36
|
+
// caller's code-9 ambiguous-match error). Only absorb when the bare
|
|
37
|
+
// match is unique.
|
|
38
|
+
const _firstBare = rawBuf.indexOf(oldBytes);
|
|
39
|
+
if (_firstBare !== -1 && rawBuf.indexOf(oldBytes, _firstBare + 1) !== -1) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const oldWithCrlf = Buffer.from(`${oldStr}\r\n`, 'utf-8');
|
|
43
|
+
const oldWithLf = bufferWithTrailingLf(oldBytes);
|
|
44
|
+
const oldWithCr = Buffer.from(`${oldStr}\r`, 'utf-8');
|
|
45
|
+
if (rawBuf.indexOf(oldWithCrlf) !== -1) {
|
|
46
|
+
eOldStr = `${oldStr}\r\n`;
|
|
47
|
+
eOldBytes = oldWithCrlf;
|
|
48
|
+
} else if (rawBuf.indexOf(oldWithLf) !== -1) {
|
|
49
|
+
eOldStr = `${oldStr}\n`;
|
|
50
|
+
eOldBytes = oldWithLf;
|
|
51
|
+
} else if (rawBuf.indexOf(oldWithCr) !== -1) {
|
|
52
|
+
eOldStr = `${oldStr}\r`;
|
|
53
|
+
eOldBytes = oldWithCr;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const _perOccurrenceAbsorb = _pureDeletion && replaceAll;
|
|
57
|
+
if (!replaceAll) {
|
|
58
|
+
const _fb = rawBuf.indexOf(eOldBytes);
|
|
59
|
+
if (_fb !== -1 && rawBuf.indexOf(eOldBytes, _fb + 1) !== -1) return null;
|
|
60
|
+
}
|
|
61
|
+
const spans = [];
|
|
62
|
+
let idx = 0;
|
|
63
|
+
while ((idx = rawBuf.indexOf(eOldBytes, idx)) !== -1) {
|
|
64
|
+
let _end = idx + eOldBytes.length;
|
|
65
|
+
if (_perOccurrenceAbsorb) {
|
|
66
|
+
// CRLF (\r\n) first so we don't strand a stray \r; then LF;
|
|
67
|
+
// then lone-CR. Bare / EOF occurrences leave _end as-is.
|
|
68
|
+
if (rawBuf[_end] === 0x0d && rawBuf[_end + 1] === 0x0a) _end += 2;
|
|
69
|
+
else if (rawBuf[_end] === 0x0a) _end += 1;
|
|
70
|
+
else if (rawBuf[_end] === 0x0d) _end += 1;
|
|
71
|
+
}
|
|
72
|
+
spans.push({ start: idx, end: _end });
|
|
73
|
+
if (!replaceAll && spans.length > 1) return null;
|
|
74
|
+
idx += eOldBytes.length;
|
|
75
|
+
}
|
|
76
|
+
if (spans.length === 0) return null;
|
|
77
|
+
const fileUtf8 = rawBuf.toString('utf-8');
|
|
78
|
+
const replacements = spans.map((span) => ({
|
|
79
|
+
...span,
|
|
80
|
+
newBytes: Buffer.from(
|
|
81
|
+
replacementForOriginalSlice(newStr, rawBuf.subarray(span.start, span.end).toString('utf-8'), fileUtf8),
|
|
82
|
+
'utf-8',
|
|
83
|
+
),
|
|
84
|
+
}));
|
|
85
|
+
const sameSize = replacements.every((span) => span.end - span.start === span.newBytes.length);
|
|
86
|
+
if (spans.length === 1) {
|
|
87
|
+
const first = spans[0].start;
|
|
88
|
+
if (sameSize) {
|
|
89
|
+
return {
|
|
90
|
+
replacements,
|
|
91
|
+
sameSize: true,
|
|
92
|
+
contentHash: hashBytesWithReplacements(rawBuf, replacements),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
replacements,
|
|
97
|
+
sameSize: false,
|
|
98
|
+
updated: Buffer.concat([
|
|
99
|
+
rawBuf.subarray(0, first),
|
|
100
|
+
replacements[0].newBytes,
|
|
101
|
+
rawBuf.subarray(spans[0].end),
|
|
102
|
+
], rawBuf.length - (spans[0].end - first) + replacements[0].newBytes.length),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (sameSize) {
|
|
106
|
+
return {
|
|
107
|
+
replacements,
|
|
108
|
+
sameSize: true,
|
|
109
|
+
contentHash: hashBytesWithReplacements(rawBuf, replacements),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
replacements,
|
|
114
|
+
sameSize: false,
|
|
115
|
+
updated: concatByteReplacements(rawBuf, replacements),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function tryBuildMultiExactEditBuffer(rawBuf, edits, args, snapshot, filePath) {
|
|
120
|
+
if (!Buffer.isBuffer(rawBuf) || !Array.isArray(edits) || edits.length === 0) return null;
|
|
121
|
+
// Fast path treats every edit as locating its span in the ORIGINAL buffer
|
|
122
|
+
// and applies all replacements at once. Sequential-apply semantics differ
|
|
123
|
+
// whenever edit A's new_string can synthesise (or destroy) bytes that
|
|
124
|
+
// edit B's old_string would match. Disable the fast path unless edits
|
|
125
|
+
// are provably independent — no pair where one's new_string contains
|
|
126
|
+
// another's old_string. The slow path runs them sequentially.
|
|
127
|
+
if (edits.length > 1) {
|
|
128
|
+
for (let a = 0; a < edits.length; a++) {
|
|
129
|
+
const ea = edits[a];
|
|
130
|
+
if (!ea || typeof ea.old_string !== 'string' || typeof ea.new_string !== 'string') return null;
|
|
131
|
+
for (let b = 0; b < edits.length; b++) {
|
|
132
|
+
if (a === b) continue;
|
|
133
|
+
const eb = edits[b];
|
|
134
|
+
if (!eb || typeof eb.old_string !== 'string') return null;
|
|
135
|
+
if (eb.old_string.length === 0) continue;
|
|
136
|
+
if (ea.new_string.indexOf(eb.old_string) !== -1) return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const isMarkdown = /\.(?:md|mdx)$/i.test(filePath);
|
|
141
|
+
const replacements = [];
|
|
142
|
+
const normalizedEdits = [];
|
|
143
|
+
for (let i = 0; i < edits.length; i++) {
|
|
144
|
+
const entry = edits[i];
|
|
145
|
+
if (!entry || typeof entry.old_string !== 'string' || typeof entry.new_string !== 'string') return null;
|
|
146
|
+
let oldString = entry.old_string;
|
|
147
|
+
let newString = entry.new_string;
|
|
148
|
+
{
|
|
149
|
+
const _nulIdx = newString.indexOf('\u0000');
|
|
150
|
+
if (_nulIdx !== -1) {
|
|
151
|
+
return { error: `Error [code 11]: edit ${i} — new_string contains NUL byte (U+0000) at offset ${_nulIdx} — source text must not contain NUL: ${filePath}` };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const replaceAll = entry.replace_all === true;
|
|
155
|
+
// Size gate moved out of the pre-loop guard: the exact byte
|
|
156
|
+
// path proves uniqueness below (spans.length === 1 for
|
|
157
|
+
// !replaceAll), so an exact-unique multi edit applies at any
|
|
158
|
+
// size. The fold-fallback path in edit-engine still enforces
|
|
159
|
+
// the >=30-line code-10 wording for genuine fold-misses.
|
|
160
|
+
if (/^\s*\d+[\t│→]/.test(oldString)) {
|
|
161
|
+
const stripped = maybeAutoStripLineNumberPrefixes(oldString);
|
|
162
|
+
if (stripped === null) return null;
|
|
163
|
+
oldString = stripped;
|
|
164
|
+
}
|
|
165
|
+
// Final-line-aware hygiene — same rule as the edit-engine slow path
|
|
166
|
+
// (stripTrailingWhitespaceForEdit), so fast/slow paths cannot diverge
|
|
167
|
+
// on identical inputs.
|
|
168
|
+
if (!isMarkdown) newString = stripTrailingWhitespaceForEdit(newString, oldString);
|
|
169
|
+
if (oldString.length === 0) {
|
|
170
|
+
return { error: `Error: edit ${i} — old_string must be non-empty` };
|
|
171
|
+
}
|
|
172
|
+
if (oldString === newString) {
|
|
173
|
+
return { error: `Error: edit ${i} — new_string must differ from old_string` };
|
|
174
|
+
}
|
|
175
|
+
if (newString === '' || countLfInString(oldString) !== countLfInString(newString)) return null;
|
|
176
|
+
const oldBytes = Buffer.from(oldString, 'utf-8');
|
|
177
|
+
const fileUtf8 = rawBuf.toString('utf-8');
|
|
178
|
+
if (oldBytes.length === 0) return null;
|
|
179
|
+
if (!replaceAll) {
|
|
180
|
+
const _fb = rawBuf.indexOf(oldBytes);
|
|
181
|
+
if (_fb !== -1 && rawBuf.indexOf(oldBytes, _fb + 1) !== -1) return null;
|
|
182
|
+
}
|
|
183
|
+
normalizedEdits.push({ oldString, newString, replaceAll });
|
|
184
|
+
const spans = [];
|
|
185
|
+
let idx = 0;
|
|
186
|
+
while ((idx = rawBuf.indexOf(oldBytes, idx)) !== -1) {
|
|
187
|
+
const spanEnd = idx + oldBytes.length;
|
|
188
|
+
const sliceText = rawBuf.subarray(idx, spanEnd).toString('utf-8');
|
|
189
|
+
const newBytes = Buffer.from(
|
|
190
|
+
replacementForOriginalSlice(newString, sliceText, fileUtf8),
|
|
191
|
+
'utf-8',
|
|
192
|
+
);
|
|
193
|
+
spans.push({ start: idx, end: spanEnd, editIndex: i, newBytes });
|
|
194
|
+
if (!replaceAll && spans.length > 1) return null;
|
|
195
|
+
idx += oldBytes.length;
|
|
196
|
+
}
|
|
197
|
+
if (spans.length === 0) return null;
|
|
198
|
+
replacements.push(...spans);
|
|
199
|
+
}
|
|
200
|
+
replacements.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
201
|
+
for (let i = 1; i < replacements.length; i++) {
|
|
202
|
+
if (replacements[i].start < replacements[i - 1].end) return null;
|
|
203
|
+
}
|
|
204
|
+
// Independence guard (above) only checks new_string-contains-old_string; it
|
|
205
|
+
// misses old_strings SYNTHESISED across edit boundaries (e.g. "A"->"y" turns
|
|
206
|
+
// "Az yz" into "yz yz", making a later "yz"->Q match twice — which sequential
|
|
207
|
+
// apply would reject as ambiguous). Verify the fast (apply-in-original)
|
|
208
|
+
// result equals an in-order SEQUENTIAL replay; on any divergence or
|
|
209
|
+
// sequential-reject, return null so the caller runs the slow sequential
|
|
210
|
+
// path. Fail-safe: a false mismatch only costs a fallback, never corruption.
|
|
211
|
+
{
|
|
212
|
+
const _fastBytes = concatByteReplacements(rawBuf, replacements);
|
|
213
|
+
let seq = rawBuf.toString('utf-8');
|
|
214
|
+
for (const { oldString: _o, newString: _n, replaceAll: _ra } of normalizedEdits) {
|
|
215
|
+
const repl = replacementForOriginalSlice(_n, _o, seq);
|
|
216
|
+
if (_ra) {
|
|
217
|
+
if (!seq.includes(_o)) return null;
|
|
218
|
+
seq = seq.split(_o).join(repl);
|
|
219
|
+
} else {
|
|
220
|
+
const first = seq.indexOf(_o);
|
|
221
|
+
if (first === -1 || seq.indexOf(_o, first + _o.length) !== -1) return null;
|
|
222
|
+
seq = seq.slice(0, first) + repl + seq.slice(first + _o.length);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (!Buffer.from(seq, 'utf-8').equals(_fastBytes)) return null;
|
|
226
|
+
}
|
|
227
|
+
const sameSize = replacements.every((span) => span.end - span.start === span.newBytes.length);
|
|
228
|
+
if (sameSize) {
|
|
229
|
+
return {
|
|
230
|
+
replacements,
|
|
231
|
+
sameSize: true,
|
|
232
|
+
contentHash: hashBytesWithReplacements(rawBuf, replacements),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
replacements,
|
|
237
|
+
sameSize: false,
|
|
238
|
+
updated: concatByteReplacements(rawBuf, replacements),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { statSync } from 'fs';
|
|
3
|
+
import { hashText } from './hash-utils.mjs';
|
|
4
|
+
import { statMatchesSnapshot } from './snapshot-helpers.mjs';
|
|
5
|
+
|
|
6
|
+
export function lineRangesForByteSpans(rawBuf, spans) {
|
|
7
|
+
if (!Buffer.isBuffer(rawBuf) || !Array.isArray(spans) || spans.length === 0) return [];
|
|
8
|
+
const sorted = spans
|
|
9
|
+
.filter((span) => span && Number.isFinite(span.start) && Number.isFinite(span.end) && span.start >= 0 && span.end >= span.start)
|
|
10
|
+
.sort((a, b) => a.start - b.start);
|
|
11
|
+
const out = [];
|
|
12
|
+
let lineNo = 1;
|
|
13
|
+
let scanned = 0;
|
|
14
|
+
for (const span of sorted) {
|
|
15
|
+
const start = Math.min(rawBuf.length, span.start);
|
|
16
|
+
const end = Math.min(rawBuf.length, span.end);
|
|
17
|
+
for (let i = scanned; i < start; i++) {
|
|
18
|
+
if (rawBuf[i] === 10) lineNo++;
|
|
19
|
+
}
|
|
20
|
+
const startLine = lineNo;
|
|
21
|
+
let lineCount = 1;
|
|
22
|
+
for (let i = start; i < end; i++) {
|
|
23
|
+
if (rawBuf[i] === 10) lineCount++;
|
|
24
|
+
}
|
|
25
|
+
for (let i = start; i < end; i++) {
|
|
26
|
+
if (rawBuf[i] === 10) lineNo++;
|
|
27
|
+
}
|
|
28
|
+
scanned = end;
|
|
29
|
+
out.push({ startLine, endLine: startLine + lineCount - 1 });
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function bufferWithTrailingLf(buf) {
|
|
35
|
+
const out = Buffer.allocUnsafe(buf.length + 1);
|
|
36
|
+
buf.copy(out, 0);
|
|
37
|
+
out[buf.length] = 10;
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function concatByteReplacements(rawBuf, replacements) {
|
|
42
|
+
const parts = [];
|
|
43
|
+
let cursor = 0;
|
|
44
|
+
let totalLength = 0;
|
|
45
|
+
for (const span of replacements) {
|
|
46
|
+
if (span.start < cursor) return null;
|
|
47
|
+
const before = rawBuf.subarray(cursor, span.start);
|
|
48
|
+
parts.push(before, span.newBytes);
|
|
49
|
+
totalLength += before.length + span.newBytes.length;
|
|
50
|
+
cursor = span.end;
|
|
51
|
+
}
|
|
52
|
+
const tail = rawBuf.subarray(cursor);
|
|
53
|
+
parts.push(tail);
|
|
54
|
+
totalLength += tail.length;
|
|
55
|
+
return Buffer.concat(parts, totalLength);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function hashBytesWithReplacements(rawBuf, replacements) {
|
|
59
|
+
const hasher = createHash('sha256');
|
|
60
|
+
let cursor = 0;
|
|
61
|
+
const sorted = Array.isArray(replacements)
|
|
62
|
+
? replacements.slice().sort((a, b) => a.start - b.start || a.end - b.end)
|
|
63
|
+
: [];
|
|
64
|
+
for (const span of sorted) {
|
|
65
|
+
if (!span || span.start < cursor || span.end < span.start || !Buffer.isBuffer(span.newBytes)) return hashText(rawBuf);
|
|
66
|
+
hasher.update(rawBuf.subarray(cursor, span.start));
|
|
67
|
+
hasher.update(span.newBytes);
|
|
68
|
+
cursor = span.end;
|
|
69
|
+
}
|
|
70
|
+
hasher.update(rawBuf.subarray(cursor));
|
|
71
|
+
return hasher.digest('hex');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function materialiseByteReplacements(rawBuf, replacements) {
|
|
75
|
+
return concatByteReplacements(rawBuf, replacements);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function partialByteWriteEnabled() {
|
|
79
|
+
return !/^(0|false|no|off|atomic)$/i.test(String(process.env.MIXDOG_EDIT_PARTIAL_WRITE || process.env.MIXDOG_PARTIAL_WRITE || ''));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function captureStableBaseStatSnapshot(fullPath, statHint, rawBuf) {
|
|
83
|
+
if (!fullPath || !statHint || !Buffer.isBuffer(rawBuf)) return null;
|
|
84
|
+
try {
|
|
85
|
+
const postReadStat = statSync(fullPath);
|
|
86
|
+
if (statMatchesSnapshot(postReadStat, statHint) && postReadStat.size === rawBuf.length) {
|
|
87
|
+
return {
|
|
88
|
+
mtimeMs: postReadStat.mtimeMs,
|
|
89
|
+
ctimeMs: postReadStat.ctimeMs,
|
|
90
|
+
size: postReadStat.size,
|
|
91
|
+
ino: Number(postReadStat.ino),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
} catch {}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** TOCTOU guard for atomicWrite — same shape as write-tool captureTargetSnapshot. */
|
|
99
|
+
export function captureExpectedTargetSnapshot(fullPath, statHint = null) {
|
|
100
|
+
try {
|
|
101
|
+
const st = statHint || statSync(fullPath);
|
|
102
|
+
return {
|
|
103
|
+
exists: true,
|
|
104
|
+
size: st.size,
|
|
105
|
+
mtimeMs: Number(st.mtimeMs),
|
|
106
|
+
ctimeMs: Number(st.ctimeMs),
|
|
107
|
+
ino: Number(st.ino),
|
|
108
|
+
};
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if (err && err.code === 'ENOENT') return { exists: false };
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { markCodeGraphDirtyPaths } from '../code-graph.mjs';
|
|
2
|
+
import { atomicWrite } from './atomic-write.mjs';
|
|
3
|
+
import {
|
|
4
|
+
invalidateBuiltinResultCache,
|
|
5
|
+
seedRawContentCacheAfterWrite,
|
|
6
|
+
} from './cache-layers.mjs';
|
|
7
|
+
import { captureExpectedTargetSnapshot, materialiseByteReplacements } from './edit-byte-utils.mjs';
|
|
8
|
+
import { postEditSnapshotMeta } from './edit-context-utils.mjs';
|
|
9
|
+
import { tryWriteSameSizeByteReplacementsSync } from './edit-partial-write.mjs';
|
|
10
|
+
import { recordReadSnapshot } from './read-snapshot-runtime.mjs';
|
|
11
|
+
import { validatePreparedEditBase } from './edit-base-guard.mjs';
|
|
12
|
+
|
|
13
|
+
function commitHooks(hooks = {}) {
|
|
14
|
+
return {
|
|
15
|
+
ioTraceStart: typeof hooks.ioTraceStart === 'function' ? hooks.ioTraceStart : () => 0,
|
|
16
|
+
ioTraceDone: typeof hooks.ioTraceDone === 'function' ? hooks.ioTraceDone : () => {},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function commitPreparedEditUnlocked(prepared, readStateScope, options = {}, hooks = {}) {
|
|
21
|
+
const traceHooks = commitHooks(hooks);
|
|
22
|
+
if (Array.isArray(prepared.sameSizeByteReplacements) && prepared.sameSizeByteReplacements.length > 0) {
|
|
23
|
+
const partial = tryWriteSameSizeByteReplacementsSync(prepared.fullPath, prepared.sameSizeByteReplacements, {
|
|
24
|
+
baseStatSnapshot: prepared.baseStatSnapshot,
|
|
25
|
+
baseMutationGeneration: prepared.baseMutationGeneration,
|
|
26
|
+
baseContentHash: prepared.baseContentHash,
|
|
27
|
+
contentHash: prepared.contentHash,
|
|
28
|
+
fsync: options?.fsync,
|
|
29
|
+
filePath: prepared.filePath,
|
|
30
|
+
}, {
|
|
31
|
+
...traceHooks,
|
|
32
|
+
validatePreparedEditBase,
|
|
33
|
+
});
|
|
34
|
+
if (partial?.ok) {
|
|
35
|
+
invalidateBuiltinResultCache([prepared.fullPath]);
|
|
36
|
+
markCodeGraphDirtyPaths([prepared.fullPath]);
|
|
37
|
+
const afterBuf = materialiseByteReplacements(prepared.baseRawContent, prepared.sameSizeByteReplacements);
|
|
38
|
+
const snapMeta = postEditSnapshotMeta(prepared.snapshot, 'edit', afterBuf, {
|
|
39
|
+
contentBeforeEdit: prepared.baseRawContent,
|
|
40
|
+
shiftRanges: false,
|
|
41
|
+
});
|
|
42
|
+
recordReadSnapshot(prepared.fullPath, partial.stat || undefined, readStateScope, snapMeta);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (partial?.error) throw new Error(partial.error.replace(/^Error:\s*/, ''));
|
|
46
|
+
if (!Buffer.isBuffer(prepared.content) && Buffer.isBuffer(prepared.baseRawContent)) {
|
|
47
|
+
prepared.content = materialiseByteReplacements(prepared.baseRawContent, prepared.sameSizeByteReplacements);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!Buffer.isBuffer(prepared.content) && typeof prepared.content !== 'string') {
|
|
51
|
+
throw new Error('prepared edit missing materialised content');
|
|
52
|
+
}
|
|
53
|
+
const expectedTargetSnapshot = prepared.expectedTargetSnapshot
|
|
54
|
+
|| captureExpectedTargetSnapshot(prepared.fullPath);
|
|
55
|
+
await atomicWrite(prepared.fullPath, prepared.content, {
|
|
56
|
+
sessionId: options?.sessionId,
|
|
57
|
+
mode: prepared.baseMode,
|
|
58
|
+
expectedTargetSnapshot,
|
|
59
|
+
});
|
|
60
|
+
invalidateBuiltinResultCache([prepared.fullPath]);
|
|
61
|
+
const writtenStat = seedRawContentCacheAfterWrite(prepared.fullPath, prepared.content);
|
|
62
|
+
markCodeGraphDirtyPaths([prepared.fullPath]);
|
|
63
|
+
recordReadSnapshot(prepared.fullPath, writtenStat || undefined, readStateScope, postEditSnapshotMeta(prepared.snapshot, 'edit', prepared.content, {
|
|
64
|
+
contentBeforeEdit: prepared.baseRawContent,
|
|
65
|
+
shiftRanges: false,
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function commitPreparedEditCheckedUnlocked(prepared, readStateScope, options = {}, hooks = {}) {
|
|
70
|
+
const prewriteErr = validatePreparedEditBase(prepared);
|
|
71
|
+
if (prewriteErr) return { ok: false, error: prewriteErr };
|
|
72
|
+
await commitPreparedEditUnlocked(prepared, readStateScope, options, hooks);
|
|
73
|
+
return { ok: true };
|
|
74
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { hashText } from './hash-utils.mjs';
|
|
2
|
+
import { renderReadLine } from './read-formatting.mjs';
|
|
3
|
+
import {
|
|
4
|
+
snapshotCoversFullFile,
|
|
5
|
+
snapshotRangesCoverAllLines,
|
|
6
|
+
} from './snapshot-helpers.mjs';
|
|
7
|
+
|
|
8
|
+
export function countLfInString(value) {
|
|
9
|
+
let n = 0;
|
|
10
|
+
const s = String(value ?? '');
|
|
11
|
+
for (let i = 0; i < s.length; i++) {
|
|
12
|
+
if (s.charCodeAt(i) === 10) n++;
|
|
13
|
+
}
|
|
14
|
+
return n;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normaliseSnapshotRange(r) {
|
|
18
|
+
if (!r) return null;
|
|
19
|
+
const startLine = Math.max(1, Number(r.startLine) || 1);
|
|
20
|
+
const endLine = r.endLine === Infinity ? Infinity : Number(r.endLine);
|
|
21
|
+
if (!Number.isFinite(startLine)) return null;
|
|
22
|
+
if (endLine !== Infinity && (!Number.isFinite(endLine) || endLine < startLine)) return null;
|
|
23
|
+
return { startLine, endLine };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isFullFileSentinelRange(r) {
|
|
27
|
+
return r && r.startLine <= 1 && r.endLine === Infinity;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function lineRangeForSubstring(content, needle, { replaceAll = false } = {}) {
|
|
31
|
+
if (typeof content !== 'string' || typeof needle !== 'string' || needle.length === 0) return null;
|
|
32
|
+
let oldLineCount = 1;
|
|
33
|
+
for (let i = 0; i < needle.length; i++) {
|
|
34
|
+
if (needle.charCodeAt(i) === 10) oldLineCount++;
|
|
35
|
+
}
|
|
36
|
+
// A needle ending in "\n" terminates its last content line rather than
|
|
37
|
+
// starting a new one. Without this, endLine over-counts by 1 and the edit
|
|
38
|
+
// marks the FOLLOWING line as part of its (inclusive) range, mis-shifting
|
|
39
|
+
// snapshot/context ranges. Reviewer-verified: ranges are inclusive
|
|
40
|
+
// end-to-end with no downstream compensation. No effect for needles that
|
|
41
|
+
// do not end in "\n".
|
|
42
|
+
if (needle.endsWith('\n')) oldLineCount--;
|
|
43
|
+
let idx = 0;
|
|
44
|
+
let scanned = 0;
|
|
45
|
+
let lineNo = 1;
|
|
46
|
+
let minStart = Infinity;
|
|
47
|
+
let maxEnd = 0;
|
|
48
|
+
let found = false;
|
|
49
|
+
while ((idx = content.indexOf(needle, idx)) !== -1) {
|
|
50
|
+
for (let i = scanned; i < idx; i++) {
|
|
51
|
+
if (content.charCodeAt(i) === 10) lineNo++;
|
|
52
|
+
}
|
|
53
|
+
scanned = idx;
|
|
54
|
+
const startLine = lineNo;
|
|
55
|
+
const endLine = startLine + oldLineCount - 1;
|
|
56
|
+
minStart = Math.min(minStart, startLine);
|
|
57
|
+
maxEnd = Math.max(maxEnd, endLine);
|
|
58
|
+
found = true;
|
|
59
|
+
idx += needle.length;
|
|
60
|
+
if (!replaceAll) break;
|
|
61
|
+
}
|
|
62
|
+
if (!found) return null;
|
|
63
|
+
return { startLine: minStart, endLine: maxEnd };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function shiftSnapshotRangesForEdit(snapshot, opts = {}) {
|
|
67
|
+
if (!snapshot || !Array.isArray(snapshot.ranges)) return snapshot;
|
|
68
|
+
const lineDelta = Number(opts.lineDelta);
|
|
69
|
+
const delta = Number.isFinite(lineDelta) ? lineDelta : 0;
|
|
70
|
+
const editStart = Number(opts.editStartLine);
|
|
71
|
+
const editEnd = Number(opts.editEndLine);
|
|
72
|
+
const hasSpan = Number.isFinite(editStart) && Number.isFinite(editEnd) && editStart >= 1 && editEnd >= editStart;
|
|
73
|
+
|
|
74
|
+
const out = [];
|
|
75
|
+
for (const raw of snapshot.ranges) {
|
|
76
|
+
const r = normaliseSnapshotRange(raw);
|
|
77
|
+
if (!r) continue;
|
|
78
|
+
if (isFullFileSentinelRange(r)) {
|
|
79
|
+
out.push({ startLine: 1, endLine: Infinity });
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
let { startLine, endLine } = r;
|
|
83
|
+
if (!hasSpan) {
|
|
84
|
+
if (delta !== 0) {
|
|
85
|
+
startLine = Math.max(1, startLine + delta);
|
|
86
|
+
if (endLine !== Infinity) endLine = endLine + delta;
|
|
87
|
+
}
|
|
88
|
+
} else if (endLine !== Infinity && endLine < editStart) {
|
|
89
|
+
// entirely before the edited span
|
|
90
|
+
} else if (startLine > editEnd) {
|
|
91
|
+
startLine = Math.max(1, startLine + delta);
|
|
92
|
+
if (endLine !== Infinity) endLine = endLine + delta;
|
|
93
|
+
} else {
|
|
94
|
+
// overlaps the edited span — keep start, extend/shrink end safely
|
|
95
|
+
if (endLine !== Infinity) {
|
|
96
|
+
endLine = Math.max(startLine, endLine + delta);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (endLine !== Infinity && endLine < startLine) continue;
|
|
100
|
+
out.push({ startLine, endLine });
|
|
101
|
+
}
|
|
102
|
+
return { ...snapshot, ranges: out };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function shiftSnapshotRangesByLineDelta(snapshot, lineDelta) {
|
|
106
|
+
return shiftSnapshotRangesForEdit(snapshot, { lineDelta });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function isStrongExactEditTarget(oldStr, stage) {
|
|
110
|
+
const s = String(oldStr ?? '').trim();
|
|
111
|
+
const fuzzy = stage && stage !== 'exact';
|
|
112
|
+
const singleLineMin = fuzzy ? 16 : 32;
|
|
113
|
+
const multiLineMin = fuzzy ? 10 : 20;
|
|
114
|
+
if (s.length >= singleLineMin) return true;
|
|
115
|
+
if (s.includes('\n') && s.length >= multiLineMin) return true;
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function snapshotMetaHasFullCoverage(meta, content) {
|
|
120
|
+
if (!meta || !Array.isArray(meta.ranges)) return false;
|
|
121
|
+
if (snapshotCoversFullFile(meta)) return true;
|
|
122
|
+
const lineCount = String(content ?? '').split(/\r?\n/).length;
|
|
123
|
+
return snapshotRangesCoverAllLines(meta, lineCount);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function postEditSnapshotMeta(prevSnapshot, source, content, opts = {}) {
|
|
127
|
+
const text = Buffer.isBuffer(content) ? content.toString('utf-8') : String(content ?? '');
|
|
128
|
+
let lineDelta = Number(opts.lineDelta);
|
|
129
|
+
if (!Number.isFinite(lineDelta) && opts.contentBeforeEdit != null) {
|
|
130
|
+
const before = Buffer.isBuffer(opts.contentBeforeEdit)
|
|
131
|
+
? opts.contentBeforeEdit.toString('utf-8')
|
|
132
|
+
: String(opts.contentBeforeEdit ?? '');
|
|
133
|
+
const after = text;
|
|
134
|
+
lineDelta = countLfInString(after) - countLfInString(before);
|
|
135
|
+
}
|
|
136
|
+
if (!Number.isFinite(lineDelta) && opts.oldStr != null && opts.newStr != null) {
|
|
137
|
+
const per = countLfInString(opts.newStr) - countLfInString(opts.oldStr);
|
|
138
|
+
const beforeText = Buffer.isBuffer(opts.contentBeforeEdit)
|
|
139
|
+
? opts.contentBeforeEdit.toString('utf-8')
|
|
140
|
+
: String(opts.contentBeforeEdit ?? '');
|
|
141
|
+
const occ = opts.replaceAll === true
|
|
142
|
+
? Math.max(1, countOccurrences(beforeText, opts.oldStr))
|
|
143
|
+
: 1;
|
|
144
|
+
lineDelta = per * occ;
|
|
145
|
+
}
|
|
146
|
+
if (!Number.isFinite(lineDelta)) lineDelta = 0;
|
|
147
|
+
|
|
148
|
+
let editStartLine = Number(opts.editStartLine);
|
|
149
|
+
let editEndLine = Number(opts.editEndLine);
|
|
150
|
+
if (!(Number.isFinite(editStartLine) && Number.isFinite(editEndLine)) && typeof opts.oldStr === 'string' && opts.contentBeforeEdit != null) {
|
|
151
|
+
const beforeText = Buffer.isBuffer(opts.contentBeforeEdit)
|
|
152
|
+
? opts.contentBeforeEdit.toString('utf-8')
|
|
153
|
+
: String(opts.contentBeforeEdit ?? '');
|
|
154
|
+
const span = lineRangeForSubstring(beforeText, opts.oldStr, { replaceAll: opts.replaceAll === true });
|
|
155
|
+
if (span) {
|
|
156
|
+
editStartLine = span.startLine;
|
|
157
|
+
editEndLine = span.endLine;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const meta = { source };
|
|
162
|
+
if (prevSnapshot && Array.isArray(prevSnapshot.ranges)) {
|
|
163
|
+
if (opts.shiftRanges === false) {
|
|
164
|
+
meta.ranges = prevSnapshot.ranges.map((r) => ({ ...r }));
|
|
165
|
+
} else {
|
|
166
|
+
const shifted = shiftSnapshotRangesForEdit(prevSnapshot, {
|
|
167
|
+
lineDelta,
|
|
168
|
+
editStartLine,
|
|
169
|
+
editEndLine,
|
|
170
|
+
});
|
|
171
|
+
if (shifted && Array.isArray(shifted.ranges)) meta.ranges = shifted.ranges;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (snapshotMetaHasFullCoverage(meta, text)) {
|
|
175
|
+
meta.contentHash = hashText(text);
|
|
176
|
+
}
|
|
177
|
+
return meta;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function maybeAutoStripLineNumberPrefixes(oldStr) {
|
|
181
|
+
if (typeof oldStr !== 'string' || oldStr.length === 0) return null;
|
|
182
|
+
if (!/^\s*\d+[\t│→]/.test(oldStr)) return null;
|
|
183
|
+
const lines = oldStr.split('\n');
|
|
184
|
+
const stripped = [];
|
|
185
|
+
for (const ln of lines) {
|
|
186
|
+
const m = ln.match(/^\s*\d+[\t│→](.*)$/);
|
|
187
|
+
if (!m) return null;
|
|
188
|
+
stripped.push(m[1]);
|
|
189
|
+
}
|
|
190
|
+
return stripped.join('\n');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function countOccurrences(haystack, needle) {
|
|
194
|
+
if (typeof needle !== 'string' || needle.length === 0) return 0;
|
|
195
|
+
let count = 0;
|
|
196
|
+
let idx = 0;
|
|
197
|
+
while ((idx = haystack.indexOf(needle, idx)) !== -1) {
|
|
198
|
+
count++;
|
|
199
|
+
idx += needle.length;
|
|
200
|
+
}
|
|
201
|
+
return count;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function lineContextAround(content, startLine, endLine, radius = 3, maxChars = 1600) {
|
|
205
|
+
const lines = String(content ?? '').split('\n');
|
|
206
|
+
const total = lines.length;
|
|
207
|
+
const start = Math.max(1, Math.min(total, startLine) - radius);
|
|
208
|
+
const end = Math.min(total, Math.max(startLine, endLine) + radius);
|
|
209
|
+
let out = lines
|
|
210
|
+
.slice(start - 1, end)
|
|
211
|
+
.map((line, i) => renderReadLine(start + i, line, { truncateLongLine: false }))
|
|
212
|
+
.join('\n');
|
|
213
|
+
if (out.length > maxChars) {
|
|
214
|
+
const head = out.slice(0, Math.floor(maxChars * 0.6));
|
|
215
|
+
const tail = out.slice(Math.max(0, out.length - Math.floor(maxChars * 0.4)));
|
|
216
|
+
out = `${head}\n... [context middle omitted] ...\n${tail}`;
|
|
217
|
+
}
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function compactEditContext(content, startLine, endLine, opts = {}) {
|
|
222
|
+
const lines = String(content ?? '').split('\n');
|
|
223
|
+
const total = Math.max(1, lines.length);
|
|
224
|
+
const maxLines = Math.max(1, Math.min(20, Number(opts.maxLines) || 20));
|
|
225
|
+
const targetStart = Math.max(1, Math.min(total, Number(startLine) || 1));
|
|
226
|
+
const targetEnd = Math.max(targetStart, Math.min(total, Number(endLine) || targetStart));
|
|
227
|
+
const targetLines = Math.max(1, targetEnd - targetStart + 1);
|
|
228
|
+
const extra = Math.max(0, maxLines - targetLines);
|
|
229
|
+
let start = Math.max(1, targetStart - Math.floor(extra / 2));
|
|
230
|
+
let end = Math.min(total, start + maxLines - 1);
|
|
231
|
+
start = Math.max(1, Math.min(start, Math.max(1, end - maxLines + 1)));
|
|
232
|
+
const range = { startLine: start, endLine: end };
|
|
233
|
+
const maxLineChars = Math.max(80, Math.min(240, Number(opts.maxLineChars) || 180));
|
|
234
|
+
let text = lines.slice(start - 1, end).map((line, i) => {
|
|
235
|
+
let s = String(line ?? '');
|
|
236
|
+
if (s.length > maxLineChars) s = `${s.slice(0, Math.max(20, maxLineChars - 24))} ... [line truncated]`;
|
|
237
|
+
return renderReadLine(start + i, s, { truncateLongLine: false });
|
|
238
|
+
}).join('\n');
|
|
239
|
+
const maxChars = Math.max(300, Math.min(1800, Number(opts.maxChars) || 1400));
|
|
240
|
+
if (text.length > maxChars) text = `${text.slice(0, maxChars - 28)}\n... [context truncated]`;
|
|
241
|
+
return { range, text };
|
|
242
|
+
}
|