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,126 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { findActualString } from '../edit-normalize.mjs';
|
|
3
|
+
import { normalizeOutputPath } from './path-utils.mjs';
|
|
4
|
+
import { READ_MAX_SIZE_BYTES } from './read-constants.mjs';
|
|
5
|
+
import { isBinaryFile } from './binary-file.mjs';
|
|
6
|
+
import { hashText } from './hash-utils.mjs';
|
|
7
|
+
import { rangeHashesForReadRanges } from './snapshot-helpers.mjs';
|
|
8
|
+
import {
|
|
9
|
+
compactEditContext,
|
|
10
|
+
countOccurrences,
|
|
11
|
+
lineContextAround,
|
|
12
|
+
} from './edit-context-utils.mjs';
|
|
13
|
+
import { findEditHint } from './edit-hint.mjs';
|
|
14
|
+
import { recordReadSnapshot } from './read-snapshot-runtime.mjs';
|
|
15
|
+
|
|
16
|
+
export function recordPreviewSnapshot(fullPath, scope, content, range) {
|
|
17
|
+
if (!fullPath || !range) return false;
|
|
18
|
+
try {
|
|
19
|
+
recordReadSnapshot(fullPath, undefined, scope, {
|
|
20
|
+
source: 'edit_failure_preview',
|
|
21
|
+
ranges: [range],
|
|
22
|
+
rangeHashes: rangeHashesForReadRanges(content, [range]),
|
|
23
|
+
});
|
|
24
|
+
return true;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function editFailureContextHint(content, startLine, endLine, options = {}, meta = {}) {
|
|
31
|
+
if (options.includePreview === false) return '';
|
|
32
|
+
const rendered = compactEditContext(content, startLine, endLine, {
|
|
33
|
+
maxLines: options.previewMaxLines || 20,
|
|
34
|
+
maxChars: options.previewMaxChars || 1400,
|
|
35
|
+
});
|
|
36
|
+
const canRecord = options.recordPreviewSnapshot === true && meta.matchesLength === 1;
|
|
37
|
+
const recorded = canRecord
|
|
38
|
+
? recordPreviewSnapshot(options.fullPath, options.scope, options.snapshotContent ?? content, rendered.range)
|
|
39
|
+
: false;
|
|
40
|
+
return `\n context ${rendered.range.startLine}-${rendered.range.endLine}${recorded ? ' (snapshot recorded)' : ''}:\n${rendered.text}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function buildStaleEditRecovery({ fullPath, scope, oldStrings = [], recordPreviewSnapshot: shouldRecordPreview = false } = {}) {
|
|
44
|
+
let content = '';
|
|
45
|
+
try { content = readFileSync(fullPath, 'utf-8'); }
|
|
46
|
+
catch { return ''; }
|
|
47
|
+
const candidates = (Array.isArray(oldStrings) ? oldStrings : [])
|
|
48
|
+
.map((entry) => typeof entry === 'string' ? entry : entry?.old_string)
|
|
49
|
+
.filter((s) => typeof s === 'string' && s.length > 0)
|
|
50
|
+
.slice(0, 3);
|
|
51
|
+
for (const oldString of candidates) {
|
|
52
|
+
const actual = findActualString(content, oldString) || oldString;
|
|
53
|
+
const count = countOccurrences(content, actual);
|
|
54
|
+
if (count !== 1) continue;
|
|
55
|
+
const idx = content.indexOf(actual);
|
|
56
|
+
const startLine = lineForIndex(content, idx);
|
|
57
|
+
const endLine = startLine + actual.split('\n').length - 1;
|
|
58
|
+
return editFailureContextHint(content, startLine, endLine, {
|
|
59
|
+
fullPath,
|
|
60
|
+
scope,
|
|
61
|
+
snapshotContent: content,
|
|
62
|
+
recordPreviewSnapshot: shouldRecordPreview,
|
|
63
|
+
previewMaxLines: 12,
|
|
64
|
+
previewMaxChars: 1000,
|
|
65
|
+
}, { matchesLength: 1 });
|
|
66
|
+
}
|
|
67
|
+
if (candidates[0]) {
|
|
68
|
+
const hint = findEditHint(content, candidates[0], null);
|
|
69
|
+
if (hint) return `\n current file:${hint}`;
|
|
70
|
+
}
|
|
71
|
+
return '';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function lineForIndex(content, index) {
|
|
75
|
+
if (index <= 0) return 1;
|
|
76
|
+
const end = Math.min(index, content.length);
|
|
77
|
+
let lineNo = 1;
|
|
78
|
+
for (let i = 0; i < end; i++) {
|
|
79
|
+
if (content.charCodeAt(i) === 10) lineNo++;
|
|
80
|
+
}
|
|
81
|
+
return lineNo;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function primeReadSnapshotForEdit({ fullPath, filePath, st, scope, oldStrings = [], lineRange = null }) {
|
|
85
|
+
if (!/^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_EDIT_AUTO_SNAPSHOT || ''))) return null;
|
|
86
|
+
if (!st || st.size > READ_MAX_SIZE_BYTES || isBinaryFile(fullPath, st.size)) return null;
|
|
87
|
+
let rawBuf;
|
|
88
|
+
try { rawBuf = readFileSync(fullPath); }
|
|
89
|
+
catch { return null; }
|
|
90
|
+
if (!Buffer.isBuffer(rawBuf)) return null;
|
|
91
|
+
const content = rawBuf.toString('utf-8');
|
|
92
|
+
const lines = content.split('\n');
|
|
93
|
+
recordReadSnapshot(fullPath, st, scope, {
|
|
94
|
+
source: 'auto_snapshot',
|
|
95
|
+
contentHash: hashText(content),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const out = [
|
|
99
|
+
`Edit snapshot primed from disk for ${normalizeOutputPath(filePath)} (no prior read in this scope).`,
|
|
100
|
+
];
|
|
101
|
+
const checks = [];
|
|
102
|
+
let firstContext = null;
|
|
103
|
+
for (let i = 0; i < Math.min(oldStrings.length, 5); i++) {
|
|
104
|
+
const entry = oldStrings[i] || {};
|
|
105
|
+
const label = entry.label || `edit ${i}`;
|
|
106
|
+
const oldString = entry.old_string;
|
|
107
|
+
if (typeof oldString !== 'string' || oldString.length === 0) continue;
|
|
108
|
+
const count = countOccurrences(content, oldString);
|
|
109
|
+
checks.push(`${label}: old_string ${count === 1 ? 'found once' : count === 0 ? 'not found' : `found ${count} times`}`);
|
|
110
|
+
if (!firstContext && count > 0) {
|
|
111
|
+
const idx = content.indexOf(oldString);
|
|
112
|
+
const startLine = lineForIndex(content, idx);
|
|
113
|
+
const endLine = startLine + oldString.split('\n').length - 1;
|
|
114
|
+
firstContext = lineContextAround(content, startLine, endLine);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (checks.length > 0) out.push(`Match check: ${checks.join('; ')}`);
|
|
118
|
+
if (lineRange) {
|
|
119
|
+
out.push(`Line check: requested ${lineRange.startLine}-${lineRange.endLine}; file has ${lines.length} lines.`);
|
|
120
|
+
out.push(`Context around requested lines:\n${lineContextAround(content, lineRange.startLine, lineRange.endLine)}`);
|
|
121
|
+
} else if (firstContext) {
|
|
122
|
+
out.push(`Context around first match:\n${firstContext}`);
|
|
123
|
+
}
|
|
124
|
+
const diagnostic = out.join('\n');
|
|
125
|
+
return { content, rawBuf, diagnostic: diagnostic || undefined };
|
|
126
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { snapshotCoversFullFile } from './snapshot-helpers.mjs';
|
|
2
|
+
import {
|
|
3
|
+
firstDivergence,
|
|
4
|
+
renderCodepointPreview,
|
|
5
|
+
describeFirstDivergence,
|
|
6
|
+
renderCharForDiff,
|
|
7
|
+
} from './edit-diagnostics.mjs';
|
|
8
|
+
import { editFailureSuffix } from './edit-match-utils.mjs';
|
|
9
|
+
|
|
10
|
+
function leadingWhitespaceColumns(line) {
|
|
11
|
+
let cols = 0;
|
|
12
|
+
for (const ch of String(line ?? '')) {
|
|
13
|
+
if (ch === ' ') cols++;
|
|
14
|
+
else if (ch === '\t') cols += 4;
|
|
15
|
+
else break;
|
|
16
|
+
}
|
|
17
|
+
return cols;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatIndentKind(line) {
|
|
21
|
+
const raw = /^[ \t]*/.exec(String(line ?? ''))?.[0] || '';
|
|
22
|
+
if (!raw) return 'none';
|
|
23
|
+
const spaces = (raw.match(/ /g) || []).length;
|
|
24
|
+
const tabs = (raw.match(/\t/g) || []).length;
|
|
25
|
+
return [spaces ? `${spaces} spaces` : '', tabs ? `${tabs} tabs` : ''].filter(Boolean).join(' + ');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function indentMismatchHint(sentLine, fileLine) {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function firstLineFallbackStageHint(content, line) {
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function editRetryCallHint(options, candidate) {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function editNearestReadHint(options, line) {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Lightweight nearest-match hint for `Error [code 8]: old_string not
|
|
45
|
+
// found`. Probes by the first non-empty line of `old_string` (trimmed,
|
|
46
|
+
// capped at 60 chars then 30) so callers see where they likely meant
|
|
47
|
+
// to land. Substring only, no fuzzy diff, to keep the failure path cheap.
|
|
48
|
+
export function findEditHint(content, oldStr, snapshot = null, options = {}) {
|
|
49
|
+
const oldLines = String(oldStr || '').split(/\r?\n/);
|
|
50
|
+
const firstNonEmptyIndex = oldLines.findIndex((l) => l.trim().length > 0);
|
|
51
|
+
const firstNonEmpty = firstNonEmptyIndex >= 0 ? oldLines[firstNonEmptyIndex] : '';
|
|
52
|
+
const trimmed = firstNonEmpty.trim();
|
|
53
|
+
const editIdx = Number.isInteger(options?.editIndex) ? options.editIndex : null;
|
|
54
|
+
const idxTag = editIdx !== null ? ` [edit ${editIdx}]` : '';
|
|
55
|
+
if (trimmed.length < 8) {
|
|
56
|
+
// No probe long enough to anchor a nearest-match search. Still emit
|
|
57
|
+
// an invariant divergence line so the caller knows where the closest
|
|
58
|
+
// prefix landed instead of getting silence.
|
|
59
|
+
const div = describeFirstDivergence(content, oldStr);
|
|
60
|
+
if (!div) return '';
|
|
61
|
+
return `${idxTag ? `\n edit ${editIdx} miss:` : ''}`
|
|
62
|
+
+ `\n diverge${idxTag}: at line ${div.line} col ${div.col} expected ${renderCharForDiff(div.expected)} but file has ${renderCharForDiff(div.found)} (common prefix ${div.prefixLen} chars from line ${div.startLine} col ${div.startCol})`;
|
|
63
|
+
}
|
|
64
|
+
const probes = [trimmed.slice(0, 60), trimmed.slice(0, 30)].filter((p) => p.length >= 8);
|
|
65
|
+
const lines = String(content).split('\n');
|
|
66
|
+
|
|
67
|
+
let normHint = '';
|
|
68
|
+
try {
|
|
69
|
+
const rawContent = String(content);
|
|
70
|
+
const rawOld = String(oldStr);
|
|
71
|
+
if (rawContent.indexOf(rawOld) === -1) {
|
|
72
|
+
const nfcContent = rawContent.normalize('NFC');
|
|
73
|
+
const nfcOld = rawOld.normalize('NFC');
|
|
74
|
+
const nfdContent = rawContent.normalize('NFD');
|
|
75
|
+
const nfdOld = rawOld.normalize('NFD');
|
|
76
|
+
if (nfcOld !== rawOld && nfcContent.indexOf(nfcOld) !== -1) {
|
|
77
|
+
normHint = ' Unicode normalisation mismatch: NFC-normalising old_string matches the file. Re-send old_string in NFC form (e.g. JS `s.normalize("NFC")`).';
|
|
78
|
+
} else if (nfdOld !== rawOld && nfdContent.indexOf(nfdOld) !== -1 && nfcContent.indexOf(nfcOld) === -1) {
|
|
79
|
+
normHint = ' Unicode normalisation mismatch: NFD form of old_string matches the file but NFC does not. The file likely stores NFD; re-send old_string in NFD form.';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch {}
|
|
83
|
+
|
|
84
|
+
let winStart = 1;
|
|
85
|
+
let winEnd = lines.length;
|
|
86
|
+
if (snapshot && !snapshotCoversFullFile(snapshot)) {
|
|
87
|
+
const ranges = Array.isArray(snapshot.ranges) ? snapshot.ranges : [];
|
|
88
|
+
if (ranges.length === 0) return normHint;
|
|
89
|
+
winStart = ranges[0].startLine;
|
|
90
|
+
const last = ranges[ranges.length - 1];
|
|
91
|
+
winEnd = last.endLine === Infinity ? lines.length : Math.min(lines.length, last.endLine);
|
|
92
|
+
}
|
|
93
|
+
for (const probe of probes) {
|
|
94
|
+
for (let i = winStart - 1; i < winEnd; i++) {
|
|
95
|
+
if (lines[i] !== undefined && lines[i].includes(probe)) {
|
|
96
|
+
const preview = lines[i].length > 80 ? lines[i].slice(0, 77) + '...' : lines[i];
|
|
97
|
+
const sentPreview = renderCodepointPreview(firstNonEmpty, 40);
|
|
98
|
+
const filePreview = renderCodepointPreview(lines[i], 40);
|
|
99
|
+
const probeIdx = lines[i].indexOf(probe);
|
|
100
|
+
const sliceForDiff = probeIdx >= 0 ? lines[i].slice(probeIdx, probeIdx + trimmed.length) : lines[i];
|
|
101
|
+
const div = firstDivergence(trimmed, sliceForDiff);
|
|
102
|
+
// Translate the intra-slice char offset back into absolute
|
|
103
|
+
// file line+col so the operator can jump straight to the
|
|
104
|
+
// mismatch. lines[] is split on \n, so col is 1-based within
|
|
105
|
+
// the line. The probe sits at probeIdx (0-based) inside the
|
|
106
|
+
// nearest line; the divergence sits div.index codepoints
|
|
107
|
+
// further along. Fall back to `describeFirstDivergence` when
|
|
108
|
+
// the probe-vs-trimmed compare reports no divergence (i.e.
|
|
109
|
+
// probe is a true prefix of the slice) — we still want a
|
|
110
|
+
// global coordinate against the full old_string.
|
|
111
|
+
let divLine = '';
|
|
112
|
+
if (div) {
|
|
113
|
+
const lineNo = i + 1;
|
|
114
|
+
const col = (probeIdx >= 0 ? probeIdx : 0) + div.index + 1;
|
|
115
|
+
divLine = `\n diverge${idxTag}: at line ${lineNo} col ${col} expected ${renderCharForDiff(div.expected)} but file has ${renderCharForDiff(div.found)}`;
|
|
116
|
+
} else {
|
|
117
|
+
const globalDiv = describeFirstDivergence(content, oldStr);
|
|
118
|
+
if (globalDiv && globalDiv.prefixLen < String(oldStr).length) {
|
|
119
|
+
divLine = `\n diverge${idxTag}: at line ${globalDiv.line} col ${globalDiv.col} expected ${renderCharForDiff(globalDiv.expected)} but file has ${renderCharForDiff(globalDiv.found)} (common prefix ${globalDiv.prefixLen} chars from line ${globalDiv.startLine} col ${globalDiv.startCol})`;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return ` Nearest match${idxTag} at line ${i + 1}: ${JSON.stringify(preview)}.${normHint}`
|
|
123
|
+
+ `\n sent : ${sentPreview}`
|
|
124
|
+
+ `\n file : ${filePreview}`
|
|
125
|
+
+ divLine
|
|
126
|
+
+ indentMismatchHint(firstNonEmpty, lines[i])
|
|
127
|
+
+ firstLineFallbackStageHint(content, firstNonEmpty)
|
|
128
|
+
+ editNearestReadHint(options, i + 1)
|
|
129
|
+
+ editFailureSuffix(content, oldStr);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Invariant fallback: no probe landed, but the operator still needs a
|
|
134
|
+
// file coordinate. Run the longest-common-prefix scan against the full
|
|
135
|
+
// old_string so the miss is debuggable rather than silent.
|
|
136
|
+
const globalDiv = describeFirstDivergence(content, oldStr);
|
|
137
|
+
const globalLine = globalDiv && globalDiv.prefixLen < String(oldStr).length
|
|
138
|
+
? `\n diverge${idxTag}: at line ${globalDiv.line} col ${globalDiv.col} expected ${renderCharForDiff(globalDiv.expected)} but file has ${renderCharForDiff(globalDiv.found)} (common prefix ${globalDiv.prefixLen} chars from line ${globalDiv.startLine} col ${globalDiv.startCol})`
|
|
139
|
+
: '';
|
|
140
|
+
return normHint + globalLine + editFailureSuffix(content, oldStr);
|
|
141
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
export function editFailureSuffix(content, oldStr) {
|
|
2
|
+
return '';
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function validateEditChunkSize(oldStr, replaceAll, allowLarge) {
|
|
6
|
+
if (replaceAll || allowLarge) return null;
|
|
7
|
+
const lines = String(oldStr || '').split(/\r?\n/).length;
|
|
8
|
+
if (lines < 30) return null;
|
|
9
|
+
return `Error [code 10]: old_string is ${lines} lines (>= 30).`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function occurrenceLinesPlain(content, needle, max = 3) {
|
|
13
|
+
if (typeof needle !== 'string' || needle.length === 0) return [];
|
|
14
|
+
const lines = [];
|
|
15
|
+
let pos = 0, scanned = 0, lineNo = 1;
|
|
16
|
+
while (lines.length < max) {
|
|
17
|
+
const idx = content.indexOf(needle, pos);
|
|
18
|
+
if (idx === -1) break;
|
|
19
|
+
for (let i = scanned; i < idx; i++) {
|
|
20
|
+
if (content.charCodeAt(i) === 10) lineNo++;
|
|
21
|
+
}
|
|
22
|
+
scanned = idx;
|
|
23
|
+
lines.push(lineNo);
|
|
24
|
+
pos = idx + needle.length;
|
|
25
|
+
}
|
|
26
|
+
return lines;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function occurrenceLinesCrlf(content, ranges, max = 3) {
|
|
30
|
+
if (!Array.isArray(ranges) || ranges.length === 0) return [];
|
|
31
|
+
const lines = [];
|
|
32
|
+
let scanned = 0, lineNo = 1;
|
|
33
|
+
for (let k = 0; k < Math.min(max, ranges.length); k++) {
|
|
34
|
+
const idx = ranges[k].start;
|
|
35
|
+
for (let i = scanned; i < idx; i++) {
|
|
36
|
+
if (content.charCodeAt(i) === 10) lineNo++;
|
|
37
|
+
}
|
|
38
|
+
scanned = idx;
|
|
39
|
+
lines.push(lineNo);
|
|
40
|
+
}
|
|
41
|
+
return lines;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function formatMatchLines(linesArr, totalCount) {
|
|
45
|
+
if (linesArr.length === 0) return '';
|
|
46
|
+
const more = totalCount > linesArr.length ? ` (+${totalCount - linesArr.length} more)` : '';
|
|
47
|
+
return ` Matches at lines: ${linesArr.join(', ')}${more}.`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function buildCrlfNormalisedViewWithMap(text) {
|
|
51
|
+
const source = String(text ?? '');
|
|
52
|
+
let normalised = '';
|
|
53
|
+
const map = [];
|
|
54
|
+
for (let i = 0; i < source.length;) {
|
|
55
|
+
map[normalised.length] = i;
|
|
56
|
+
if (source[i] === '\r' && source[i + 1] === '\n') {
|
|
57
|
+
normalised += '\n';
|
|
58
|
+
i += 2;
|
|
59
|
+
} else {
|
|
60
|
+
normalised += source[i];
|
|
61
|
+
i += 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
map[normalised.length] = source.length;
|
|
65
|
+
return { normalised, map };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function findCrlfNormalisedMatches(content, oldStr) {
|
|
69
|
+
if (typeof oldStr !== 'string' || oldStr.length === 0) return null;
|
|
70
|
+
if (String(content).indexOf('\r\n') === -1 && oldStr.indexOf('\r\n') === -1) return null;
|
|
71
|
+
const { normalised, map } = buildCrlfNormalisedViewWithMap(content);
|
|
72
|
+
const normalisedOld = oldStr.replace(/\r\n/g, '\n');
|
|
73
|
+
if (normalisedOld.length === 0 || normalisedOld === oldStr && normalised === content) return null;
|
|
74
|
+
const ranges = [];
|
|
75
|
+
let idx = 0;
|
|
76
|
+
while ((idx = normalised.indexOf(normalisedOld, idx)) !== -1) {
|
|
77
|
+
ranges.push({
|
|
78
|
+
normStart: idx,
|
|
79
|
+
normEnd: idx + normalisedOld.length,
|
|
80
|
+
start: map[idx],
|
|
81
|
+
end: map[idx + normalisedOld.length],
|
|
82
|
+
});
|
|
83
|
+
idx += normalisedOld.length;
|
|
84
|
+
}
|
|
85
|
+
return { normalised, normalisedOld, ranges };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function replacementForOriginalSlice(newStr, originalSlice, fileContent) {
|
|
89
|
+
if (typeof newStr !== 'string') return newStr;
|
|
90
|
+
const slice = String(originalSlice);
|
|
91
|
+
if (!slice.includes('\r\n') && !slice.includes('\n')) {
|
|
92
|
+
if (typeof fileContent !== 'string') return newStr;
|
|
93
|
+
if (fileContent.indexOf('\r\n') === -1) return newStr;
|
|
94
|
+
// Pure-CRLF file: every bare \n is part of a \r\n pair. Mixed
|
|
95
|
+
// files (any bare \n that is not preceded by \r) stay untouched
|
|
96
|
+
// so we don't synthesise CRLF where the file uses LF.
|
|
97
|
+
for (let i = 0; i < fileContent.length; i += 1) {
|
|
98
|
+
if (fileContent.charCodeAt(i) === 10 && (i === 0 || fileContent.charCodeAt(i - 1) !== 13)) {
|
|
99
|
+
return newStr;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return newStr.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n');
|
|
103
|
+
}
|
|
104
|
+
const lfReplacement = newStr.replace(/\r\n/g, '\n');
|
|
105
|
+
let result = slice.includes('\r\n')
|
|
106
|
+
? lfReplacement.replace(/\n/g, '\r\n')
|
|
107
|
+
: lfReplacement;
|
|
108
|
+
// A matched slice can end on the last line's trailing EOL CR (split
|
|
109
|
+
// CRLF) while newStr — typically copied from a \r-stripped read view —
|
|
110
|
+
// has none. If the replacement ends mid-line, re-attach that consumed
|
|
111
|
+
// CR so the following LF still forms a \r\n instead of silently
|
|
112
|
+
// degrading to a lone \n (mixed-EOL corruption). A slice ending in a
|
|
113
|
+
// bare CR is always a split CRLF; the end-of-line guard avoids
|
|
114
|
+
// appending a stray CR when the replacement already carries its own
|
|
115
|
+
// line ending.
|
|
116
|
+
if (slice.endsWith('\r') && !/[\r\n]$/.test(result)) result += '\r';
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function replaceRangesFromOriginal(content, ranges, newStr) {
|
|
121
|
+
if (!Array.isArray(ranges) || ranges.length === 0) return content;
|
|
122
|
+
const sorted = ranges.slice().sort((a, b) => a.start - b.start);
|
|
123
|
+
let cursor = 0;
|
|
124
|
+
let out = '';
|
|
125
|
+
for (const range of sorted) {
|
|
126
|
+
if (!range || range.start < cursor || range.end < range.start) continue;
|
|
127
|
+
out += content.slice(cursor, range.start);
|
|
128
|
+
const originalSlice = content.slice(range.start, range.end);
|
|
129
|
+
out += replacementForOriginalSlice(newStr, originalSlice);
|
|
130
|
+
cursor = range.end;
|
|
131
|
+
}
|
|
132
|
+
return out + content.slice(cursor);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function replaceSingleLiteralAt(content, index, needle, replacement) {
|
|
136
|
+
return content.slice(0, index) + replacement + content.slice(index + needle.length);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function findLiteralOccurrenceState(haystack, needle) {
|
|
140
|
+
if (!needle) return { count: 0, index: -1 };
|
|
141
|
+
const first = haystack.indexOf(needle);
|
|
142
|
+
if (first === -1) return { count: 0, index: -1 };
|
|
143
|
+
// Overlap-aware ambiguity: `aa` in `aaa` has a second hit at first+1,
|
|
144
|
+
// not only at first+needle.length (which misses overlapping pairs).
|
|
145
|
+
const second = haystack.indexOf(needle, first + 1);
|
|
146
|
+
if (second === -1) return { count: 1, index: first };
|
|
147
|
+
return { count: 2, index: first };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function countLiteralOccurrences(haystack, needle, limit = Infinity) {
|
|
151
|
+
if (!needle) return 0;
|
|
152
|
+
let count = 0;
|
|
153
|
+
let idx = 0;
|
|
154
|
+
while ((idx = haystack.indexOf(needle, idx)) !== -1) {
|
|
155
|
+
count++;
|
|
156
|
+
if (count >= limit) return count;
|
|
157
|
+
idx += 1;
|
|
158
|
+
}
|
|
159
|
+
return count;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function diagnoseOtherEdits(content, edits, failedIndex) {
|
|
163
|
+
if (!Array.isArray(edits) || edits.length <= 1) return '';
|
|
164
|
+
let okCount = 0;
|
|
165
|
+
const problems = [];
|
|
166
|
+
for (let j = 0; j < edits.length; j++) {
|
|
167
|
+
if (j === failedIndex) continue;
|
|
168
|
+
const e = edits[j];
|
|
169
|
+
if (!e || typeof e.old_string !== 'string' || typeof e.new_string !== 'string') {
|
|
170
|
+
problems.push(`${j}=invalid`); continue;
|
|
171
|
+
}
|
|
172
|
+
const oldStr = e.old_string;
|
|
173
|
+
if (oldStr.length === 0) { problems.push(`${j}=empty`); continue; }
|
|
174
|
+
const cnt = countLiteralOccurrences(content, oldStr);
|
|
175
|
+
if (cnt === 1) { okCount++; continue; }
|
|
176
|
+
if (cnt > 1) {
|
|
177
|
+
if (e.replace_all === true) okCount++;
|
|
178
|
+
else problems.push(`${j}=ambig(${cnt})`);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const crlf = findCrlfNormalisedMatches(content, oldStr);
|
|
182
|
+
const crlfCnt = crlf ? crlf.ranges.length : 0;
|
|
183
|
+
if (crlfCnt === 1) { okCount++; continue; }
|
|
184
|
+
if (crlfCnt > 1) {
|
|
185
|
+
if (e.replace_all === true) okCount++;
|
|
186
|
+
else problems.push(`${j}=ambig(${crlfCnt})`);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
problems.push(`${j}=miss`);
|
|
190
|
+
}
|
|
191
|
+
if (problems.length === 0) return '';
|
|
192
|
+
const head = okCount > 0 ? `${okCount} ok, ` : '';
|
|
193
|
+
return `\n Peers: ${head}${problems.join(', ')}`;
|
|
194
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { openSync, writeSync, closeSync, fsyncSync, statSync } from 'fs';
|
|
2
|
+
import { normalizeErrorMessage } from './path-diagnostics.mjs';
|
|
3
|
+
import { hashText } from './hash-utils.mjs';
|
|
4
|
+
import { partialByteWriteEnabled } from './edit-byte-utils.mjs';
|
|
5
|
+
import { atomicWriteShouldFsync } from './atomic-write.mjs';
|
|
6
|
+
|
|
7
|
+
function partialWriteHooks(hooks = {}) {
|
|
8
|
+
return {
|
|
9
|
+
ioTraceStart: typeof hooks.ioTraceStart === 'function' ? hooks.ioTraceStart : () => 0,
|
|
10
|
+
ioTraceDone: typeof hooks.ioTraceDone === 'function' ? hooks.ioTraceDone : () => {},
|
|
11
|
+
validatePreparedEditBase: typeof hooks.validatePreparedEditBase === 'function' ? hooks.validatePreparedEditBase : () => null,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function tryWriteSameSizeByteReplacementsSync(fullPath, replacements, { baseStatSnapshot, baseMutationGeneration, baseContentHash, contentHash, fsync, filePath } = {}, hooks = {}) {
|
|
16
|
+
const { ioTraceStart, ioTraceDone, validatePreparedEditBase } = partialWriteHooks(hooks);
|
|
17
|
+
if (!partialByteWriteEnabled()) return null;
|
|
18
|
+
if (!Array.isArray(replacements) || replacements.length === 0 || !baseStatSnapshot) return null;
|
|
19
|
+
const sorted = replacements.slice().sort((a, b) => a.start - b.start || a.end - b.end);
|
|
20
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
21
|
+
const span = sorted[i];
|
|
22
|
+
if (!span || !Buffer.isBuffer(span.newBytes)) return null;
|
|
23
|
+
if (!Number.isFinite(span.start) || !Number.isFinite(span.end) || span.start < 0 || span.end < span.start) return null;
|
|
24
|
+
if (span.end - span.start !== span.newBytes.length) return null;
|
|
25
|
+
if (i > 0 && span.start < sorted[i - 1].end) return null;
|
|
26
|
+
}
|
|
27
|
+
const prewriteErr = validatePreparedEditBase({
|
|
28
|
+
fullPath,
|
|
29
|
+
filePath: filePath || fullPath,
|
|
30
|
+
baseStatSnapshot,
|
|
31
|
+
baseMutationGeneration,
|
|
32
|
+
baseContentHash,
|
|
33
|
+
});
|
|
34
|
+
if (prewriteErr) return { ok: false, error: prewriteErr };
|
|
35
|
+
const traceStart = ioTraceStart();
|
|
36
|
+
let fd = null;
|
|
37
|
+
try {
|
|
38
|
+
fd = openSync(fullPath, 'r+');
|
|
39
|
+
for (const span of sorted) {
|
|
40
|
+
writeSync(fd, span.newBytes, 0, span.newBytes.length, span.start);
|
|
41
|
+
}
|
|
42
|
+
if (atomicWriteShouldFsync(fsync)) {
|
|
43
|
+
try { fsyncSync(fd); } catch (err) {
|
|
44
|
+
if (!['EPERM', 'ENOTSUP', 'EINVAL'].includes(err?.code)) throw err;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch (err) {
|
|
48
|
+
return { ok: false, error: `Error: partial byte write failed — ${normalizeErrorMessage(err instanceof Error ? err.message : String(err))}` };
|
|
49
|
+
} finally {
|
|
50
|
+
try { if (fd !== null) closeSync(fd); } catch {}
|
|
51
|
+
}
|
|
52
|
+
let stat = null;
|
|
53
|
+
try { stat = statSync(fullPath); } catch {}
|
|
54
|
+
ioTraceDone('edit_partial_write', traceStart, {
|
|
55
|
+
pathHash: hashText(fullPath).slice(0, 12),
|
|
56
|
+
replacements: sorted.length,
|
|
57
|
+
bytes: sorted.reduce((sum, span) => sum + span.newBytes.length, 0),
|
|
58
|
+
});
|
|
59
|
+
return { ok: true, stat, contentHash };
|
|
60
|
+
}
|