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,603 @@
|
|
|
1
|
+
// Edit input normalization helpers — extracted from builtin.mjs so the
|
|
2
|
+
// matching tiers (curly-quote fold / NFC-fold) can be tested in isolation
|
|
3
|
+
// and audited without scrolling through the full tool dispatcher.
|
|
4
|
+
//
|
|
5
|
+
import { findCrlfNormalisedMatches } from './builtin/edit-match-utils.mjs';
|
|
6
|
+
//
|
|
7
|
+
// HARD RULE — tier ordering + ambiguity gates. Invariant-safe tiers run
|
|
8
|
+
// first (byte-exact, curly-quote fold, NFC-fold, CRLF-fold). Last-resort Claude-Code
|
|
9
|
+
// parity tiers (rstrip-fold, indent-fold, eol-fold) are lossy views but
|
|
10
|
+
// safe: each runs only after invariant-safe tiers (including CRLF-fold) miss, rejects >1 folded
|
|
11
|
+
// hit with code 9 (diagnoseFoldTierAmbiguity), and always replaces a real
|
|
12
|
+
// content.substring slice — never synthesised folded bytes. Still removed:
|
|
13
|
+
// case/dash/fullwidth/Unicode-space folding and unconstrained indent-shift
|
|
14
|
+
// (whole-block re-indent), which can alias unrelated regions.
|
|
15
|
+
//
|
|
16
|
+
// All helpers preserve the same byte-exact invariant: matchers return a
|
|
17
|
+
// real substring of `content` (never a synthesised one), and length-
|
|
18
|
+
// preserving folds keep input.length so an indexOf hit re-indexes the
|
|
19
|
+
// original bytes. See per-function comments for stage-specific guards.
|
|
20
|
+
|
|
21
|
+
// CC parity: curly-quote tolerant match. Models can't reliably emit `‘`
|
|
22
|
+
// / `’` / `“` / `”`, so when a file has curly quotes but
|
|
23
|
+
// old_string uses straight (or vice versa) the literal indexOf misses.
|
|
24
|
+
// This is the ONLY fold retained in this stage and it is invariant-safe:
|
|
25
|
+
// a curly quote and its straight counterpart are the same character in a
|
|
26
|
+
// different typographic encoding. Each mapped codepoint is a single
|
|
27
|
+
// UTF-16 code unit (BMP only, no surrogate pairs), so an `indexOf` hit in
|
|
28
|
+
// the folded view re-indexes the original file substring without
|
|
29
|
+
// arithmetic adjustment — the caller can
|
|
30
|
+
// `content.substring(idx, idx + searchStr.length)` and get a byte-exact
|
|
31
|
+
// slice back.
|
|
32
|
+
//
|
|
33
|
+
// Folded codepoints:
|
|
34
|
+
// Single → "'" : U+2018-U+201B (curly + low/high-reversed-9).
|
|
35
|
+
// Double → '"' : U+201C-U+201F (curly + low/high-reversed-9).
|
|
36
|
+
//
|
|
37
|
+
// Deliberately NOT folded (would be heuristic-risky, not invariant-safe):
|
|
38
|
+
// case, dash/minus variants, fullwidth forms, and Unicode whitespace —
|
|
39
|
+
// each can map text that is genuinely different to the same view and hit
|
|
40
|
+
// the wrong location. NFC / NFD is handled in its own invariant-safe tier
|
|
41
|
+
// (nfcFoldMatch). ZWSP / BOM remain hint-only.
|
|
42
|
+
export const MATCH_FOLD_RE = /[‘-‟]/g;
|
|
43
|
+
export function normalizeForMatch(s) {
|
|
44
|
+
return String(s).replace(MATCH_FOLD_RE, (c) => {
|
|
45
|
+
const code = c.charCodeAt(0);
|
|
46
|
+
if (code >= 0x2018 && code <= 0x201B) return "'";
|
|
47
|
+
return '"';
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// NFC/NFD fold helper. Returns the byte-exact slice from `content` whose
|
|
52
|
+
// normalised form equals the normalised search, or null if no unique
|
|
53
|
+
// safe match exists.
|
|
54
|
+
//
|
|
55
|
+
// Why NFD (not NFC) is the comparison medium: NFC composition is NOT
|
|
56
|
+
// char-by-char — multiple input chars can collapse into one (e.g. Hangul
|
|
57
|
+
// `각` → `각`). That breaks any attempt to map an
|
|
58
|
+
// index in nfcContent back to original char positions via per-char NFC
|
|
59
|
+
// length accumulation. NFD decomposition IS char-by-char additive: each
|
|
60
|
+
// input char produces its own NFD output independent of neighbours, so
|
|
61
|
+
// per-char NFD length accumulates monotonically and origStart / origEnd
|
|
62
|
+
// are recoverable in O(N) total work.
|
|
63
|
+
//
|
|
64
|
+
// Invariants:
|
|
65
|
+
// • Returned string === content.substring(start, end) for some
|
|
66
|
+
// (start, end); never synthesised from normalised bytes.
|
|
67
|
+
// • At least one side drifts under normalisation; otherwise the exact
|
|
68
|
+
// tier already covered it (early bail).
|
|
69
|
+
// • Exactly one occurrence of nfdSearch in nfdContent — multi-match
|
|
70
|
+
// collapses to null so the caller still receives Error [code 8].
|
|
71
|
+
// • origStart lands on an original-content char boundary, not in the
|
|
72
|
+
// middle of an NFD decomposition.
|
|
73
|
+
export function nfcFoldMatch(content, searchStr) {
|
|
74
|
+
if (typeof content !== 'string' || typeof searchStr !== 'string') return null;
|
|
75
|
+
if (searchStr.length === 0 || content.length === 0) return null;
|
|
76
|
+
let nfdSearch;
|
|
77
|
+
let nfdContent;
|
|
78
|
+
try {
|
|
79
|
+
nfdSearch = searchStr.normalize('NFD');
|
|
80
|
+
nfdContent = content.normalize('NFD');
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
if (nfdSearch === searchStr && nfdContent === content) return null;
|
|
85
|
+
if (nfdSearch.length === 0) return null;
|
|
86
|
+
const firstIdx = nfdContent.indexOf(nfdSearch);
|
|
87
|
+
if (firstIdx === -1) return null;
|
|
88
|
+
if (nfdContent.indexOf(nfdSearch, firstIdx + 1) !== -1) return null;
|
|
89
|
+
let origStart = 0;
|
|
90
|
+
let nfdCursor = 0;
|
|
91
|
+
while (origStart < content.length && nfdCursor < firstIdx) {
|
|
92
|
+
nfdCursor += content[origStart].normalize('NFD').length;
|
|
93
|
+
origStart++;
|
|
94
|
+
}
|
|
95
|
+
if (nfdCursor !== firstIdx) return null;
|
|
96
|
+
const targetLen = nfdSearch.length;
|
|
97
|
+
let endNfd = 0;
|
|
98
|
+
let origEnd = origStart;
|
|
99
|
+
while (origEnd < content.length) {
|
|
100
|
+
endNfd += content[origEnd].normalize('NFD').length;
|
|
101
|
+
origEnd++;
|
|
102
|
+
if (endNfd === targetLen) {
|
|
103
|
+
const slice = content.substring(origStart, origEnd);
|
|
104
|
+
return slice.normalize('NFD') === nfdSearch ? slice : null;
|
|
105
|
+
}
|
|
106
|
+
if (endNfd > targetLen) return null;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function _lineNumbersForIndices(content, indices, max = 3) {
|
|
112
|
+
const lines = [];
|
|
113
|
+
for (let k = 0; k < Math.min(max, indices.length); k++) {
|
|
114
|
+
const idx = indices[k];
|
|
115
|
+
let lineNo = 1;
|
|
116
|
+
for (let i = 0; i < idx; i++) {
|
|
117
|
+
if (content.charCodeAt(i) === 10) lineNo++;
|
|
118
|
+
}
|
|
119
|
+
lines.push(lineNo);
|
|
120
|
+
}
|
|
121
|
+
return lines;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function _collectOverlapAwareIndices(haystack, needle, limit = 64) {
|
|
125
|
+
const indices = [];
|
|
126
|
+
let idx = 0;
|
|
127
|
+
while ((idx = haystack.indexOf(needle, idx)) !== -1) {
|
|
128
|
+
indices.push(idx);
|
|
129
|
+
if (indices.length >= limit) break;
|
|
130
|
+
idx += 1;
|
|
131
|
+
}
|
|
132
|
+
return indices;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
function _parseLineRecords(s) {
|
|
137
|
+
if (typeof s !== 'string' || s.length === 0) return [];
|
|
138
|
+
const parts = s.split(/(\r\n|\n|\r)/);
|
|
139
|
+
const records = [];
|
|
140
|
+
for (let i = 0; i < parts.length; i += 2) {
|
|
141
|
+
records.push({
|
|
142
|
+
body: parts[i] ?? '',
|
|
143
|
+
sep: i + 1 < parts.length ? parts[i + 1] : '',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return records;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function _lineRecordOffset(content, contentRecords, lineIdx) {
|
|
150
|
+
let off = 0;
|
|
151
|
+
for (let i = 0; i < lineIdx; i++) {
|
|
152
|
+
off += contentRecords[i].body.length + contentRecords[i].sep.length;
|
|
153
|
+
}
|
|
154
|
+
return off;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function _sliceFromLineWindow(content, contentRecords, startLine, searchRecords) {
|
|
158
|
+
const numLines = searchRecords.length;
|
|
159
|
+
const start = _lineRecordOffset(content, contentRecords, startLine);
|
|
160
|
+
let len = 0;
|
|
161
|
+
for (let j = 0; j < numLines; j++) {
|
|
162
|
+
const rec = contentRecords[startLine + j];
|
|
163
|
+
const sRec = searchRecords[j];
|
|
164
|
+
len += rec.body.length;
|
|
165
|
+
const isLast = j === numLines - 1;
|
|
166
|
+
if (!isLast) len += rec.sep.length;
|
|
167
|
+
else if (sRec.sep !== '') len += rec.sep.length;
|
|
168
|
+
}
|
|
169
|
+
return content.substring(start, start + len);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function _lineRecordsMatch(contentRecords, searchRecords, startLine, normalizeBody) {
|
|
173
|
+
const n = searchRecords.length;
|
|
174
|
+
if (startLine + n > contentRecords.length) return false;
|
|
175
|
+
for (let j = 0; j < n; j++) {
|
|
176
|
+
const cRec = contentRecords[startLine + j];
|
|
177
|
+
const sRec = searchRecords[j];
|
|
178
|
+
if (normalizeBody(cRec.body) !== normalizeBody(sRec.body)) return false;
|
|
179
|
+
const isLast = j === n - 1;
|
|
180
|
+
if (isLast && sRec.sep === '') continue;
|
|
181
|
+
if (cRec.sep !== sRec.sep) return false;
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function _normalizeLineBodyRstrip(body) {
|
|
187
|
+
return String(body).replace(/[ \t]+$/, '');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// CC parity: convertLeadingTabsToSpaces (leading tab run only, 2 spaces/tab).
|
|
191
|
+
function _normalizeLeadingIndentLine(body) {
|
|
192
|
+
const s = String(body);
|
|
193
|
+
if (!s.includes('\t')) return s;
|
|
194
|
+
const m = s.match(/^(\t+)(.*)$/);
|
|
195
|
+
if (!m) return s;
|
|
196
|
+
return `${' '.repeat(m[1].length)}${m[2]}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function _lineOrientedFoldMatch(content, searchStr, normalizeBody) {
|
|
200
|
+
const searchRecords = _parseLineRecords(searchStr);
|
|
201
|
+
const contentRecords = _parseLineRecords(content);
|
|
202
|
+
if (searchRecords.length === 0) return null;
|
|
203
|
+
let slice = null;
|
|
204
|
+
for (let start = 0; start + searchRecords.length <= contentRecords.length; start++) {
|
|
205
|
+
if (!_lineRecordsMatch(contentRecords, searchRecords, start, normalizeBody)) continue;
|
|
206
|
+
if (slice !== null) return null;
|
|
207
|
+
slice = _sliceFromLineWindow(content, contentRecords, start, searchRecords);
|
|
208
|
+
if (!content.includes(slice)) return null;
|
|
209
|
+
}
|
|
210
|
+
return slice;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function _diagnoseLineOrientedFold(content, searchStr, normalizeBody, stage) {
|
|
214
|
+
const searchRecords = _parseLineRecords(searchStr);
|
|
215
|
+
const contentRecords = _parseLineRecords(content);
|
|
216
|
+
const starts = [];
|
|
217
|
+
for (let start = 0; start + searchRecords.length <= contentRecords.length; start++) {
|
|
218
|
+
if (_lineRecordsMatch(contentRecords, searchRecords, start, normalizeBody)) starts.push(start);
|
|
219
|
+
}
|
|
220
|
+
if (starts.length <= 1) return null;
|
|
221
|
+
const lines = starts.map((st) => _lineNumbersForIndices(content, [_lineRecordOffset(content, contentRecords, st)], 1)[0] || 1);
|
|
222
|
+
return { stage, count: starts.length, lines };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function _eolFoldVariants(searchStr) {
|
|
226
|
+
const variants = [];
|
|
227
|
+
const endsWithCrlf = searchStr.endsWith('\r\n');
|
|
228
|
+
const endsWithLf = !endsWithCrlf && searchStr.endsWith('\n');
|
|
229
|
+
if (!endsWithCrlf && !endsWithLf) {
|
|
230
|
+
variants.push(`${searchStr}\n`, `${searchStr}\r\n`);
|
|
231
|
+
} else if (endsWithCrlf) {
|
|
232
|
+
const bare = searchStr.slice(0, -2);
|
|
233
|
+
if (bare.length > 0) variants.push(bare);
|
|
234
|
+
} else if (endsWithLf) {
|
|
235
|
+
const bare = searchStr.slice(0, -1);
|
|
236
|
+
if (bare.length > 0) variants.push(bare);
|
|
237
|
+
}
|
|
238
|
+
return variants;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function _isTrailingEolBoundary(content, endIdx) {
|
|
242
|
+
if (endIdx >= content.length) return true;
|
|
243
|
+
if (content.charCodeAt(endIdx) === 10) return true;
|
|
244
|
+
if (content.charCodeAt(endIdx) === 13 && content.charCodeAt(endIdx + 1) === 10) return true;
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function _sliceAtTrailingEolBoundary(content, start, bare, searchStr) {
|
|
249
|
+
const bareEnd = start + bare.length;
|
|
250
|
+
if (!_isTrailingEolBoundary(content, bareEnd)) return null;
|
|
251
|
+
if (bareEnd === content.length) return content.substring(start, bareEnd);
|
|
252
|
+
if (content.charCodeAt(bareEnd) === 13 && content.charCodeAt(bareEnd + 1) === 10) {
|
|
253
|
+
return content.substring(start, bareEnd + 2);
|
|
254
|
+
}
|
|
255
|
+
if (content.charCodeAt(bareEnd) === 10) return content.substring(start, bareEnd + 1);
|
|
256
|
+
return content.substring(start, bareEnd);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function _collectEolBoundaryBareIndices(content, bare) {
|
|
260
|
+
const indices = [];
|
|
261
|
+
let idx = 0;
|
|
262
|
+
while ((idx = content.indexOf(bare, idx)) !== -1) {
|
|
263
|
+
if (_isTrailingEolBoundary(content, idx + bare.length)) indices.push(idx);
|
|
264
|
+
idx += 1;
|
|
265
|
+
}
|
|
266
|
+
return indices;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function crlfFoldMatch(content, searchStr) {
|
|
270
|
+
const match = findCrlfNormalisedMatches(content, searchStr);
|
|
271
|
+
if (!match || match.ranges.length !== 1) return null;
|
|
272
|
+
const { start, end } = match.ranges[0];
|
|
273
|
+
return content.substring(start, end);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function _diagnoseCrlfFoldAmbiguity(content, searchStr) {
|
|
277
|
+
const match = findCrlfNormalisedMatches(content, searchStr);
|
|
278
|
+
if (!match || match.ranges.length <= 1) return null;
|
|
279
|
+
const indices = match.ranges.map((r) => r.start);
|
|
280
|
+
return {
|
|
281
|
+
stage: 'crlf-fold',
|
|
282
|
+
count: match.ranges.length,
|
|
283
|
+
lines: _lineNumbersForIndices(content, indices),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function eolFoldMatch(content, searchStr) {
|
|
288
|
+
if (typeof content !== 'string' || typeof searchStr !== 'string' || searchStr.length === 0) return null;
|
|
289
|
+
if (content.includes(searchStr)) return null;
|
|
290
|
+
const variants = _eolFoldVariants(searchStr);
|
|
291
|
+
const endsWithCrlf = searchStr.endsWith('\r\n');
|
|
292
|
+
const endsWithLf = !endsWithCrlf && searchStr.endsWith('\n');
|
|
293
|
+
let slice = null;
|
|
294
|
+
let total = 0;
|
|
295
|
+
for (const needle of variants) {
|
|
296
|
+
if (!needle) continue;
|
|
297
|
+
const isBare = (endsWithCrlf || endsWithLf) && needle.length < searchStr.length;
|
|
298
|
+
const indices = isBare
|
|
299
|
+
? _collectEolBoundaryBareIndices(content, needle)
|
|
300
|
+
: _collectOverlapAwareIndices(content, needle);
|
|
301
|
+
if (indices.length === 0) continue;
|
|
302
|
+
total += indices.length;
|
|
303
|
+
if (indices.length === 1 && slice === null) {
|
|
304
|
+
slice = isBare
|
|
305
|
+
? _sliceAtTrailingEolBoundary(content, indices[0], needle, searchStr)
|
|
306
|
+
: content.substring(indices[0], indices[0] + needle.length);
|
|
307
|
+
} else if (indices.length > 0) {
|
|
308
|
+
if (indices.length > 1 || slice !== null) slice = null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (total === 1 && slice !== null) return slice;
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function _diagnoseEolFoldAmbiguity(content, searchStr) {
|
|
316
|
+
const variants = _eolFoldVariants(searchStr);
|
|
317
|
+
const endsWithCrlf = searchStr.endsWith('\r\n');
|
|
318
|
+
const endsWithLf = !endsWithCrlf && searchStr.endsWith('\n');
|
|
319
|
+
const indices = [];
|
|
320
|
+
for (const needle of variants) {
|
|
321
|
+
if (!needle) continue;
|
|
322
|
+
const isBare = (endsWithCrlf || endsWithLf) && needle.length < searchStr.length;
|
|
323
|
+
if (isBare) indices.push(..._collectEolBoundaryBareIndices(content, needle));
|
|
324
|
+
else indices.push(..._collectOverlapAwareIndices(content, needle));
|
|
325
|
+
}
|
|
326
|
+
if (indices.length <= 1) return null;
|
|
327
|
+
return { stage: 'eol-fold', count: indices.length, lines: _lineNumbersForIndices(content, indices) };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Smoke / audit: eol-fold ambiguity in isolation (ignores exact-tier literal hits).
|
|
331
|
+
export function diagnoseEolFoldAmbiguity(content, searchStr) {
|
|
332
|
+
return _diagnoseEolFoldAmbiguity(content, searchStr);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// When invariant-safe or last-resort fold tiers would match more than once, surface code 9
|
|
336
|
+
// (ambiguous) instead of falling through to code 8 (not found).
|
|
337
|
+
export function diagnoseFoldTierAmbiguity(content, searchStr) {
|
|
338
|
+
if (typeof content !== 'string' || typeof searchStr !== 'string' || searchStr.length === 0) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
if (content.includes(searchStr)) return null;
|
|
342
|
+
|
|
343
|
+
const nContent = normalizeForMatch(content);
|
|
344
|
+
const nSearch = normalizeForMatch(searchStr);
|
|
345
|
+
if (nContent !== content || nSearch !== searchStr) {
|
|
346
|
+
const indices = _collectOverlapAwareIndices(nContent, nSearch);
|
|
347
|
+
if (indices.length > 1) {
|
|
348
|
+
return {
|
|
349
|
+
stage: 'fold',
|
|
350
|
+
count: indices.length,
|
|
351
|
+
lines: _lineNumbersForIndices(content, indices),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
let nfdSearch;
|
|
357
|
+
let nfdContent;
|
|
358
|
+
try {
|
|
359
|
+
nfdSearch = searchStr.normalize('NFD');
|
|
360
|
+
nfdContent = content.normalize('NFD');
|
|
361
|
+
} catch {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
const nfdDrift = !(nfdSearch === searchStr && nfdContent === content);
|
|
365
|
+
if (nfdDrift && nfdSearch.length > 0) {
|
|
366
|
+
const nfdIndices = _collectOverlapAwareIndices(nfdContent, nfdSearch);
|
|
367
|
+
if (nfdIndices.length > 1) {
|
|
368
|
+
const lines = [];
|
|
369
|
+
for (let k = 0; k < Math.min(3, nfdIndices.length); k++) {
|
|
370
|
+
const firstIdx = nfdIndices[k];
|
|
371
|
+
let origStart = 0;
|
|
372
|
+
let nfdCursor = 0;
|
|
373
|
+
while (origStart < content.length && nfdCursor < firstIdx) {
|
|
374
|
+
nfdCursor += content[origStart].normalize('NFD').length;
|
|
375
|
+
origStart++;
|
|
376
|
+
}
|
|
377
|
+
lines.push(_lineNumbersForIndices(content, [origStart], 1)[0] || 1);
|
|
378
|
+
}
|
|
379
|
+
return { stage: 'nfc-fold', count: nfdIndices.length, lines };
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const crlfAmb = _diagnoseCrlfFoldAmbiguity(content, searchStr);
|
|
384
|
+
if (crlfAmb) return crlfAmb;
|
|
385
|
+
const rstripAmb = _diagnoseLineOrientedFold(content, searchStr, _normalizeLineBodyRstrip, 'rstrip-fold');
|
|
386
|
+
if (rstripAmb) return rstripAmb;
|
|
387
|
+
const indentAmb = _diagnoseLineOrientedFold(content, searchStr, _normalizeLeadingIndentLine, 'indent-fold');
|
|
388
|
+
if (indentAmb) return indentAmb;
|
|
389
|
+
return _diagnoseEolFoldAmbiguity(content, searchStr);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Tiered matcher: exact → curly-quote fold → NFC-fold → crlf-fold →
|
|
393
|
+
// rstrip-fold → indent-fold → eol-fold (last resort). `info.stage` is set
|
|
394
|
+
// to the tier that landed. Returns null if every tier fails — the caller
|
|
395
|
+
// may still attempt engine CRLF range replace or surfaces Error [code 8].
|
|
396
|
+
export function findActualString(content, searchStr, info) {
|
|
397
|
+
if (typeof content !== 'string' || typeof searchStr !== 'string' || searchStr.length === 0) return null;
|
|
398
|
+
if (content.includes(searchStr)) {
|
|
399
|
+
if (info) info.stage = 'exact';
|
|
400
|
+
return searchStr;
|
|
401
|
+
}
|
|
402
|
+
const nContent = normalizeForMatch(content);
|
|
403
|
+
const nSearch = normalizeForMatch(searchStr);
|
|
404
|
+
if (nContent !== content || nSearch !== searchStr) {
|
|
405
|
+
const idx = nContent.indexOf(nSearch);
|
|
406
|
+
if (idx !== -1) {
|
|
407
|
+
if (nContent.indexOf(nSearch, idx + 1) === -1) {
|
|
408
|
+
if (info) info.stage = 'fold';
|
|
409
|
+
return content.substring(idx, idx + searchStr.length);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
const nfcSlice = nfcFoldMatch(content, searchStr);
|
|
414
|
+
if (nfcSlice !== null) {
|
|
415
|
+
if (info) info.stage = 'nfc-fold';
|
|
416
|
+
return nfcSlice;
|
|
417
|
+
}
|
|
418
|
+
const crlfSlice = crlfFoldMatch(content, searchStr);
|
|
419
|
+
if (crlfSlice !== null) {
|
|
420
|
+
if (info) info.stage = 'crlf-fold';
|
|
421
|
+
return crlfSlice;
|
|
422
|
+
}
|
|
423
|
+
const rstripSlice = _lineOrientedFoldMatch(content, searchStr, _normalizeLineBodyRstrip);
|
|
424
|
+
if (rstripSlice !== null) {
|
|
425
|
+
if (info) info.stage = 'rstrip-fold';
|
|
426
|
+
return rstripSlice;
|
|
427
|
+
}
|
|
428
|
+
const indentSlice = _lineOrientedFoldMatch(content, searchStr, _normalizeLeadingIndentLine);
|
|
429
|
+
if (indentSlice !== null) {
|
|
430
|
+
if (info) info.stage = 'indent-fold';
|
|
431
|
+
return indentSlice;
|
|
432
|
+
}
|
|
433
|
+
const eolSlice = eolFoldMatch(content, searchStr);
|
|
434
|
+
if (eolSlice !== null) {
|
|
435
|
+
if (info) info.stage = 'eol-fold';
|
|
436
|
+
return eolSlice;
|
|
437
|
+
}
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Strip trailing space/tab from each line of `s` while preserving every
|
|
442
|
+
// line terminator exactly as encountered (LF, CRLF, or lone CR). Used for
|
|
443
|
+
// insert-side new_string normalization and as the rstrip-fold view (match
|
|
444
|
+
// location only via findActualString's last-resort tier + ambiguity gate).
|
|
445
|
+
// Models routinely append stray spaces at line ends; in source code that
|
|
446
|
+
// has no semantic meaning, so silent diffs from those bytes are pure
|
|
447
|
+
// noise. Caller is responsible for skipping markdown (where `" \n"` is
|
|
448
|
+
// the hard-line-break syntax).
|
|
449
|
+
export function stripTrailingWhitespacePerLine(s) {
|
|
450
|
+
if (typeof s !== 'string' || s.length === 0) return s;
|
|
451
|
+
const parts = s.split(/(\r\n|\n|\r)/);
|
|
452
|
+
let changed = false;
|
|
453
|
+
for (let i = 0; i < parts.length; i += 2) {
|
|
454
|
+
const before = parts[i];
|
|
455
|
+
if (before === undefined) continue;
|
|
456
|
+
const after = before.replace(/[ \t]+$/, '');
|
|
457
|
+
if (after !== before) {
|
|
458
|
+
parts[i] = after;
|
|
459
|
+
changed = true;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return changed ? parts.join('') : s;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Trailing-whitespace hygiene for Edit new_string, FINAL-LINE AWARE. The
|
|
466
|
+
// per-line strip exists to drop model-emitted trailing-space noise, but when
|
|
467
|
+
// old_string itself ends in space/tab the edit span deliberately ends
|
|
468
|
+
// MID-LINE inside meaningful whitespace (old `const m = ` / new
|
|
469
|
+
// `const mX = `) — stripping new_string's final line there silently eats a
|
|
470
|
+
// load-bearing space and corrupts the line. Non-final lines always end at a
|
|
471
|
+
// terminator, so they are unconditionally safe to strip.
|
|
472
|
+
export function stripTrailingWhitespaceForEdit(newString, oldString) {
|
|
473
|
+
if (typeof newString !== 'string' || newString.length === 0) return newString;
|
|
474
|
+
if (!/[ \t]$/.test(String(oldString ?? ''))) {
|
|
475
|
+
return stripTrailingWhitespacePerLine(newString);
|
|
476
|
+
}
|
|
477
|
+
const i = newString.lastIndexOf('\n');
|
|
478
|
+
if (i === -1) return newString;
|
|
479
|
+
return stripTrailingWhitespacePerLine(newString.slice(0, i + 1)) + newString.slice(i + 1);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
483
|
+
// Typography preservation — Claude Code parity (FileEditTool/utils.ts:96-199).
|
|
484
|
+
//
|
|
485
|
+
// CORE-PRINCIPLE EXCEPTION: every tier in this module is a deterministic,
|
|
486
|
+
// invariant-based recovery; this single helper is a heuristic — explicitly
|
|
487
|
+
// opted in because the alternative (always emitting ASCII `'` / `"` into a
|
|
488
|
+
// curly-quoted file) silently drifts the typography of every edited prose
|
|
489
|
+
// file. Note this only rewrites `newString` bytes the model is about to
|
|
490
|
+
// insert; it never affects WHERE the match lands. The heuristic is
|
|
491
|
+
// well-bounded:
|
|
492
|
+
//
|
|
493
|
+
// • Only ASCII `'` / `"` characters in `newString` are touched. All
|
|
494
|
+
// other bytes — letters, numbers, punctuation, code identifiers,
|
|
495
|
+
// existing curly quotes — pass through unmodified.
|
|
496
|
+
// • Triggered only when `matchedSlice` (the file's actual bytes at the
|
|
497
|
+
// match position) contains at least one curly quote AND differs from
|
|
498
|
+
// `oldString` (i.e. the model emitted straight, the file had curly).
|
|
499
|
+
// • Contraction guard: an apostrophe between two Unicode letters is
|
|
500
|
+
// classified as a contraction (`it's`, `don't`) and always becomes
|
|
501
|
+
// RIGHT_SINGLE (`'`), regardless of preceding-char context.
|
|
502
|
+
// • LEFT / RIGHT inference: an opening quote is one preceded by
|
|
503
|
+
// whitespace, an opening bracket, an em / en dash, or string start.
|
|
504
|
+
// Otherwise closing.
|
|
505
|
+
//
|
|
506
|
+
// Known limitations (accepted):
|
|
507
|
+
// • Mid-sentence quote in code string literals — irrelevant because
|
|
508
|
+
// code files almost never contain curly quotes (skip-by-design via
|
|
509
|
+
// the `hasDoubleQuotes || hasSingleQuotes` gate).
|
|
510
|
+
// • CJK / Korean prose where quote-adjacent characters are letters —
|
|
511
|
+
// the contraction guard may fire and pick RIGHT. Net effect: a
|
|
512
|
+
// consistent right-quote rather than a drift back to ASCII, which
|
|
513
|
+
// is still a typography preservation, just not perfectly directional.
|
|
514
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
515
|
+
|
|
516
|
+
export const LEFT_SINGLE_CURLY_QUOTE = '‘';
|
|
517
|
+
export const RIGHT_SINGLE_CURLY_QUOTE = '’';
|
|
518
|
+
export const LEFT_DOUBLE_CURLY_QUOTE = '“';
|
|
519
|
+
export const RIGHT_DOUBLE_CURLY_QUOTE = '”';
|
|
520
|
+
|
|
521
|
+
function _isOpeningContext(chars, index) {
|
|
522
|
+
if (index === 0) return true;
|
|
523
|
+
const prev = chars[index - 1];
|
|
524
|
+
return (
|
|
525
|
+
prev === ' '
|
|
526
|
+
|| prev === '\t'
|
|
527
|
+
|| prev === '\n'
|
|
528
|
+
|| prev === '\r'
|
|
529
|
+
|| prev === '('
|
|
530
|
+
|| prev === '['
|
|
531
|
+
|| prev === '{'
|
|
532
|
+
|| prev === '—' // em dash
|
|
533
|
+
|| prev === '–' // en dash
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function _applyCurlyDoubleQuotes(str) {
|
|
538
|
+
const chars = Array.from(str);
|
|
539
|
+
const result = [];
|
|
540
|
+
for (let i = 0; i < chars.length; i++) {
|
|
541
|
+
if (chars[i] === '"') {
|
|
542
|
+
result.push(_isOpeningContext(chars, i) ? LEFT_DOUBLE_CURLY_QUOTE : RIGHT_DOUBLE_CURLY_QUOTE);
|
|
543
|
+
} else {
|
|
544
|
+
result.push(chars[i]);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return result.join('');
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function _applyCurlySingleQuotes(str) {
|
|
551
|
+
const chars = Array.from(str);
|
|
552
|
+
const result = [];
|
|
553
|
+
for (let i = 0; i < chars.length; i++) {
|
|
554
|
+
if (chars[i] === "'") {
|
|
555
|
+
const prev = i > 0 ? chars[i - 1] : undefined;
|
|
556
|
+
const next = i < chars.length - 1 ? chars[i + 1] : undefined;
|
|
557
|
+
const prevIsLetter = prev !== undefined && /\p{L}/u.test(prev);
|
|
558
|
+
const nextIsLetter = next !== undefined && /\p{L}/u.test(next);
|
|
559
|
+
if (prevIsLetter && nextIsLetter) {
|
|
560
|
+
// Contraction (e.g. "it's", "don't") — always right curly.
|
|
561
|
+
result.push(RIGHT_SINGLE_CURLY_QUOTE);
|
|
562
|
+
} else {
|
|
563
|
+
result.push(_isOpeningContext(chars, i) ? LEFT_SINGLE_CURLY_QUOTE : RIGHT_SINGLE_CURLY_QUOTE);
|
|
564
|
+
}
|
|
565
|
+
} else {
|
|
566
|
+
result.push(chars[i]);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return result.join('');
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export function preserveQuoteTypography(oldString, matchedSlice, newString) {
|
|
573
|
+
if (typeof matchedSlice !== 'string' || typeof newString !== 'string') return newString;
|
|
574
|
+
if (oldString === matchedSlice) return newString;
|
|
575
|
+
const hasDoubleQuotes =
|
|
576
|
+
matchedSlice.includes(LEFT_DOUBLE_CURLY_QUOTE) ||
|
|
577
|
+
matchedSlice.includes(RIGHT_DOUBLE_CURLY_QUOTE);
|
|
578
|
+
const hasSingleQuotes =
|
|
579
|
+
matchedSlice.includes(LEFT_SINGLE_CURLY_QUOTE) ||
|
|
580
|
+
matchedSlice.includes(RIGHT_SINGLE_CURLY_QUOTE);
|
|
581
|
+
if (!hasDoubleQuotes && !hasSingleQuotes) return newString;
|
|
582
|
+
let result = newString;
|
|
583
|
+
if (hasDoubleQuotes) result = _applyCurlyDoubleQuotes(result);
|
|
584
|
+
if (hasSingleQuotes) result = _applyCurlySingleQuotes(result);
|
|
585
|
+
return result;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Single-stage inline note → " (via fold)" suffix for error messages.
|
|
589
|
+
// Empty when the stage is missing or exact (the steady-state path).
|
|
590
|
+
export function formatStageInline(stage) {
|
|
591
|
+
return (stage && stage !== 'exact') ? ` (via ${stage})` : '';
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Stage stats → " [fold:2, crlf-fold:0]" suffix. Empty / null counts
|
|
595
|
+
// render as empty string so exact-only edits stay terse. Stable key order
|
|
596
|
+
// (Object.entries insertion order) keeps two edits with the same matches
|
|
597
|
+
// rendering identically.
|
|
598
|
+
export function formatStageNote(stageCounts) {
|
|
599
|
+
if (!stageCounts) return '';
|
|
600
|
+
const entries = Object.entries(stageCounts).filter(([, v]) => v > 0);
|
|
601
|
+
if (entries.length === 0) return '';
|
|
602
|
+
return ` [${entries.map(([k, v]) => `${k}:${v}`).join(', ')}]`;
|
|
603
|
+
}
|