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,37 @@
|
|
|
1
|
+
import { statSync } from 'fs';
|
|
2
|
+
import { isAbsolute, resolve } from 'path';
|
|
3
|
+
import {
|
|
4
|
+
normalizeInputPath,
|
|
5
|
+
normalizeOutputPath,
|
|
6
|
+
resolveAgainstCwd,
|
|
7
|
+
} from './path-utils.mjs';
|
|
8
|
+
|
|
9
|
+
export function literalPathHint(rawPath) {
|
|
10
|
+
const s = String(rawPath || '');
|
|
11
|
+
if (/\$env:[A-Za-z_][A-Za-z0-9_]*|\$[A-Za-z_][A-Za-z0-9_]*|%[^%]+%/.test(s)) {
|
|
12
|
+
return ' Path fields do not expand shell environment variables; pass the resolved absolute path.';
|
|
13
|
+
}
|
|
14
|
+
if (process.platform === 'win32' && /^[A-Za-z]:[^\\/]/.test(s)) {
|
|
15
|
+
return ' Windows drive-relative paths like C:Project are ambiguous; use C:/Project or C:\\Project.';
|
|
16
|
+
}
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function resolveOptionalCwd(rawCwd, baseCwd) {
|
|
21
|
+
if (typeof rawCwd !== 'string' || rawCwd.trim() === '') {
|
|
22
|
+
return { cwd: baseCwd };
|
|
23
|
+
}
|
|
24
|
+
const normalized = normalizeInputPath(rawCwd.trim());
|
|
25
|
+
const resolved = isAbsolute(normalized) ? resolve(normalized) : resolveAgainstCwd(normalized, baseCwd);
|
|
26
|
+
try {
|
|
27
|
+
const st = statSync(resolved);
|
|
28
|
+
if (!st.isDirectory()) {
|
|
29
|
+
return { error: `Error: cwd is not a directory: ${normalizeOutputPath(resolved)}.${literalPathHint(rawCwd)}` };
|
|
30
|
+
}
|
|
31
|
+
return { cwd: resolved };
|
|
32
|
+
} catch (err) {
|
|
33
|
+
return {
|
|
34
|
+
error: `Error: cwd does not exist: ${normalizeOutputPath(resolved)} (${err?.code || 'ENOENT'}).${literalPathHint(rawCwd)} Pass an absolute repo path or omit cwd to use ${normalizeOutputPath(baseCwd)}.`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// G6: device path block list (Claude Code parity). Reading these paths
|
|
2
|
+
// would either hang (waiting for stdin / tty) or produce infinite output
|
|
3
|
+
// (/dev/zero, /dev/random). The device block list catches pseudo-files on
|
|
4
|
+
// POSIX hosts that a user-allowed path can still hit.
|
|
5
|
+
const BLOCKED_DEVICE_PATHS = new Set([
|
|
6
|
+
'/dev/zero', '/dev/random', '/dev/urandom', '/dev/full',
|
|
7
|
+
'/dev/stdin', '/dev/tty', '/dev/console',
|
|
8
|
+
'/dev/stdout', '/dev/stderr',
|
|
9
|
+
'/dev/fd/0', '/dev/fd/1', '/dev/fd/2',
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
export function isBlockedDevicePath(p) {
|
|
13
|
+
if (BLOCKED_DEVICE_PATHS.has(p)) return true;
|
|
14
|
+
// /proc/self/fd/0-2 and /proc/<pid>/fd/0-2 are Linux aliases for stdio.
|
|
15
|
+
if (typeof p === 'string' && p.startsWith('/proc/')
|
|
16
|
+
&& (p.endsWith('/fd/0') || p.endsWith('/fd/1') || p.endsWith('/fd/2'))) return true;
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// R1: UNC / SMB share paths (\\server\share or //server/share). Reading these
|
|
21
|
+
// causes Windows to auto-authenticate to the remote host, leaking the NTLM
|
|
22
|
+
// hash of the current user to any attacker-controlled SMB target. CC parity:
|
|
23
|
+
// FileReadTool.ts:461 rejects the same prefix before stat. Accepts both
|
|
24
|
+
// backslash and forward-slash leaders so a normalize that picked either form
|
|
25
|
+
// is caught.
|
|
26
|
+
export function isUncPath(p) {
|
|
27
|
+
if (typeof p !== 'string' || p.length < 2) return false;
|
|
28
|
+
return (p[0] === '\\' && p[1] === '\\') || (p[0] === '/' && p[1] === '/');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// R2: Windows reserved device basenames (CON, NUL, PRN, AUX, COM0-9, LPT0-9)
|
|
32
|
+
// and raw-device namespace prefixes (\\.\ DosDevices, \\?\ NT namespace).
|
|
33
|
+
// CON/NUL etc. are kernel-level aliases that never resolve to real files
|
|
34
|
+
// when used as bare names; \\.\PhysicalDrive0 / \\.\CON allow raw device
|
|
35
|
+
// access. Basenames with a real extension (nul.bin) are ordinary files.
|
|
36
|
+
const WIN_RESERVED_BASENAMES = new Set([
|
|
37
|
+
'CON', 'NUL', 'PRN', 'AUX',
|
|
38
|
+
// CONIN$ / CONOUT$ are the console input/output device aliases; the
|
|
39
|
+
// trailing '$' is part of the name and survives stem extraction
|
|
40
|
+
// (cut happens at the first '.'/':' only).
|
|
41
|
+
'CONIN$', 'CONOUT$',
|
|
42
|
+
'COM0', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
|
|
43
|
+
'LPT0', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9',
|
|
44
|
+
// Win32 normalizes the Latin-1 superscript digits ¹ (U+00B9), ²
|
|
45
|
+
// (U+00B2), ³ (U+00B3) to 1/2/3 when resolving device names, so
|
|
46
|
+
// COM¹/COM²/COM³ and LPT¹/LPT²/LPT³ open the real COM1-3/LPT1-3
|
|
47
|
+
// devices. toUpperCase() leaves these codepoints unchanged, so they
|
|
48
|
+
// must be listed literally.
|
|
49
|
+
'COM\u00B9', 'COM\u00B2', 'COM\u00B3',
|
|
50
|
+
'LPT\u00B9', 'LPT\u00B2', 'LPT\u00B3',
|
|
51
|
+
]);
|
|
52
|
+
export function isWindowsDevicePath(p) {
|
|
53
|
+
if (typeof p !== 'string' || p.length === 0) return false;
|
|
54
|
+
// \\.\ and \\?\ raw-device / DOS-device namespace (both slash forms).
|
|
55
|
+
if (p.startsWith('\\\\.\\') || p.startsWith('\\\\?\\')) return true;
|
|
56
|
+
if (p.startsWith('//./') || p.startsWith('//?/')) return true;
|
|
57
|
+
// Reserved basenames (CON/NUL/PRN/AUX/COM*/LPT*) are device aliases ONLY on
|
|
58
|
+
// Windows; on POSIX a file literally named `con.txt` is a normal regular
|
|
59
|
+
// file. Gate the basename check to win32 so non-Windows reads of such names
|
|
60
|
+
// aren't false-rejected (the \\.\ / \\?\ namespace checks above stay
|
|
61
|
+
// unconditional). reviewer-flagged false positive.
|
|
62
|
+
if (process.platform !== 'win32') return false;
|
|
63
|
+
// Basename match, case-insensitive, extension-stripped. R12: splitting
|
|
64
|
+
// on '.' alone missed NTFS ADS suffixes (NUL:stream, CON:stream) and
|
|
65
|
+
// trailing Win32-ignored dots/spaces ("CON. ", "NUL. ") that the
|
|
66
|
+
// kernel still resolves to the reserved device. Compute the stem by
|
|
67
|
+
// cutting at the FIRST '.' or ':' in the basename, then stripping
|
|
68
|
+
// any trailing dots/spaces that Win32 silently drops.
|
|
69
|
+
const lastSep = Math.max(p.lastIndexOf('\\'), p.lastIndexOf('/'));
|
|
70
|
+
const base = lastSep >= 0 ? p.slice(lastSep + 1) : p;
|
|
71
|
+
let cut = base.length;
|
|
72
|
+
for (let i = 0; i < base.length; i += 1) {
|
|
73
|
+
const ch = base.charCodeAt(i);
|
|
74
|
+
if (ch === 0x2E /* . */ || ch === 0x3A /* : */) { cut = i; break; }
|
|
75
|
+
}
|
|
76
|
+
let stem = base.slice(0, cut);
|
|
77
|
+
// R14: strip Unicode invisibles (BOM, zero-width joiners, bidi controls)
|
|
78
|
+
// BEFORE the trailing-dot / reserved-name checks. Win32 GetFinalPathName
|
|
79
|
+
// and a number of file APIs collapse / ignore these characters when
|
|
80
|
+
// opening, so "CON\u200B" or "\uFEFFCON" can resolve to the CON device
|
|
81
|
+
// even though byte-comparison says "not reserved". Drop them defensively.
|
|
82
|
+
stem = stem.replace(/[\uFEFF\u200B-\u200F\u202A-\u202E\u2066-\u2069]/g, '');
|
|
83
|
+
// Strip trailing dots / spaces — Win32 ignores them when opening a
|
|
84
|
+
// file, so "CON. " and "CON " resolve to CON. Loop instead of regex
|
|
85
|
+
// to keep allocation-free hot path.
|
|
86
|
+
let end = stem.length;
|
|
87
|
+
while (end > 0) {
|
|
88
|
+
const ch = stem.charCodeAt(end - 1);
|
|
89
|
+
if (ch === 0x2E /* . */ || ch === 0x20 /* SP */) { end -= 1; continue; }
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
stem = stem.slice(0, end).toUpperCase();
|
|
93
|
+
if (!WIN_RESERVED_BASENAMES.has(stem)) return false;
|
|
94
|
+
// Bare device alias only (NUL, CON, …). A dotted extension means a normal
|
|
95
|
+
// file even when the stem matches a reserved name (dir/nul.bin).
|
|
96
|
+
return cut >= base.length;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// R12: Win32 path-component guard. Rejects any path component that
|
|
100
|
+
// (a) ends in '.' or ' ' — Win32 silently strips trailing dots/spaces
|
|
101
|
+
// when opening, so the kernel resolves a DIFFERENT path than the one
|
|
102
|
+
// the guard sees, bypassing string-based device/UNC checks; or
|
|
103
|
+
// (b) contains ':' after the optional `<drive>:` prefix — colon inside
|
|
104
|
+
// a component names an NTFS Alternate Data Stream (e.g. `file.txt:hidden`),
|
|
105
|
+
// which is a hidden writable channel attached to another file.
|
|
106
|
+
// Legitimate Windows filenames never have trailing dot/space or embedded
|
|
107
|
+
// ':' in any component, so unconditional rejection is safe.
|
|
108
|
+
export function hasUnsafeWin32Component(p) {
|
|
109
|
+
if (process.platform !== 'win32') return false;
|
|
110
|
+
if (typeof p !== 'string' || p.length === 0) return false;
|
|
111
|
+
// Strip optional drive prefix (`C:` / `c:`) so the drive colon is not
|
|
112
|
+
// mistaken for an ADS marker. \\.\ / \\?\ raw-device prefixes are
|
|
113
|
+
// already rejected by isWindowsDevicePath; treat their bytes as
|
|
114
|
+
// ordinary components here so a `\\?\C:\foo:stream` still trips.
|
|
115
|
+
let rest = p;
|
|
116
|
+
if (rest.length >= 2 && rest.charCodeAt(1) === 0x3A /* : */
|
|
117
|
+
&& ((rest.charCodeAt(0) >= 0x41 && rest.charCodeAt(0) <= 0x5A)
|
|
118
|
+
|| (rest.charCodeAt(0) >= 0x61 && rest.charCodeAt(0) <= 0x7A))) {
|
|
119
|
+
rest = rest.slice(2);
|
|
120
|
+
}
|
|
121
|
+
// Split on both Win32 separators.
|
|
122
|
+
const parts = rest.split(/[\\/]+/);
|
|
123
|
+
for (const rawPart of parts) {
|
|
124
|
+
if (!rawPart) continue;
|
|
125
|
+
// R14: strip Unicode invisibles (BOM, zero-width joiners, bidi controls)
|
|
126
|
+
// BEFORE the trailing-dot / ADS-colon checks. Win32 ignores these
|
|
127
|
+
// characters in many path-open codepaths, so "foo \u200B" (trailing
|
|
128
|
+
// space + ZWSP) or "name\uFEFF:stream" must still trip the guard.
|
|
129
|
+
const part = rawPart.replace(/[\uFEFF\u200B-\u200F\u202A-\u202E\u2066-\u2069]/g, '');
|
|
130
|
+
if (!part) continue;
|
|
131
|
+
// `.` and `..` are the standard current/parent-dir components — they end
|
|
132
|
+
// in '.' but are NOT a trailing-dot bypass, so exempt them (without this
|
|
133
|
+
// a default relative path like "." or "src/.." is rejected on Windows).
|
|
134
|
+
// Any other trailing dot/space is still unsafe.
|
|
135
|
+
if (part === '.' || part === '..') continue;
|
|
136
|
+
const last = part.charCodeAt(part.length - 1);
|
|
137
|
+
if (last === 0x2E /* . */ || last === 0x20 /* SP */) return true;
|
|
138
|
+
if (part.indexOf(':') >= 0) return true;
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// R2: Stat-based special-file reject. FIFOs, character devices, block devices,
|
|
144
|
+
// and sockets pass a normal statSync but reading them either hangs (FIFO with
|
|
145
|
+
// no writer, socket) or produces unbounded output (/dev/zero, /dev/random).
|
|
146
|
+
// Must be called AFTER statSync (or fstatSync) — the string-based device
|
|
147
|
+
// guard catches well-known paths, this catches arbitrary user paths that
|
|
148
|
+
// happen to point at a special inode (custom mknod, symlink targets, etc.).
|
|
149
|
+
export function isSpecialFileStat(st) {
|
|
150
|
+
if (!st || typeof st !== 'object') return false;
|
|
151
|
+
try {
|
|
152
|
+
return st.isFIFO() || st.isCharacterDevice() || st.isBlockDevice() || st.isSocket();
|
|
153
|
+
} catch { return false; }
|
|
154
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// Server-less diagnostics tool.
|
|
2
|
+
//
|
|
3
|
+
// Mixdog has no resident LSP server. Instead this tool detects the project
|
|
4
|
+
// marker(s) under the target path, runs the matching project CLI ONCE via the
|
|
5
|
+
// same exec path bash-tool uses (execShellCommand + resolveShell), then PARSES
|
|
6
|
+
// the checker's stdout/stderr into structured findings. No watch, no daemon.
|
|
7
|
+
//
|
|
8
|
+
// Graceful contract: a missing marker or a missing checker binary returns a
|
|
9
|
+
// clear "no checker available for X" message — it never throws.
|
|
10
|
+
import { existsSync, readFileSync, statSync } from 'fs';
|
|
11
|
+
import { dirname, isAbsolute, resolve as pathResolve } from 'path';
|
|
12
|
+
import { execShellCommand, stripAnsi } from '../shell-command.mjs';
|
|
13
|
+
import { resolveShell } from './shell-runtime.mjs';
|
|
14
|
+
|
|
15
|
+
const DIAGNOSTICS_TIMEOUT_MS = 120_000;
|
|
16
|
+
|
|
17
|
+
// Probe whether a CLI binary is resolvable on PATH without running real work.
|
|
18
|
+
// Uses the platform command-resolver so an absent checker degrades gracefully
|
|
19
|
+
// into a "no checker available" message instead of a spawn error.
|
|
20
|
+
async function _commandExists(bin, cwd, env) {
|
|
21
|
+
const { shell, shellArg, shellArgs, shellType } = resolveShell();
|
|
22
|
+
const probe = shellType === 'powershell'
|
|
23
|
+
? `Get-Command ${bin} -ErrorAction SilentlyContinue | Select-Object -First 1`
|
|
24
|
+
: `command -v ${bin}`;
|
|
25
|
+
try {
|
|
26
|
+
const r = await execShellCommand({
|
|
27
|
+
shell, shellArg, shellArgs, command: probe,
|
|
28
|
+
env, cwd, timeoutMs: 10_000, abortSignal: null,
|
|
29
|
+
});
|
|
30
|
+
const out = `${r.stdout || ''}`.trim();
|
|
31
|
+
if (shellType === 'powershell') return out.length > 0;
|
|
32
|
+
return r.exitCode === 0 && out.length > 0;
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function _runChecker(command, cwd, env) {
|
|
39
|
+
const { shell, shellArg, shellArgs } = resolveShell();
|
|
40
|
+
const r = await execShellCommand({
|
|
41
|
+
shell, shellArg, shellArgs, command,
|
|
42
|
+
env, cwd, timeoutMs: DIAGNOSTICS_TIMEOUT_MS, abortSignal: null,
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
stdout: stripAnsi(r.stdout || ''),
|
|
46
|
+
stderr: stripAnsi(r.stderr || ''),
|
|
47
|
+
exitCode: r.timedOut ? null : r.exitCode,
|
|
48
|
+
timedOut: !!r.timedOut,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---- per-checker output parsers -> [{file,line,col,severity,code,message}] ----
|
|
53
|
+
|
|
54
|
+
function _parseTsc(text) {
|
|
55
|
+
const findings = [];
|
|
56
|
+
const re = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s+(.*)$/;
|
|
57
|
+
for (const line of text.split(/\r?\n/)) {
|
|
58
|
+
const m = re.exec(line.trim());
|
|
59
|
+
if (m) findings.push({ file: m[1], line: +m[2], col: +m[3], severity: m[4], code: m[5], message: m[6] });
|
|
60
|
+
}
|
|
61
|
+
return findings;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function _parseRuff(text) {
|
|
65
|
+
const findings = [];
|
|
66
|
+
// ruff default text: path:line:col: CODE message
|
|
67
|
+
const re = /^(.+?):(\d+):(\d+):\s+([A-Z]+\d+)\s+(.*)$/;
|
|
68
|
+
for (const line of text.split(/\r?\n/)) {
|
|
69
|
+
const m = re.exec(line.trim());
|
|
70
|
+
if (!m) continue;
|
|
71
|
+
const code = m[4];
|
|
72
|
+
const severity = (/^(E9|F)/.test(code)) ? 'error' : 'warning';
|
|
73
|
+
findings.push({ file: m[1], line: +m[2], col: +m[3], severity, code, message: m[5] });
|
|
74
|
+
}
|
|
75
|
+
return findings;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function _parsePyflakes(text) {
|
|
79
|
+
const findings = [];
|
|
80
|
+
// pyflakes: path:line: message (no column)
|
|
81
|
+
const re = /^(.+?):(\d+):\s*(.*)$/;
|
|
82
|
+
for (const line of text.split(/\r?\n/)) {
|
|
83
|
+
const trimmed = line.trim();
|
|
84
|
+
if (!trimmed) continue;
|
|
85
|
+
const m = re.exec(trimmed);
|
|
86
|
+
if (m) findings.push({ file: m[1], line: +m[2], col: null, severity: 'warning', code: null, message: m[3] });
|
|
87
|
+
}
|
|
88
|
+
return findings;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function _parseGoVet(text) {
|
|
92
|
+
const findings = [];
|
|
93
|
+
// go vet: path:line:col: message OR path:line: message
|
|
94
|
+
const re = /^(.+?\.go):(\d+):(?:(\d+):)?\s+(.*)$/;
|
|
95
|
+
for (const line of text.split(/\r?\n/)) {
|
|
96
|
+
const m = re.exec(line.trim());
|
|
97
|
+
if (m) findings.push({ file: m[1], line: +m[2], col: m[3] ? +m[3] : null, severity: 'error', code: 'vet', message: m[4] });
|
|
98
|
+
}
|
|
99
|
+
return findings;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function _parseCargo(text) {
|
|
103
|
+
const findings = [];
|
|
104
|
+
// cargo check --message-format=short: path:line:col: severity[CODE]: message
|
|
105
|
+
const re = /^(.+?):(\d+):(\d+):\s+(warning|error)(?:\[([A-Za-z0-9]+)\])?:\s+(.*)$/;
|
|
106
|
+
for (const line of text.split(/\r?\n/)) {
|
|
107
|
+
const m = re.exec(line.trim());
|
|
108
|
+
if (m) findings.push({ file: m[1], line: +m[2], col: +m[3], severity: m[4], code: m[5] || null, message: m[6] });
|
|
109
|
+
}
|
|
110
|
+
return findings;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// node --check failure shape: "<abs file>:<line>" on its own line, followed
|
|
114
|
+
// by the offending source + caret, then "SyntaxError: <message>".
|
|
115
|
+
function _parseNodeCheck(text) {
|
|
116
|
+
const findings = [];
|
|
117
|
+
const head = /^(.+?):(\d+)\s*$/m.exec(text);
|
|
118
|
+
const err = /^([A-Za-z]*Error):\s+(.*)$/m.exec(text);
|
|
119
|
+
if (head && err) {
|
|
120
|
+
findings.push({ file: head[1], line: +head[2], col: null, severity: 'error', code: err[1], message: err[2] });
|
|
121
|
+
}
|
|
122
|
+
return findings;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function _parseEslintCompact(text) {
|
|
126
|
+
const findings = [];
|
|
127
|
+
// eslint --format compact: path: line N, col M, Severity - message (rule)
|
|
128
|
+
const re = /^(.+?):\s+line\s+(\d+),\s+col\s+(\d+),\s+(Error|Warning)\s+-\s+(.*?)(?:\s+\(([^)]+)\))?$/;
|
|
129
|
+
for (const line of text.split(/\r?\n/)) {
|
|
130
|
+
const m = re.exec(line.trim());
|
|
131
|
+
if (m) findings.push({ file: m[1], line: +m[2], col: +m[3], severity: m[4].toLowerCase(), code: m[6] || null, message: m[5] });
|
|
132
|
+
}
|
|
133
|
+
return findings;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Detect the project marker under projectDir and return the checker plan.
|
|
137
|
+
// Order is deterministic; the first matching marker wins.
|
|
138
|
+
function _detectChecker(projectDir) {
|
|
139
|
+
const has = (f) => existsSync(pathResolve(projectDir, f));
|
|
140
|
+
if (has('tsconfig.json')) {
|
|
141
|
+
return { kind: 'tsc', bin: 'tsc', command: 'tsc --noEmit', parse: _parseTsc, marker: 'tsconfig.json' };
|
|
142
|
+
}
|
|
143
|
+
if (has('pyproject.toml') || has('setup.py')) {
|
|
144
|
+
return { kind: 'python', marker: has('pyproject.toml') ? 'pyproject.toml' : 'setup.py' };
|
|
145
|
+
}
|
|
146
|
+
if (has('go.mod')) {
|
|
147
|
+
return { kind: 'go', bin: 'go', command: 'go vet ./...', parse: _parseGoVet, marker: 'go.mod' };
|
|
148
|
+
}
|
|
149
|
+
if (has('Cargo.toml')) {
|
|
150
|
+
return { kind: 'cargo', bin: 'cargo', command: 'cargo check --message-format=short', parse: _parseCargo, marker: 'Cargo.toml' };
|
|
151
|
+
}
|
|
152
|
+
if (has('package.json')) {
|
|
153
|
+
let usesEslint = false;
|
|
154
|
+
try {
|
|
155
|
+
const pkg = JSON.parse(readFileSync(pathResolve(projectDir, 'package.json'), 'utf8'));
|
|
156
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
157
|
+
usesEslint = Object.prototype.hasOwnProperty.call(deps, 'eslint');
|
|
158
|
+
} catch { /* unreadable/invalid package.json */ }
|
|
159
|
+
const hasEslintConfig = ['.eslintrc', '.eslintrc.js', '.eslintrc.cjs', '.eslintrc.json', '.eslintrc.yml', '.eslintrc.yaml', 'eslint.config.js', 'eslint.config.mjs', 'eslint.config.cjs']
|
|
160
|
+
.some((f) => has(f));
|
|
161
|
+
if (usesEslint || hasEslintConfig) {
|
|
162
|
+
return { kind: 'eslint', bin: 'eslint', command: 'eslint . --format compact', parse: _parseEslintCompact, marker: 'package.json (eslint)' };
|
|
163
|
+
}
|
|
164
|
+
return { kind: 'none', reason: 'package.json present but no eslint dependency/config detected' };
|
|
165
|
+
}
|
|
166
|
+
return { kind: 'none', reason: 'no recognized project marker (tsconfig.json / pyproject.toml / setup.py / go.mod / Cargo.toml / package.json)' };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function _formatResult({ marker, checker, findings, raw, note }) {
|
|
170
|
+
return JSON.stringify({
|
|
171
|
+
ok: true,
|
|
172
|
+
marker: marker || null,
|
|
173
|
+
checker: checker || null,
|
|
174
|
+
note: note || null,
|
|
175
|
+
count: findings.length,
|
|
176
|
+
findings,
|
|
177
|
+
raw: raw ? raw.slice(0, 4000) : undefined,
|
|
178
|
+
}, null, 2);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export async function executeDiagnosticsTool(args, workDir, options = {}) {
|
|
182
|
+
const result = await _executeDiagnosticsImpl(args, workDir);
|
|
183
|
+
// ② completion progress (claude "Found N" parity). Best-effort, no-op
|
|
184
|
+
// when onProgress is absent (no progressToken). Never throws — the tool
|
|
185
|
+
// result is returned regardless.
|
|
186
|
+
if (typeof options?.onProgress === 'function') {
|
|
187
|
+
try {
|
|
188
|
+
let _n = null;
|
|
189
|
+
try { _n = JSON.parse(result)?.count; } catch { /* non-JSON envelope */ }
|
|
190
|
+
if (Number.isFinite(_n)) {
|
|
191
|
+
options.onProgress(_n === 0 ? 'no issues' : `${_n} issue${_n === 1 ? '' : 's'}`);
|
|
192
|
+
}
|
|
193
|
+
} catch { /* best-effort */ }
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function _executeDiagnosticsImpl(args, workDir) {
|
|
199
|
+
try {
|
|
200
|
+
// Resolve the target path (file or dir). Default: cwd.
|
|
201
|
+
const rawPath = (args && typeof args.path === 'string' && args.path.trim()) ? args.path.trim() : '';
|
|
202
|
+
const absTarget = rawPath
|
|
203
|
+
? (isAbsolute(rawPath) ? rawPath : pathResolve(workDir, rawPath))
|
|
204
|
+
: workDir;
|
|
205
|
+
if (rawPath && !existsSync(absTarget)) {
|
|
206
|
+
return _formatResult({ note: `path not found: ${rawPath}`, findings: [] });
|
|
207
|
+
}
|
|
208
|
+
let projectDir = absTarget;
|
|
209
|
+
try {
|
|
210
|
+
if (statSync(absTarget).isFile()) projectDir = dirname(absTarget);
|
|
211
|
+
} catch { /* fall through with absTarget */ }
|
|
212
|
+
|
|
213
|
+
let plan = _detectChecker(projectDir);
|
|
214
|
+
// A file deep in the tree (src/search/lib/foo.mjs) has its project
|
|
215
|
+
// marker at the repo ROOT — walk parent directories until a marker is
|
|
216
|
+
// found instead of reporting "no recognized project marker" from the
|
|
217
|
+
// file's immediate dirname. Stops at the first directory that yields
|
|
218
|
+
// any non-"no marker" answer (including "package.json present but no
|
|
219
|
+
// eslint"), or at the filesystem root.
|
|
220
|
+
{
|
|
221
|
+
const _noMarker = (p) => p.kind === 'none' && String(p.reason || '').startsWith('no recognized project marker');
|
|
222
|
+
let walk = projectDir;
|
|
223
|
+
while (_noMarker(plan)) {
|
|
224
|
+
const parent = dirname(walk);
|
|
225
|
+
if (!parent || parent === walk) break;
|
|
226
|
+
walk = parent;
|
|
227
|
+
plan = _detectChecker(walk);
|
|
228
|
+
}
|
|
229
|
+
if (!_noMarker(plan)) projectDir = walk;
|
|
230
|
+
}
|
|
231
|
+
if (plan.kind === 'none') {
|
|
232
|
+
// Syntax-check fallback: a JS file target can still be validated
|
|
233
|
+
// with `node --check` even when the project has no lint setup —
|
|
234
|
+
// catch the dominant failure class (parse errors) instead of
|
|
235
|
+
// going dark with "no checker".
|
|
236
|
+
let isJsFile = false;
|
|
237
|
+
try { isJsFile = /\.(mjs|cjs|js)$/i.test(absTarget) && statSync(absTarget).isFile(); } catch { /* keep false */ }
|
|
238
|
+
if (isJsFile) {
|
|
239
|
+
const env = { ...process.env, LANG: 'C.UTF-8', LC_ALL: 'C.UTF-8' };
|
|
240
|
+
if (await _commandExists('node', projectDir, env)) {
|
|
241
|
+
const r = await _runChecker(`node --check "${absTarget}"`, projectDir, env);
|
|
242
|
+
const combined = `${r.stdout}\n${r.stderr}`;
|
|
243
|
+
const findings = _parseNodeCheck(combined);
|
|
244
|
+
return _formatResult({ checker: 'node --check (no-lint fallback)', findings, raw: combined, note: `${plan.reason} — fell back to syntax check only` });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return _formatResult({ note: `no checker available for this path — ${plan.reason}`, findings: [] });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const env = { ...process.env, LANG: 'C.UTF-8', LC_ALL: 'C.UTF-8' };
|
|
251
|
+
|
|
252
|
+
// Python is special: prefer ruff, fall back to pyflakes.
|
|
253
|
+
if (plan.kind === 'python') {
|
|
254
|
+
if (await _commandExists('ruff', projectDir, env)) {
|
|
255
|
+
const r = await _runChecker('ruff check .', projectDir, env);
|
|
256
|
+
const findings = _parseRuff(`${r.stdout}\n${r.stderr}`);
|
|
257
|
+
return _formatResult({ marker: plan.marker, checker: 'ruff check', findings, raw: `${r.stdout}\n${r.stderr}` });
|
|
258
|
+
}
|
|
259
|
+
if (await _commandExists('python', projectDir, env)) {
|
|
260
|
+
const r = await _runChecker('python -m pyflakes .', projectDir, env);
|
|
261
|
+
const out = `${r.stdout}\n${r.stderr}`;
|
|
262
|
+
if (/No module named pyflakes/i.test(out)) {
|
|
263
|
+
return _formatResult({ marker: plan.marker, note: 'no checker available for Python — ruff absent and pyflakes module not installed', findings: [] });
|
|
264
|
+
}
|
|
265
|
+
const findings = _parsePyflakes(out);
|
|
266
|
+
return _formatResult({ marker: plan.marker, checker: 'python -m pyflakes (ruff fallback)', findings, raw: out });
|
|
267
|
+
}
|
|
268
|
+
return _formatResult({ marker: plan.marker, note: 'no checker available for Python — neither ruff nor python found on PATH', findings: [] });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!(await _commandExists(plan.bin, projectDir, env))) {
|
|
272
|
+
return _formatResult({ marker: plan.marker, note: `no checker available for ${plan.kind} — "${plan.bin}" not found on PATH`, findings: [] });
|
|
273
|
+
}
|
|
274
|
+
const r = await _runChecker(plan.command, projectDir, env);
|
|
275
|
+
const combined = `${r.stdout}\n${r.stderr}`;
|
|
276
|
+
const findings = plan.parse(combined);
|
|
277
|
+
const note = r.timedOut ? `checker timed out after ${DIAGNOSTICS_TIMEOUT_MS}ms` : null;
|
|
278
|
+
return _formatResult({ marker: plan.marker, checker: plan.command, findings, raw: combined, note });
|
|
279
|
+
} catch (err) {
|
|
280
|
+
// Never throw — surface a graceful diagnostic envelope.
|
|
281
|
+
return JSON.stringify({ ok: false, error: `diagnostics failed: ${err && err.message ? err.message : String(err)}`, findings: [] }, null, 2);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function _isDir(p, workDir) {
|
|
286
|
+
try {
|
|
287
|
+
const abs = isAbsolute(p) ? p : pathResolve(workDir, p);
|
|
288
|
+
return statSync(abs).isDirectory();
|
|
289
|
+
} catch { return false; }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export default executeDiagnosticsTool;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Self-contained unified diff so the plugin does not need to take on an
|
|
2
|
+
// external `diff` npm dep. LCS dynamic-programming table is O(n*m) memory
|
|
3
|
+
// and time for normal tool-sized inputs; very large inputs fall back to a
|
|
4
|
+
// compact "files differ" summary.
|
|
5
|
+
export function computeUnifiedDiff(a, b, ctx, fromLabel, toLabel) {
|
|
6
|
+
const n = a.length;
|
|
7
|
+
const m = b.length;
|
|
8
|
+
if (n > 10000 || m > 10000 || n * m > 4_000_000) {
|
|
9
|
+
if (n === m) {
|
|
10
|
+
let same = true;
|
|
11
|
+
for (let k = 0; k < n; k++) {
|
|
12
|
+
if (a[k] !== b[k]) { same = false; break; }
|
|
13
|
+
}
|
|
14
|
+
if (same) return '';
|
|
15
|
+
}
|
|
16
|
+
return `--- ${fromLabel}\n+++ ${toLabel}\n(files too large for inline diff — ${n} vs ${m} lines)`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const dp = Array.from({ length: n + 1 }, () => new Int32Array(m + 1));
|
|
20
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
21
|
+
const aI = a[i];
|
|
22
|
+
const rowI = dp[i];
|
|
23
|
+
const rowI1 = dp[i + 1];
|
|
24
|
+
for (let j = m - 1; j >= 0; j--) {
|
|
25
|
+
if (aI === b[j]) rowI[j] = rowI1[j + 1] + 1;
|
|
26
|
+
else rowI[j] = rowI1[j] >= rowI[j + 1] ? rowI1[j] : rowI[j + 1];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const ops = [];
|
|
31
|
+
let i = 0;
|
|
32
|
+
let j = 0;
|
|
33
|
+
while (i < n && j < m) {
|
|
34
|
+
if (a[i] === b[j]) { ops.push(['=', a[i]]); i++; j++; }
|
|
35
|
+
else if (dp[i + 1][j] >= dp[i][j + 1]) { ops.push(['-', a[i]]); i++; }
|
|
36
|
+
else { ops.push(['+', b[j]]); j++; }
|
|
37
|
+
}
|
|
38
|
+
while (i < n) ops.push(['-', a[i++]]);
|
|
39
|
+
while (j < m) ops.push(['+', b[j++]]);
|
|
40
|
+
|
|
41
|
+
if (!ops.some((op) => op[0] !== '=')) return '';
|
|
42
|
+
|
|
43
|
+
const hunks = [];
|
|
44
|
+
let aLine = 1;
|
|
45
|
+
let bLine = 1;
|
|
46
|
+
let current = null;
|
|
47
|
+
let eqRun = 0;
|
|
48
|
+
const openHunk = (aStart, bStart) => ({ aStart, bStart, aCount: 0, bCount: 0, lines: [] });
|
|
49
|
+
|
|
50
|
+
for (let k = 0; k < ops.length; k++) {
|
|
51
|
+
const [op, line] = ops[k];
|
|
52
|
+
if (op === '=') {
|
|
53
|
+
if (current) {
|
|
54
|
+
let nextChangeWithin = false;
|
|
55
|
+
for (let la = 1; la <= ctx && k + la < ops.length; la++) {
|
|
56
|
+
if (ops[k + la][0] !== '=') { nextChangeWithin = true; break; }
|
|
57
|
+
}
|
|
58
|
+
if (nextChangeWithin || eqRun < ctx) {
|
|
59
|
+
current.lines.push([' ', line]);
|
|
60
|
+
current.aCount++;
|
|
61
|
+
current.bCount++;
|
|
62
|
+
eqRun++;
|
|
63
|
+
} else {
|
|
64
|
+
hunks.push(current);
|
|
65
|
+
current = null;
|
|
66
|
+
eqRun = 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
aLine++;
|
|
70
|
+
bLine++;
|
|
71
|
+
} else {
|
|
72
|
+
if (!current) {
|
|
73
|
+
const leading = [];
|
|
74
|
+
let leadA = 0;
|
|
75
|
+
let leadB = 0;
|
|
76
|
+
for (let back = k - 1; back >= 0 && leading.length < ctx; back--) {
|
|
77
|
+
if (ops[back][0] !== '=') break;
|
|
78
|
+
leading.unshift([' ', ops[back][1]]);
|
|
79
|
+
leadA++;
|
|
80
|
+
leadB++;
|
|
81
|
+
}
|
|
82
|
+
current = openHunk(aLine - leadA, bLine - leadB);
|
|
83
|
+
current.lines.push(...leading);
|
|
84
|
+
current.aCount += leadA;
|
|
85
|
+
current.bCount += leadB;
|
|
86
|
+
}
|
|
87
|
+
if (op === '-') {
|
|
88
|
+
current.lines.push(['-', line]);
|
|
89
|
+
current.aCount++;
|
|
90
|
+
aLine++;
|
|
91
|
+
} else {
|
|
92
|
+
current.lines.push(['+', line]);
|
|
93
|
+
current.bCount++;
|
|
94
|
+
bLine++;
|
|
95
|
+
}
|
|
96
|
+
eqRun = 0;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (current) hunks.push(current);
|
|
100
|
+
|
|
101
|
+
const out = [`--- ${fromLabel}`, `+++ ${toLabel}`];
|
|
102
|
+
for (const h of hunks) {
|
|
103
|
+
const aHdr = h.aCount === 0 ? `${h.aStart - 1},0` : (h.aCount === 1 ? `${h.aStart}` : `${h.aStart},${h.aCount}`);
|
|
104
|
+
const bHdr = h.bCount === 0 ? `${h.bStart - 1},0` : (h.bCount === 1 ? `${h.bStart}` : `${h.bStart},${h.bCount}`);
|
|
105
|
+
out.push(`@@ -${aHdr} +${bHdr} @@`);
|
|
106
|
+
for (const [sign, line] of h.lines) out.push(`${sign}${line}`);
|
|
107
|
+
}
|
|
108
|
+
return out.join('\n');
|
|
109
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readFileSync, statSync } from 'fs';
|
|
2
|
+
import { hashText } from './hash-utils.mjs';
|
|
3
|
+
import { statMatchesSnapshot } from './snapshot-helpers.mjs';
|
|
4
|
+
import { normalizeErrorMessage } from './path-diagnostics.mjs';
|
|
5
|
+
import { getPathMutationGeneration } from './cache-layers.mjs';
|
|
6
|
+
|
|
7
|
+
// Change detection: a full stat match (size + mtime±1 + ctime±1) is
|
|
8
|
+
// proof the file is untouched since the preflight snapshot — accept
|
|
9
|
+
// without re-reading the body (fast-path). Only when stat drifts do we
|
|
10
|
+
// fall back to the authoritative content-hash compare. Tradeoff
|
|
11
|
+
// (accepted policy, parity with write's isSnapshotStale): a size-
|
|
12
|
+
// preserving write that ALSO restores mtime AND ctime is not caught —
|
|
13
|
+
// vanishingly rare, since ctime is not userland-settable on the usual
|
|
14
|
+
// platforms.
|
|
15
|
+
export function validatePreparedEditBase(prepared) {
|
|
16
|
+
if (!prepared || !prepared.fullPath) return 'Error [code 7]: edit prewrite check failed — missing prepared file path';
|
|
17
|
+
// In-process CAS, checked before the stat fast-path below. Every committed
|
|
18
|
+
// write bumps the target's mutation generation (bumpPathMutationGeneration,
|
|
19
|
+
// reached via invalidateBuiltinResultCache after each atomic/byte write).
|
|
20
|
+
// If the generation captured at preflight no longer matches, a concurrent
|
|
21
|
+
// in-process edit committed between this edit's snapshot and now. This
|
|
22
|
+
// signal is deterministic and independent of mtime/size granularity — it
|
|
23
|
+
// closes the window where the stat fast-path (size + mtime±1 + ctime±1)
|
|
24
|
+
// false-negatives on a same-size write that lands within the mtime
|
|
25
|
+
// tolerance (e.g. two concurrent cross-file batches racing the same
|
|
26
|
+
// targets), which would otherwise wave drift through. Only enforced when a
|
|
27
|
+
// generation was captured (Number.isFinite) so callers that omit it keep
|
|
28
|
+
// their prior behavior; sequential edits capture and commit without an
|
|
29
|
+
// intervening write, so the generation is stable and never false-positives.
|
|
30
|
+
if (Number.isFinite(prepared.baseMutationGeneration)
|
|
31
|
+
&& getPathMutationGeneration(prepared.fullPath) !== prepared.baseMutationGeneration) {
|
|
32
|
+
return `Error [code 7]: file modified between edit preflight and write — read it again before editing: ${prepared.filePath}`;
|
|
33
|
+
}
|
|
34
|
+
let currentStat;
|
|
35
|
+
try { currentStat = statSync(prepared.fullPath); }
|
|
36
|
+
catch (err) {
|
|
37
|
+
return `Error [code 7]: file changed before edit write — read it again before editing: ${prepared.filePath} (${normalizeErrorMessage(err instanceof Error ? err.message : String(err))})`;
|
|
38
|
+
}
|
|
39
|
+
// Fast-path: clean stat match ⇒ untouched, accept without reading.
|
|
40
|
+
// statMatchesSnapshot returns false when baseStatSnapshot is missing
|
|
41
|
+
// or incomplete, so an incomplete snapshot falls through to the
|
|
42
|
+
// fail-closed / content-hash path below (no fail-open).
|
|
43
|
+
if (statMatchesSnapshot(currentStat, prepared.baseStatSnapshot)) return null;
|
|
44
|
+
// Stat drifted (or no stat material) → content hash is authoritative.
|
|
45
|
+
// Without baseContentHash we cannot prove identity, so fail closed.
|
|
46
|
+
if (!prepared.baseContentHash) {
|
|
47
|
+
return `Error [code 7]: file modified between edit preflight and write — read it again before editing: ${prepared.filePath}`;
|
|
48
|
+
}
|
|
49
|
+
let current;
|
|
50
|
+
try { current = readFileSync(prepared.fullPath); }
|
|
51
|
+
catch (err) {
|
|
52
|
+
return `Error [code 7]: file changed before edit write — read it again before editing: ${prepared.filePath} (${normalizeErrorMessage(err instanceof Error ? err.message : String(err))})`;
|
|
53
|
+
}
|
|
54
|
+
if (hashText(current) !== prepared.baseContentHash) {
|
|
55
|
+
return `Error [code 7]: file modified between edit preflight and write — read it again before editing: ${prepared.filePath}`;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|