ashlr-plugin 1.36.4
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/.agents/plugins/marketplace.json +20 -0
- package/.claude-plugin/marketplace.json +39 -0
- package/.claude-plugin/plugin.json +36 -0
- package/.codex/agents/README.md +9 -0
- package/.codex/agents/ashlr-explorer.md +36 -0
- package/.codex/agents/ashlr-worker.md +38 -0
- package/.codex-plugin/plugin.json +45 -0
- package/.mcp.json +14 -0
- package/LICENSE +21 -0
- package/README.md +311 -0
- package/agents/ashlr-code.md +69 -0
- package/agents/ashlr-explore.md +44 -0
- package/agents/ashlr-plan.md +54 -0
- package/bench/fixtures/notebook-large.ipynb +660 -0
- package/bench/fixtures/notebook-small.ipynb +39 -0
- package/bench/fixtures/tasklist-closed.json +62 -0
- package/bench/fixtures/tasklist-large-mixed.json +27 -0
- package/bench/fixtures/tasklist-sprint-open.json +79 -0
- package/bench/fixtures/websearch-bun-runtime.json +32 -0
- package/bench/fixtures/websearch-react-hooks-patterns.json +32 -0
- package/bench/fixtures/websearch-typescript-generics.json +38 -0
- package/bench/refs/node-sdk/.refrev +1 -0
- package/bench/refs/node-sdk/README.md +17 -0
- package/bench/refs/node-sdk/src/agent/agent.ts +163 -0
- package/bench/refs/node-sdk/src/agent/create-agent-ui-stream.ts +83 -0
- package/bench/refs/node-sdk/src/agent/tool-loop-agent.ts +238 -0
- package/bench/refs/node-sdk/src/embed/embed-many.ts +409 -0
- package/bench/refs/node-sdk/src/embed/embed-result.ts +50 -0
- package/bench/refs/node-sdk/src/embed/embed.ts +257 -0
- package/bench/refs/node-sdk/src/error/invalid-argument-error.ts +34 -0
- package/bench/refs/node-sdk/src/error/invalid-tool-input-error.ts +33 -0
- package/bench/refs/node-sdk/src/error/no-object-generated-error.ts +70 -0
- package/bench/refs/node-sdk/src/generate-image/generate-image-result.ts +42 -0
- package/bench/refs/node-sdk/src/generate-image/generate-image.ts +362 -0
- package/bench/refs/node-sdk/src/generate-object/generate-object-result.ts +67 -0
- package/bench/refs/node-sdk/src/generate-object/generate-object.ts +501 -0
- package/bench/refs/node-sdk/src/generate-object/inject-json-instruction.ts +30 -0
- package/bench/refs/node-sdk/src/generate-object/output-strategy.ts +415 -0
- package/bench/refs/node-sdk/src/generate-object/repair-text.ts +12 -0
- package/bench/refs/node-sdk/src/generate-object/stream-object.ts +960 -0
- package/bench/refs/node-sdk/src/generate-speech/generate-speech.ts +191 -0
- package/bench/refs/node-sdk/src/generate-text/generate-text.ts +1484 -0
- package/bench/refs/node-sdk/src/generate-text/stream-text.ts +2708 -0
- package/bench/refs/node-sdk/src/generate-text/tool-call.ts +59 -0
- package/bench/refs/node-sdk/src/generate-text/tool-result.ts +36 -0
- package/bench/refs/node-sdk/src/telemetry/create-telemetry-dispatcher.ts +146 -0
- package/bench/refs/node-sdk/src/telemetry/telemetry-registry.ts +15 -0
- package/bench/refs/node-sdk/src/telemetry/telemetry.ts +223 -0
- package/bench/refs/node-sdk/src/util/async-iterable-stream.ts +94 -0
- package/bench/refs/node-sdk/src/util/consume-stream.ts +31 -0
- package/bench/refs/node-sdk/src/util/create-stitchable-stream.ts +112 -0
- package/bench/refs/node-sdk/src/util/download/download.ts +74 -0
- package/bench/refs/node-sdk/src/util/error-handler.ts +1 -0
- package/bench/refs/node-sdk/src/util/fix-json.ts +401 -0
- package/bench/refs/node-sdk/src/util/merge-objects.ts +79 -0
- package/bench/refs/python-lib/.refrev +1 -0
- package/bench/refs/python-lib/README.md +18 -0
- package/bench/refs/python-lib/pandas/core/_numba/executor.py +249 -0
- package/bench/refs/python-lib/pandas/core/_numba/extensions.py +586 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/mean_.py +198 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/min_max_.py +178 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/sum_.py +255 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/var_.py +251 -0
- package/bench/refs/python-lib/pandas/core/accessor.py +520 -0
- package/bench/refs/python-lib/pandas/core/api.py +138 -0
- package/bench/refs/python-lib/pandas/core/array_algos/masked_accumulations.py +97 -0
- package/bench/refs/python-lib/pandas/core/array_algos/masked_reductions.py +252 -0
- package/bench/refs/python-lib/pandas/core/array_algos/putmask.py +150 -0
- package/bench/refs/python-lib/pandas/core/array_algos/quantile.py +232 -0
- package/bench/refs/python-lib/pandas/core/array_algos/replace.py +159 -0
- package/bench/refs/python-lib/pandas/core/array_algos/take.py +609 -0
- package/bench/refs/python-lib/pandas/core/arraylike.py +534 -0
- package/bench/refs/python-lib/pandas/core/arrays/_arrow_string_mixins.py +434 -0
- package/bench/refs/python-lib/pandas/core/arrays/_mixins.py +630 -0
- package/bench/refs/python-lib/pandas/core/arrays/_ranges.py +276 -0
- package/bench/refs/python-lib/pandas/core/arrays/_utils.py +77 -0
- package/bench/refs/python-lib/pandas/core/arrays/arrow/accessors.py +521 -0
- package/bench/refs/python-lib/pandas/core/arrays/arrow/extension_types.py +174 -0
- package/bench/refs/python-lib/pandas/core/arrays/boolean.py +454 -0
- package/bench/refs/python-lib/pandas/core/arrays/floating.py +192 -0
- package/bench/refs/python-lib/pandas/core/arrays/integer.py +296 -0
- package/bench/refs/python-lib/pandas/core/arrays/masked.py +2140 -0
- package/bench/refs/python-lib/pandas/core/arrays/numeric.py +305 -0
- package/bench/refs/python-lib/pandas/core/arrays/numpy_.py +701 -0
- package/bench/refs/python-lib/pandas/core/arrays/period.py +1586 -0
- package/bench/refs/python-lib/pandas/core/arrays/sparse/accessor.py +505 -0
- package/bench/refs/python-lib/pandas/core/arrays/sparse/scipy_sparse.py +208 -0
- package/bench/refs/rust-project/.refrev +1 -0
- package/bench/refs/rust-project/README.md +17 -0
- package/bench/refs/rust-project/tokio/src/doc/os.rs +68 -0
- package/bench/refs/rust-project/tokio/src/fs/dir_builder.rs +129 -0
- package/bench/refs/rust-project/tokio/src/fs/file.rs +1113 -0
- package/bench/refs/rust-project/tokio/src/fs/mocks.rs +176 -0
- package/bench/refs/rust-project/tokio/src/fs/mod.rs +324 -0
- package/bench/refs/rust-project/tokio/src/fs/open_options.rs +856 -0
- package/bench/refs/rust-project/tokio/src/fs/read.rs +80 -0
- package/bench/refs/rust-project/tokio/src/fs/read_dir.rs +357 -0
- package/bench/refs/rust-project/tokio/src/fs/write.rs +99 -0
- package/bench/refs/rust-project/tokio/src/future/maybe_done.rs +123 -0
- package/bench/refs/rust-project/tokio/src/future/try_join.rs +82 -0
- package/bench/refs/rust-project/tokio/src/io/async_buf_read.rs +117 -0
- package/bench/refs/rust-project/tokio/src/io/async_read.rs +133 -0
- package/bench/refs/rust-project/tokio/src/io/async_seek.rs +96 -0
- package/bench/refs/rust-project/tokio/src/io/async_write.rs +402 -0
- package/bench/refs/rust-project/tokio/src/io/blocking.rs +339 -0
- package/bench/refs/rust-project/tokio/src/io/interest.rs +345 -0
- package/bench/refs/rust-project/tokio/src/io/join.rs +130 -0
- package/bench/refs/rust-project/tokio/src/io/mod.rs +308 -0
- package/bench/refs/rust-project/tokio/src/io/poll_evented.rs +319 -0
- package/bench/refs/rust-project/tokio/src/io/read_buf.rs +344 -0
- package/bench/refs/rust-project/tokio/src/io/ready.rs +322 -0
- package/bench/refs/rust-project/tokio/src/io/split.rs +166 -0
- package/bench/refs/rust-project/tokio/src/io/stderr.rs +148 -0
- package/bench/refs/rust-project/tokio/src/io/stdin.rs +98 -0
- package/bench/refs/rust-project/tokio/src/io/stdio_common.rs +222 -0
- package/bench/refs/rust-project/tokio/src/io/stdout.rs +197 -0
- package/bench/refs/rust-project/tokio/src/io/util/buf_reader.rs +311 -0
- package/bench/refs/rust-project/tokio/src/io/util/buf_stream.rs +207 -0
- package/bench/refs/rust-project/tokio/src/io/util/buf_writer.rs +310 -0
- package/bench/refs/rust-project/tokio/src/io/util/chain.rs +144 -0
- package/commands/_spawn-patterns.json +32 -0
- package/commands/ashlr-allow.md +21 -0
- package/commands/ashlr-benchmark.md +53 -0
- package/commands/ashlr-brief.md +97 -0
- package/commands/ashlr-budget.md +76 -0
- package/commands/ashlr-compact.md +54 -0
- package/commands/ashlr-dashboard.md +37 -0
- package/commands/ashlr-doctor.md +56 -0
- package/commands/ashlr-eco-mode.md +92 -0
- package/commands/ashlr-genome-init.md +24 -0
- package/commands/ashlr-genome-keygen.md +36 -0
- package/commands/ashlr-genome-loop.md +118 -0
- package/commands/ashlr-genome-push.md +36 -0
- package/commands/ashlr-genome-rewrap.md +48 -0
- package/commands/ashlr-genome-team-init.md +67 -0
- package/commands/ashlr-handoff.md +23 -0
- package/commands/ashlr-help.md +86 -0
- package/commands/ashlr-hook-timings.md +30 -0
- package/commands/ashlr-leaderboard.md +50 -0
- package/commands/ashlr-ollama-setup.md +39 -0
- package/commands/ashlr-orchestrate-status.md +72 -0
- package/commands/ashlr-orchestrate.md +83 -0
- package/commands/ashlr-parallelize.md +63 -0
- package/commands/ashlr-report-crash.md +33 -0
- package/commands/ashlr-resume.md +72 -0
- package/commands/ashlr-savings.md +24 -0
- package/commands/ashlr-settings.md +50 -0
- package/commands/ashlr-spawn.md +61 -0
- package/commands/ashlr-start.md +60 -0
- package/commands/ashlr-status.md +64 -0
- package/commands/ashlr-team-invite.md +47 -0
- package/commands/ashlr-tier.md +122 -0
- package/commands/ashlr-tour.md +60 -0
- package/commands/ashlr-update.md +155 -0
- package/commands/ashlr-upgrade.md +96 -0
- package/commands/goal.md +58 -0
- package/commands/loop.md +40 -0
- package/docs/CNAME +1 -0
- package/docs/PRO_TIER.md +104 -0
- package/docs/README.md +21 -0
- package/docs/architecture.md +415 -0
- package/docs/ashlr-pipe.md +161 -0
- package/docs/assets/logo.svg +19 -0
- package/docs/assets/og.png +0 -0
- package/docs/assets/og.svg +41 -0
- package/docs/benchmarks-v2.json +355 -0
- package/docs/benchmarks.json +102 -0
- package/docs/benchmarks.md +211 -0
- package/docs/billing.md +185 -0
- package/docs/cloud-genome.md +155 -0
- package/docs/competitive-wozcode.md +92 -0
- package/docs/deploy.md +325 -0
- package/docs/email-templates.md +113 -0
- package/docs/embed-tuning.md +103 -0
- package/docs/genome.md +120 -0
- package/docs/github-oauth-onboarding.md +127 -0
- package/docs/hero-video-script.md +129 -0
- package/docs/index.html +2262 -0
- package/docs/install-prompt.md +78 -0
- package/docs/install-windows.md +132 -0
- package/docs/install.ps1 +171 -0
- package/docs/install.sh +146 -0
- package/docs/launch/README.md +114 -0
- package/docs/launch/hn.md +49 -0
- package/docs/launch/reddit.md +45 -0
- package/docs/launch/x.md +65 -0
- package/docs/launch-post.md +112 -0
- package/docs/legal.md +78 -0
- package/docs/llm-providers.md +99 -0
- package/docs/multi-host-mcp.md +217 -0
- package/docs/multi-turn-architecture.md +108 -0
- package/docs/operations/deployment.md +108 -0
- package/docs/operations/error-monitoring.md +70 -0
- package/docs/operations/weekly-digest.md +70 -0
- package/docs/operations.md +494 -0
- package/docs/platform-support.md +133 -0
- package/docs/policy-packs.md +188 -0
- package/docs/press-kit/fact-sheet.md +78 -0
- package/docs/press-kit/founder-bio.md +3 -0
- package/docs/press-kit/logo.svg +33 -0
- package/docs/press-kit/screenshots.md +146 -0
- package/docs/pricing.md +251 -0
- package/docs/pro-backend-architecture.md +523 -0
- package/docs/producthunt.md +52 -0
- package/docs/router-migration.md +110 -0
- package/docs/session-log-schema.md +68 -0
- package/docs/social.md +95 -0
- package/docs/team-genome.md +528 -0
- package/docs/telemetry.md +194 -0
- package/docs/test-isolation.md +100 -0
- package/docs/upgrade.md +159 -0
- package/hooks/_hook-errors.ts +87 -0
- package/hooks/_hook-perf-emit.ts +113 -0
- package/hooks/_hook-timing-ledger.ts +1133 -0
- package/hooks/_nudge-throttle.ts +127 -0
- package/hooks/_recent-blocks.ts +127 -0
- package/hooks/audit-upload.ts +86 -0
- package/hooks/codex-hooks.json +171 -0
- package/hooks/commit-attribution.ts +179 -0
- package/hooks/edit-batching-nudge.ts +154 -0
- package/hooks/genome-scribe-hook.ts +219 -0
- package/hooks/hooks.json +294 -0
- package/hooks/orient-nudge-hook.ts +242 -0
- package/hooks/policy-enforce.ts +214 -0
- package/hooks/post-tool-use-embedding.ts +93 -0
- package/hooks/post-tool-use-genome.sh +37 -0
- package/hooks/post-tool-use-genome.ts +69 -0
- package/hooks/posttooluse-correlate.ts +122 -0
- package/hooks/posttooluse-dedupe-output.ts +241 -0
- package/hooks/posttooluse-genome-refresh.ts +223 -0
- package/hooks/posttooluse-native-nudge.ts +205 -0
- package/hooks/posttooluse-stale-result.ts +277 -0
- package/hooks/pretooluse-bash.ts +63 -0
- package/hooks/pretooluse-budget-guard.ts +318 -0
- package/hooks/pretooluse-common.ts +985 -0
- package/hooks/pretooluse-eco-router.ts +196 -0
- package/hooks/pretooluse-edit.sh +93 -0
- package/hooks/pretooluse-edit.ts +197 -0
- package/hooks/pretooluse-glob.ts +70 -0
- package/hooks/pretooluse-grep.sh +85 -0
- package/hooks/pretooluse-grep.ts +96 -0
- package/hooks/pretooluse-notebookedit.ts +121 -0
- package/hooks/pretooluse-read.sh +118 -0
- package/hooks/pretooluse-read.ts +118 -0
- package/hooks/pretooluse-task.ts +130 -0
- package/hooks/pretooluse-webfetch.ts +66 -0
- package/hooks/pretooluse-websearch.ts +85 -0
- package/hooks/pretooluse-write.ts +128 -0
- package/hooks/pulse-emit.ts +197 -0
- package/hooks/session-end-consolidate.sh +35 -0
- package/hooks/session-end-consolidate.ts +188 -0
- package/hooks/session-end-stats-gc.ts +80 -0
- package/hooks/session-log-append.sh +94 -0
- package/hooks/session-log-append.ts +116 -0
- package/hooks/session-start.ts +993 -0
- package/hooks/sessionend-hook-health-nudge.ts +451 -0
- package/hooks/sessionend-savings-nudge.ts +318 -0
- package/hooks/sessionend-status-line-nudge.ts +296 -0
- package/hooks/sessionstart-brief.ts +140 -0
- package/hooks/sessionstart-cost-refactor.ts +95 -0
- package/hooks/sessionstart-efficient.ts +110 -0
- package/hooks/sessionstart-genome-author.ts +93 -0
- package/hooks/sessionstart-lean-tools.ts +98 -0
- package/hooks/sessionstart-search.ts +97 -0
- package/hooks/stop-accounting.ts +152 -0
- package/hooks/subagent-stop-rollup.ts +162 -0
- package/hooks/userpromptsubmit-brief-trigger.ts +156 -0
- package/package.json +56 -0
- package/ports/README.md +108 -0
- package/ports/cursor/mcp.json +11 -0
- package/ports/goose/recipe.yaml +26 -0
- package/scripts/ashlr-mcp.ts +40 -0
- package/scripts/auto-update.ts +177 -0
- package/scripts/baseline-scan.ts +831 -0
- package/scripts/bench-orchestrate.ts +377 -0
- package/scripts/benchmark-refs.ts +292 -0
- package/scripts/bootstrap.mjs +100 -0
- package/scripts/brief-eval.ts +234 -0
- package/scripts/bump-version.ts +82 -0
- package/scripts/bun-resolve.d.mts +11 -0
- package/scripts/bun-resolve.mjs +59 -0
- package/scripts/calibrate-grep.ts +467 -0
- package/scripts/capture-grep-workload.ts +260 -0
- package/scripts/check-tier.ts +61 -0
- package/scripts/check-version-sync.ts +97 -0
- package/scripts/cli-orchestrate-status.ts +386 -0
- package/scripts/cli-orchestrate.ts +303 -0
- package/scripts/cli.ts +478 -0
- package/scripts/cloud-smoke-test.ts +313 -0
- package/scripts/coach-report.ts +373 -0
- package/scripts/context-db-migrate.ts +137 -0
- package/scripts/context-status.ts +43 -0
- package/scripts/demo-run.ts +255 -0
- package/scripts/deploy-smoke.sh +140 -0
- package/scripts/doctor.ts +992 -0
- package/scripts/embed-file-worker.ts +104 -0
- package/scripts/embed-tune.ts +308 -0
- package/scripts/errors-report.ts +428 -0
- package/scripts/find-test-leak.ts +69 -0
- package/scripts/generate-badge.ts +357 -0
- package/scripts/genome-auto-consolidate.ts +533 -0
- package/scripts/genome-auto-propose.ts +578 -0
- package/scripts/genome-cloud-pull.ts +363 -0
- package/scripts/genome-cloud-push.ts +382 -0
- package/scripts/genome-cloud-sync.ts +587 -0
- package/scripts/genome-commit-watcher.ts +295 -0
- package/scripts/genome-init.ts +986 -0
- package/scripts/genome-key.ts +233 -0
- package/scripts/genome-keygen.ts +168 -0
- package/scripts/genome-link.ts +59 -0
- package/scripts/genome-refresh-worker.ts +364 -0
- package/scripts/genome-rewrap.ts +90 -0
- package/scripts/genome-synthesizer.ts +679 -0
- package/scripts/genome-team-init.ts +312 -0
- package/scripts/handoff-pack.ts +438 -0
- package/scripts/hook-bootstrap.mjs +75 -0
- package/scripts/hook-timing-reader.ts +371 -0
- package/scripts/hook-timings-report.ts +554 -0
- package/scripts/install-genome-hooks.ts +196 -0
- package/scripts/install-onnx-model.ts +154 -0
- package/scripts/install-permissions.ts +276 -0
- package/scripts/install-status-line.ts +150 -0
- package/scripts/mcp-entrypoint.sh +78 -0
- package/scripts/mcp-entrypoint.ts +130 -0
- package/scripts/mdx-angle-bracket-lint.ts +152 -0
- package/scripts/measure-cold-start.ts +126 -0
- package/scripts/migrate-stats-to-sqlite.ts +86 -0
- package/scripts/ollama-setup.ts +498 -0
- package/scripts/onboarding-wizard.ts +1691 -0
- package/scripts/orchestrate-emit-telemetry.ts +177 -0
- package/scripts/orchestrate-expand.ts +517 -0
- package/scripts/orchestrate-run.ts +744 -0
- package/scripts/print-legend.ts +50 -0
- package/scripts/product-metadata.ts +38 -0
- package/scripts/prune-old-memories.ts +208 -0
- package/scripts/publish.sh +88 -0
- package/scripts/read-calibration.ts +122 -0
- package/scripts/report-crash.ts +226 -0
- package/scripts/run-benchmark.ts +750 -0
- package/scripts/run-root-tests.ts +28 -0
- package/scripts/savings-dashboard.ts +1258 -0
- package/scripts/savings-report-extras.ts +447 -0
- package/scripts/savings-status-line.ts +873 -0
- package/scripts/session-event-emit.ts +266 -0
- package/scripts/session-greet.ts +686 -0
- package/scripts/session-log-report.ts +551 -0
- package/scripts/session-resume.ts +614 -0
- package/scripts/set-hook-mode.ts +83 -0
- package/scripts/smoke-cross-platform.ts +117 -0
- package/scripts/smoke-realtime.ts +257 -0
- package/scripts/smoke-tools.ts +21 -0
- package/scripts/stats-cloud-pull.ts +213 -0
- package/scripts/stats-cloud-sync.ts +261 -0
- package/scripts/telemetry-flush.ts +160 -0
- package/scripts/telemetry-status.ts +266 -0
- package/scripts/timing-budget.ts +35 -0
- package/scripts/ui-animation.ts +120 -0
- package/scripts/ui.ts +431 -0
- package/scripts/upgrade-flow.ts +775 -0
- package/scripts/weekly-digest.ts +394 -0
- package/servers/_accounting.ts +78 -0
- package/servers/_ask-router.ts +84 -0
- package/servers/_ast-chunker.ts +434 -0
- package/servers/_ast-helpers.ts +183 -0
- package/servers/_ast-languages.ts +153 -0
- package/servers/_ast-refactor/_shared.ts +221 -0
- package/servers/_ast-refactor/cross-file-rename.ts +560 -0
- package/servers/_ast-refactor/extract-function.ts +357 -0
- package/servers/_ast-refactor/file-local-rename.ts +172 -0
- package/servers/_ast-refactor.ts +41 -0
- package/servers/_ast-skeleton.ts +107 -0
- package/servers/_bash-summarizers-registry.ts +1096 -0
- package/servers/_crash-dump.ts +210 -0
- package/servers/_cwd-clamp.ts +342 -0
- package/servers/_edit-log.ts +28 -0
- package/servers/_edit-match.ts +236 -0
- package/servers/_edit-validate.ts +196 -0
- package/servers/_embed-calibration.ts +123 -0
- package/servers/_embedding-cache.ts +401 -0
- package/servers/_embedding-model.ts +329 -0
- package/servers/_events.ts +113 -0
- package/servers/_genome-cache.ts +154 -0
- package/servers/_genome-commits.ts +172 -0
- package/servers/_genome-crypto-v2.ts +242 -0
- package/servers/_genome-crypto.ts +259 -0
- package/servers/_genome-discoveries.ts +140 -0
- package/servers/_genome-embed-populator.ts +216 -0
- package/servers/_genome-freshness.ts +134 -0
- package/servers/_genome-issues.ts +171 -0
- package/servers/_genome-live.ts +263 -0
- package/servers/_genome-prs.ts +188 -0
- package/servers/_genome-search.ts +479 -0
- package/servers/_genome-sync.ts +297 -0
- package/servers/_history-tracker.ts +279 -0
- package/servers/_http-helpers.ts +151 -0
- package/servers/_identity-hash.ts +151 -0
- package/servers/_llm-providers/anthropic.ts +132 -0
- package/servers/_llm-providers/cloud.ts +195 -0
- package/servers/_llm-providers/index.ts +350 -0
- package/servers/_llm-providers/local.ts +127 -0
- package/servers/_llm-providers/onnx.ts +393 -0
- package/servers/_llm-providers/types.ts +27 -0
- package/servers/_manifest-v2.ts +447 -0
- package/servers/_mcp-host.ts +93 -0
- package/servers/_nudge-events.ts +451 -0
- package/servers/_orchestrate-executor.ts +316 -0
- package/servers/_prefetch.ts +435 -0
- package/servers/_pricing.ts +256 -0
- package/servers/_pro.ts +381 -0
- package/servers/_read-cache.ts +44 -0
- package/servers/_router-handlers.ts +46 -0
- package/servers/_router.ts +55 -0
- package/servers/_run-with-timeout.ts +173 -0
- package/servers/_savings-render.ts +214 -0
- package/servers/_stats-sqlite.ts +640 -0
- package/servers/_stats.ts +1183 -0
- package/servers/_status-line-cells.ts +469 -0
- package/servers/_streaks.ts +162 -0
- package/servers/_summarize.ts +208 -0
- package/servers/_task-graph-render.ts +217 -0
- package/servers/_task-graph.ts +545 -0
- package/servers/_telemetry.ts +719 -0
- package/servers/_test-parsers.ts +530 -0
- package/servers/_test-watch.ts +626 -0
- package/servers/_text-helpers.ts +37 -0
- package/servers/_token-measure.ts +160 -0
- package/servers/_tool-base.ts +248 -0
- package/servers/_weekly-digest.ts +391 -0
- package/servers/ask-server-handlers.ts +49 -0
- package/servers/ask-server.ts +174 -0
- package/servers/bash-server-handlers.ts +138 -0
- package/servers/bash-server.ts +1136 -0
- package/servers/bench.ts +186 -0
- package/servers/diff-semantic-server-handlers.ts +39 -0
- package/servers/diff-semantic-server.ts +566 -0
- package/servers/diff-server-handlers.ts +317 -0
- package/servers/diff-server.ts +20 -0
- package/servers/edit-server-handlers.ts +31 -0
- package/servers/edit-server.ts +209 -0
- package/servers/edit-structural-server-handlers.ts +322 -0
- package/servers/efficiency-server-handlers.ts +161 -0
- package/servers/efficiency-server.ts +1135 -0
- package/servers/flush-server-handlers.ts +21 -0
- package/servers/flush-server.ts +19 -0
- package/servers/genome-server-handlers.ts +118 -0
- package/servers/genome-server.ts +397 -0
- package/servers/github-server-handlers.ts +187 -0
- package/servers/github-server.ts +856 -0
- package/servers/glob-server-handlers.ts +233 -0
- package/servers/glob-server.ts +20 -0
- package/servers/grep-server-handlers.ts +61 -0
- package/servers/grep-server.ts +564 -0
- package/servers/http-server-handlers.ts +36 -0
- package/servers/http-server.ts +149 -0
- package/servers/logs-server-handlers.ts +42 -0
- package/servers/logs-server.ts +359 -0
- package/servers/ls-server-handlers.ts +166 -0
- package/servers/ls-server.ts +20 -0
- package/servers/multi-edit-server-handlers.ts +51 -0
- package/servers/multi-edit-server.ts +422 -0
- package/servers/notebook-edit-server-handlers.ts +34 -0
- package/servers/notebook-edit-server.ts +181 -0
- package/servers/orient-server-handlers.ts +52 -0
- package/servers/orient-server.ts +436 -0
- package/servers/pipe-server-handlers.ts +68 -0
- package/servers/pipe-server.ts +300 -0
- package/servers/read-server-handlers.ts +42 -0
- package/servers/read-server.ts +206 -0
- package/servers/rename-file-server-handlers.ts +67 -0
- package/servers/rename-file-server.ts +699 -0
- package/servers/savings-server-handlers.ts +27 -0
- package/servers/savings-server.ts +54 -0
- package/servers/search-replace-regex-server-handlers.ts +96 -0
- package/servers/search-replace-regex-server.ts +751 -0
- package/servers/sql-server-handlers.ts +58 -0
- package/servers/sql-server.ts +505 -0
- package/servers/task-server-handlers.ts +358 -0
- package/servers/task-server.ts +24 -0
- package/servers/test-server-handlers.ts +351 -0
- package/servers/test-server.ts +20 -0
- package/servers/tree-server-handlers.ts +503 -0
- package/servers/tree-server.ts +20 -0
- package/servers/webfetch-server-handlers.ts +166 -0
- package/servers/webfetch-server.ts +20 -0
- package/servers/websearch-server-handlers.ts +215 -0
- package/servers/websearch-server.ts +19 -0
- package/servers/write-server-handlers.ts +27 -0
- package/servers/write-server.ts +97 -0
- package/skills/ashlr-brief/SKILL.md +80 -0
- package/skills/ashlr-codex-doctor/SKILL.md +21 -0
- package/skills/ashlr-codex-efficient-tools/SKILL.md +17 -0
- package/skills/ashlr-codex-genome-refresh/SKILL.md +20 -0
- package/skills/ashlr-codex-parallel-explore/SKILL.md +20 -0
- package/skills/ashlr-codex-savings/SKILL.md +17 -0
- package/skills/ashlr-cost-refactor/SKILL.md +50 -0
- package/skills/ashlr-efficient/SKILL.md +64 -0
- package/skills/ashlr-genome-author/SKILL.md +43 -0
- package/skills/ashlr-lean-tools/SKILL.md +43 -0
- package/skills/ashlr-search/SKILL.md +46 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* diff-server-handlers — side-effect module.
|
|
3
|
+
*
|
|
4
|
+
* Importing this file registers the ashlr__diff tool into the shared registry
|
|
5
|
+
* (_tool-base.ts). Used by both the standalone entry point (diff-server.ts)
|
|
6
|
+
* and the router (_router.ts via _router-handlers.ts).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { registerTool, type ToolCallContext, type ToolResult } from "./_tool-base";
|
|
10
|
+
import { spawnSync } from "child_process";
|
|
11
|
+
import { existsSync } from "fs";
|
|
12
|
+
import { summarizeIfLarge, PROMPTS, confidenceBadge, confidenceTier } from "./_summarize";
|
|
13
|
+
import { recordSaving as recordSavingCore } from "./_stats";
|
|
14
|
+
import { logEvent } from "./_events";
|
|
15
|
+
import { clampToCwd } from "./_cwd-clamp";
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// snipCompact for full-mode oversized diffs
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
const FULL_SNIP_THRESHOLD = 4096;
|
|
22
|
+
const HEAD_BYTES = 1600;
|
|
23
|
+
const TAIL_BYTES = 1600;
|
|
24
|
+
|
|
25
|
+
function snipCompact(s: string): string {
|
|
26
|
+
if (s.length <= FULL_SNIP_THRESHOLD) return s;
|
|
27
|
+
const elided = s.length - HEAD_BYTES - TAIL_BYTES;
|
|
28
|
+
return (
|
|
29
|
+
s.slice(0, HEAD_BYTES) +
|
|
30
|
+
`\n[... ${elided.toLocaleString()} bytes of diff elided ...]\n` +
|
|
31
|
+
s.slice(-TAIL_BYTES)
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// git invocation
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
interface DiffArgs {
|
|
40
|
+
ref?: string;
|
|
41
|
+
path?: string;
|
|
42
|
+
cwd?: string;
|
|
43
|
+
mode?: "stat" | "summary" | "full";
|
|
44
|
+
bypassSummary?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveRefArgs(ref: string): { args: string[]; label: string } {
|
|
48
|
+
const r = ref.trim();
|
|
49
|
+
if (r === "staged" || r === "--cached" || r === "cached") {
|
|
50
|
+
return { args: ["--cached"], label: "staged" };
|
|
51
|
+
}
|
|
52
|
+
if (r === "unstaged" || r === "working") {
|
|
53
|
+
return { args: [], label: r };
|
|
54
|
+
}
|
|
55
|
+
return { args: [r], label: r };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function runGit(cwd: string, args: string[]): { ok: boolean; stdout: string; stderr: string } {
|
|
59
|
+
const res = spawnSync("git", ["-C", cwd, ...args], {
|
|
60
|
+
encoding: "utf-8",
|
|
61
|
+
maxBuffer: 100 * 1024 * 1024,
|
|
62
|
+
timeout: 15_000,
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
ok: res.status === 0,
|
|
66
|
+
stdout: res.stdout || "",
|
|
67
|
+
stderr: res.stderr || "",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isGitRepo(cwd: string): boolean {
|
|
72
|
+
const r = runGit(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
73
|
+
return r.ok && r.stdout.trim() === "true";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// numstat + hunk parsing
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
interface FileStat {
|
|
81
|
+
added: number;
|
|
82
|
+
deleted: number;
|
|
83
|
+
path: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function parseNumstat(raw: string): FileStat[] {
|
|
87
|
+
const out: FileStat[] = [];
|
|
88
|
+
for (const line of raw.split("\n")) {
|
|
89
|
+
if (!line) continue;
|
|
90
|
+
const parts = line.split("\t");
|
|
91
|
+
if (parts.length < 3) continue;
|
|
92
|
+
const [a, d, ...rest] = parts;
|
|
93
|
+
const path = rest.join("\t");
|
|
94
|
+
const added = a === "-" ? 0 : Number(a) || 0;
|
|
95
|
+
const deleted = d === "-" ? 0 : Number(d) || 0;
|
|
96
|
+
out.push({ added, deleted, path });
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function padCol(s: string, w: number): string {
|
|
102
|
+
if (s.length >= w) return s;
|
|
103
|
+
return s + " ".repeat(w - s.length);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function renderStat(files: FileStat[], header: string): string {
|
|
107
|
+
const totalAdd = files.reduce((s, f) => s + f.added, 0);
|
|
108
|
+
const totalDel = files.reduce((s, f) => s + f.deleted, 0);
|
|
109
|
+
const lines: string[] = [
|
|
110
|
+
`diff ${header} \u00b7 ${files.length} file${files.length === 1 ? "" : "s"} \u00b7 +${totalAdd} -${totalDel} lines`,
|
|
111
|
+
];
|
|
112
|
+
if (files.length === 0) return lines[0]! + "\n (no changes)";
|
|
113
|
+
const pathW = Math.min(60, Math.max(...files.map((f) => f.path.length)));
|
|
114
|
+
for (const f of files) {
|
|
115
|
+
lines.push(` ${padCol(f.path, pathW)} +${f.added} -${f.deleted}`);
|
|
116
|
+
}
|
|
117
|
+
return lines.join("\n");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface Hunk {
|
|
121
|
+
header: string;
|
|
122
|
+
added: string[];
|
|
123
|
+
deletedCount: number;
|
|
124
|
+
addedCountTotal: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function parseHunksForFile(fileBlock: string): Hunk[] {
|
|
128
|
+
const hunks: Hunk[] = [];
|
|
129
|
+
const lines = fileBlock.split("\n");
|
|
130
|
+
let cur: Hunk | null = null;
|
|
131
|
+
for (const ln of lines) {
|
|
132
|
+
if (ln.startsWith("@@")) {
|
|
133
|
+
if (cur) hunks.push(cur);
|
|
134
|
+
cur = { header: ln, added: [], deletedCount: 0, addedCountTotal: 0 };
|
|
135
|
+
} else if (cur) {
|
|
136
|
+
if (ln.startsWith("+") && !ln.startsWith("+++")) {
|
|
137
|
+
cur.addedCountTotal++;
|
|
138
|
+
cur.added.push(ln);
|
|
139
|
+
} else if (ln.startsWith("-") && !ln.startsWith("---")) {
|
|
140
|
+
cur.deletedCount++;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (cur) hunks.push(cur);
|
|
145
|
+
return hunks;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function splitDiffByFile(raw: string): Map<string, string> {
|
|
149
|
+
const map = new Map<string, string>();
|
|
150
|
+
if (!raw) return map;
|
|
151
|
+
const parts = raw.split(/^diff --git /m);
|
|
152
|
+
for (const part of parts) {
|
|
153
|
+
if (!part.trim()) continue;
|
|
154
|
+
const firstLine = part.split("\n", 1)[0] || "";
|
|
155
|
+
const m = firstLine.match(/ b\/(.+)$/);
|
|
156
|
+
const path = m ? m[1]! : firstLine.trim();
|
|
157
|
+
map.set(path, "diff --git " + part);
|
|
158
|
+
}
|
|
159
|
+
return map;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function renderSummary(files: FileStat[], header: string, rawDiff: string): string {
|
|
163
|
+
const stat = renderStat(files, header);
|
|
164
|
+
const blocks = splitDiffByFile(rawDiff);
|
|
165
|
+
const out: string[] = [stat, ""];
|
|
166
|
+
const KEY_ADDED_SHOWN = 10;
|
|
167
|
+
for (const f of files) {
|
|
168
|
+
const block = blocks.get(f.path);
|
|
169
|
+
out.push(`${f.path} +${f.added} -${f.deleted}`);
|
|
170
|
+
if (!block) {
|
|
171
|
+
out.push(" (no hunks)");
|
|
172
|
+
out.push("");
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const hunks = parseHunksForFile(block);
|
|
176
|
+
const hottest = [...hunks].sort((a, b) => b.addedCountTotal - a.addedCountTotal).slice(0, 2);
|
|
177
|
+
if (hottest.length === 0) {
|
|
178
|
+
out.push(" (no hunks)");
|
|
179
|
+
}
|
|
180
|
+
for (const h of hottest) {
|
|
181
|
+
out.push(` ${h.header}`);
|
|
182
|
+
const shown = h.added.slice(0, KEY_ADDED_SHOWN);
|
|
183
|
+
for (const s of shown) {
|
|
184
|
+
out.push(` ${s}`);
|
|
185
|
+
}
|
|
186
|
+
const moreAdded = Math.max(0, h.addedCountTotal - shown.length);
|
|
187
|
+
if (moreAdded > 0 || h.deletedCount > 0) {
|
|
188
|
+
out.push(` [... ${moreAdded} more added \u00b7 ${h.deletedCount} deleted ...]`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
out.push("");
|
|
192
|
+
}
|
|
193
|
+
return out.join("\n").replace(/\n+$/, "");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Core logic (exported for tests)
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
export async function ashlrDiff(args: DiffArgs): Promise<string> {
|
|
201
|
+
const clamp = clampToCwd(args.cwd, "ashlr__diff");
|
|
202
|
+
if (!clamp.ok) return clamp.message;
|
|
203
|
+
const cwd = clamp.abs;
|
|
204
|
+
if (!existsSync(cwd)) {
|
|
205
|
+
throw new Error(`cwd does not exist: ${cwd}`);
|
|
206
|
+
}
|
|
207
|
+
if (!isGitRepo(cwd)) {
|
|
208
|
+
throw new Error(`not a git repository: ${cwd}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const refIn = args.ref ?? "HEAD~1";
|
|
212
|
+
const { args: refArgs, label } = resolveRefArgs(refIn);
|
|
213
|
+
const pathArgs = args.path ? ["--", args.path] : [];
|
|
214
|
+
|
|
215
|
+
const numstat = runGit(cwd, ["diff", "--numstat", ...refArgs, ...pathArgs]);
|
|
216
|
+
if (!numstat.ok) {
|
|
217
|
+
const msg = numstat.stderr.trim() || "git diff failed";
|
|
218
|
+
throw new Error(`git diff failed: ${msg}`);
|
|
219
|
+
}
|
|
220
|
+
const files = parseNumstat(numstat.stdout);
|
|
221
|
+
const totalChangedLines = files.reduce((s, f) => s + f.added + f.deleted, 0);
|
|
222
|
+
|
|
223
|
+
const raw = runGit(cwd, ["diff", ...refArgs, ...pathArgs]);
|
|
224
|
+
const rawDiff = raw.ok ? raw.stdout : "";
|
|
225
|
+
const rawBytes = rawDiff.length;
|
|
226
|
+
|
|
227
|
+
let mode: "stat" | "summary" | "full";
|
|
228
|
+
if (args.mode === "stat" || args.mode === "summary" || args.mode === "full") {
|
|
229
|
+
mode = args.mode;
|
|
230
|
+
} else if (totalChangedLines > 500) {
|
|
231
|
+
mode = "stat";
|
|
232
|
+
} else if (totalChangedLines >= 100) {
|
|
233
|
+
mode = "summary";
|
|
234
|
+
} else {
|
|
235
|
+
mode = "full";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
let body: string;
|
|
239
|
+
if (mode === "stat") {
|
|
240
|
+
body = renderStat(files, label);
|
|
241
|
+
} else if (mode === "summary") {
|
|
242
|
+
body = renderSummary(files, label, rawDiff);
|
|
243
|
+
} else {
|
|
244
|
+
if (!rawDiff) {
|
|
245
|
+
body = renderStat(files, label);
|
|
246
|
+
} else {
|
|
247
|
+
const header = renderStat(files, label);
|
|
248
|
+
body = `${header}\n\n${snipCompact(rawDiff)}`;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const rawDiffBytes = Buffer.byteLength(rawDiff, "utf-8");
|
|
253
|
+
if ((mode === "full" || mode === "summary") && rawDiffBytes > 16_384 && !args.bypassSummary) {
|
|
254
|
+
const s = await summarizeIfLarge(body, {
|
|
255
|
+
toolName: "ashlr__diff",
|
|
256
|
+
systemPrompt: PROMPTS.diff,
|
|
257
|
+
bypass: false,
|
|
258
|
+
thresholdBytes: 1,
|
|
259
|
+
});
|
|
260
|
+
body = s.text;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const footer = `\n\n[ashlr__diff \u00b7 mode=${mode}]`;
|
|
264
|
+
const text = body + footer;
|
|
265
|
+
|
|
266
|
+
const diffRawBytes = Math.max(rawBytes, body.length);
|
|
267
|
+
await recordSavingCore(diffRawBytes, text.length, "ashlr__diff");
|
|
268
|
+
|
|
269
|
+
const diffBadgeOpts = {
|
|
270
|
+
toolName: "ashlr__diff",
|
|
271
|
+
rawBytes: diffRawBytes,
|
|
272
|
+
outputBytes: text.length,
|
|
273
|
+
};
|
|
274
|
+
if (confidenceTier(diffBadgeOpts) === "low") {
|
|
275
|
+
await logEvent("tool_low_confidence_shipped", { tool: "ashlr__diff", reason: "low-confidence" });
|
|
276
|
+
}
|
|
277
|
+
return text + confidenceBadge(diffBadgeOpts);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
// Registration (side-effect on import)
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
registerTool({
|
|
285
|
+
name: "ashlr__diff",
|
|
286
|
+
description:
|
|
287
|
+
"Fast adaptive git diff. Use instead of native Bash 'git diff | cat' when reviewing " +
|
|
288
|
+
"changes — avoids dumping thousands of lines into context. Compresses by auto-selecting " +
|
|
289
|
+
"stat-only (>500 changed lines), stat+hottest-hunks (100–500), or full patch (<100); " +
|
|
290
|
+
"typical savings 60–95% on large diffs. Accepts any git ref plus pseudo-refs 'staged', " +
|
|
291
|
+
"'unstaged', 'working'. For refactor/rename reviews use ashlr__diff_semantic instead.",
|
|
292
|
+
inputSchema: {
|
|
293
|
+
type: "object",
|
|
294
|
+
properties: {
|
|
295
|
+
ref: {
|
|
296
|
+
type: "string",
|
|
297
|
+
description:
|
|
298
|
+
"Git ref to diff against (default: HEAD~1). Also accepts 'staged' / 'unstaged' / 'working'.",
|
|
299
|
+
},
|
|
300
|
+
path: { type: "string", description: "Limit to path(s) \u2014 file or dir" },
|
|
301
|
+
cwd: { type: "string" },
|
|
302
|
+
mode: {
|
|
303
|
+
type: "string",
|
|
304
|
+
description:
|
|
305
|
+
"'stat' (files + lines only) \u00b7 'summary' (stat + hottest hunks) \u00b7 'full' (default: adaptive \u2014 stat if huge, summary if moderate, full if small)",
|
|
306
|
+
},
|
|
307
|
+
bypassSummary: {
|
|
308
|
+
type: "boolean",
|
|
309
|
+
description: "If true, skip LLM summarization and return the full compacted diff.",
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
handler: async (args: Record<string, unknown>, _ctx: ToolCallContext): Promise<ToolResult> => {
|
|
314
|
+
const text = await ashlrDiff(args as unknown as DiffArgs);
|
|
315
|
+
return { content: [{ type: "text", text }] };
|
|
316
|
+
},
|
|
317
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* ashlr-diff MCP server — standalone entry point.
|
|
4
|
+
*
|
|
5
|
+
* Imports diff-server-handlers (registers ashlr__diff into the shared
|
|
6
|
+
* registry) then runs as a standalone stdio MCP server. Existing plugin.json
|
|
7
|
+
* entries continue to work unchanged through the v1.12 migration window.
|
|
8
|
+
*
|
|
9
|
+
* For in-process router use, import diff-server-handlers directly.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import "./diff-server-handlers";
|
|
13
|
+
import { runStandalone } from "./_tool-base";
|
|
14
|
+
|
|
15
|
+
// Re-export core logic so existing tests that import from this module keep working.
|
|
16
|
+
export { ashlrDiff } from "./diff-server-handlers";
|
|
17
|
+
|
|
18
|
+
if (import.meta.main) {
|
|
19
|
+
await runStandalone("ashlr-diff", "0.1.0");
|
|
20
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* edit-server-handlers — registers ashlr__edit into the shared tool registry.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { registerTool, type ToolCallContext, type ToolResult } from "./_tool-base";
|
|
6
|
+
import { ashlrEdit, type EditArgs } from "./edit-server";
|
|
7
|
+
|
|
8
|
+
registerTool({
|
|
9
|
+
name: "ashlr__edit",
|
|
10
|
+
description:
|
|
11
|
+
"Single literal search/replace in one file, returns only a diff summary. " +
|
|
12
|
+
"Use instead of native Edit when the file is >5KB or the combined before/after " +
|
|
13
|
+
"is >=80 chars — avoids the full file round-trip. Compresses by returning only " +
|
|
14
|
+
"the changed lines; typical savings 80%. In strict mode (default) requires " +
|
|
15
|
+
"exactly one match. For many edits across files use ashlr__multi_edit instead. " +
|
|
16
|
+
"For symbol renames use ashlr__edit_structural instead.",
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
path: { type: "string", description: "Absolute or cwd-relative file path" },
|
|
21
|
+
search: { type: "string", description: "Exact text to find" },
|
|
22
|
+
replace: { type: "string", description: "Replacement text" },
|
|
23
|
+
strict: { type: "boolean", description: "Require exactly one match (default: true)" },
|
|
24
|
+
},
|
|
25
|
+
required: ["path", "search", "replace"],
|
|
26
|
+
},
|
|
27
|
+
handler: async (args: Record<string, unknown>, _ctx: ToolCallContext): Promise<ToolResult> => {
|
|
28
|
+
const res = await ashlrEdit(args as unknown as EditArgs);
|
|
29
|
+
return { content: [{ type: "text", text: res.text }] };
|
|
30
|
+
},
|
|
31
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* edit-server — ashlr__edit tool implementation.
|
|
3
|
+
*
|
|
4
|
+
* Owns the search/replace + diff-summary pipeline. Three post-edit
|
|
5
|
+
* side-effects (in order):
|
|
6
|
+
* 1. readCache invalidate — so subsequent ashlr__read sees fresh content
|
|
7
|
+
* 2. refreshGenomeAfterEdit (fire-and-forget) — keeps genome in sync
|
|
8
|
+
* 3. editLog append — feeds ashlr__flush summary
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFile, writeFile } from "fs/promises";
|
|
12
|
+
import { estimateTokensFromString } from "@ashlr/core-efficiency";
|
|
13
|
+
import { refreshGenomeAfterEdit } from "./_genome-live";
|
|
14
|
+
import { recordSaving } from "./_stats";
|
|
15
|
+
import { clampToCwd } from "./_cwd-clamp";
|
|
16
|
+
import { invalidateCached } from "./_read-cache";
|
|
17
|
+
import { appendEdit } from "./_edit-log";
|
|
18
|
+
import { levenshtein as _levenshtein, fuzzyTopLines, findFuzzyMatch } from "./_edit-match";
|
|
19
|
+
import { validateEdit } from "./_edit-validate";
|
|
20
|
+
|
|
21
|
+
// Re-export levenshtein so tests / other modules that may import it from here
|
|
22
|
+
// continue to work (backwards-compat shim).
|
|
23
|
+
export { _levenshtein as levenshtein };
|
|
24
|
+
|
|
25
|
+
export interface EditArgs {
|
|
26
|
+
path: string;
|
|
27
|
+
search: string;
|
|
28
|
+
replace: string;
|
|
29
|
+
/** When true (default), require exactly one match of `search` for safety. */
|
|
30
|
+
strict?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface EditResult {
|
|
34
|
+
text: string;
|
|
35
|
+
hunksApplied: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const FUZZY_SIZE_LIMIT = 2 * 1024 * 1024; // 2 MB
|
|
39
|
+
|
|
40
|
+
function summarizeEdit(
|
|
41
|
+
relPath: string,
|
|
42
|
+
search: string,
|
|
43
|
+
replace: string,
|
|
44
|
+
matchCount: number,
|
|
45
|
+
strict: boolean,
|
|
46
|
+
): string {
|
|
47
|
+
const first = (s: string) => s.split("\n")[0]?.slice(0, 72) ?? "";
|
|
48
|
+
return [
|
|
49
|
+
`[ashlr__edit] ${relPath} · ${strict ? "1 of " + matchCount : matchCount + " of " + matchCount} hunks applied`,
|
|
50
|
+
` - removed (${estimateTokensFromString(search)} tok): ${first(search)}${search.length > 72 ? "…" : ""}`,
|
|
51
|
+
` + added (${estimateTokensFromString(replace)} tok): ${first(replace)}${replace.length > 72 ? "…" : ""}`,
|
|
52
|
+
].join("\n");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function ashlrEdit(input: EditArgs): Promise<EditResult> {
|
|
56
|
+
const { path: relPath, search, replace, strict = true } = input;
|
|
57
|
+
if (!search) throw new Error("ashlr__edit: 'search' must not be empty");
|
|
58
|
+
|
|
59
|
+
const clamp = clampToCwd(relPath, "ashlr__edit");
|
|
60
|
+
if (!clamp.ok) throw new Error(clamp.message);
|
|
61
|
+
const abs = clamp.abs;
|
|
62
|
+
const original = await readFile(abs, "utf-8");
|
|
63
|
+
|
|
64
|
+
let count = 0;
|
|
65
|
+
let idx = 0;
|
|
66
|
+
while ((idx = original.indexOf(search, idx)) !== -1) { count++; idx += search.length; }
|
|
67
|
+
|
|
68
|
+
// -------------------------------------------------------------------------
|
|
69
|
+
// Zero-match: try fuzzy fallback (Feature 2) before throwing.
|
|
70
|
+
// -------------------------------------------------------------------------
|
|
71
|
+
if (count === 0) {
|
|
72
|
+
// Fuzzy is only attempted in strict mode, for files <= 2 MB, when not disabled.
|
|
73
|
+
const fuzzyEnabled =
|
|
74
|
+
strict &&
|
|
75
|
+
original.length <= FUZZY_SIZE_LIMIT &&
|
|
76
|
+
process.env.ASHLR_EDIT_FUZZY !== "off";
|
|
77
|
+
|
|
78
|
+
if (fuzzyEnabled) {
|
|
79
|
+
const fuzzyMatch = findFuzzyMatch(original, search);
|
|
80
|
+
if (fuzzyMatch !== null) {
|
|
81
|
+
// Confident, unique match found — apply it.
|
|
82
|
+
const updated =
|
|
83
|
+
original.slice(0, fuzzyMatch.start) +
|
|
84
|
+
replace +
|
|
85
|
+
original.slice(fuzzyMatch.end);
|
|
86
|
+
|
|
87
|
+
// Validate before write (Feature 3).
|
|
88
|
+
const validation = await validateEdit(abs, original, updated).catch(() => ({
|
|
89
|
+
introducedError: false,
|
|
90
|
+
detail: "",
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
validation.introducedError &&
|
|
95
|
+
process.env.ASHLR_EDIT_VALIDATE === "block"
|
|
96
|
+
) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`ashlr__edit: refused — edit introduces a syntax error (${validation.detail}); ` +
|
|
99
|
+
`set ASHLR_EDIT_VALIDATE=warn to override`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
await writeFile(abs, updated, "utf-8");
|
|
104
|
+
invalidateCached(abs);
|
|
105
|
+
refreshGenomeAfterEdit(abs, original, updated).catch(() => {});
|
|
106
|
+
|
|
107
|
+
const baseBytes = search.length + replace.length;
|
|
108
|
+
const compactSummary = summarizeEdit(relPath, search, replace, 1, true);
|
|
109
|
+
await recordSaving(baseBytes, compactSummary.length, "ashlr__edit");
|
|
110
|
+
|
|
111
|
+
appendEdit({ relPath, hunksApplied: 1 });
|
|
112
|
+
|
|
113
|
+
let resultText =
|
|
114
|
+
compactSummary +
|
|
115
|
+
`\n[ashlr__edit: applied via fuzzy match (sim=${fuzzyMatch.score.toFixed(2)}) — verify the diff]`;
|
|
116
|
+
|
|
117
|
+
if (validation.introducedError) {
|
|
118
|
+
resultText +=
|
|
119
|
+
`\n[ashlr__edit: ⚠ syntax — this edit introduces a parse error (${validation.detail}); review or fix in your next edit]`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { text: resultText, hunksApplied: 1 };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// No fuzzy match (or fuzzy disabled) — throw the existing enriched error.
|
|
127
|
+
if (strict && original.length <= FUZZY_SIZE_LIMIT) {
|
|
128
|
+
const candidates = fuzzyTopLines(search, original);
|
|
129
|
+
const hint = candidates.length
|
|
130
|
+
? "\nClosest lines in file:\n" +
|
|
131
|
+
candidates.map(c => ` ${c.lineNo}: ${c.text.slice(0, 120)} (sim=${c.sim.toFixed(2)})`).join("\n")
|
|
132
|
+
: "";
|
|
133
|
+
throw new Error(`ashlr__edit: no match for search string in ${relPath} (strict mode).${hint}`);
|
|
134
|
+
}
|
|
135
|
+
throw new Error(`ashlr__edit: search string not found in ${relPath}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (strict && count > 1) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`ashlr__edit: search string matched ${count} times in ${relPath}; pass strict:false to replace all, or widen the context to a unique span.`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// -------------------------------------------------------------------------
|
|
145
|
+
// Normal exact-match path (unchanged semantics).
|
|
146
|
+
// -------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
// Treat replacement as literal text. String.prototype.replace(string, string)
|
|
149
|
+
// interprets `$&`, `$1`, `$'`, and `$` sequences in the replacement.
|
|
150
|
+
const matchIdx = original.indexOf(search);
|
|
151
|
+
const updated = strict
|
|
152
|
+
? original.slice(0, matchIdx) + replace + original.slice(matchIdx + search.length)
|
|
153
|
+
: original.split(search).join(replace);
|
|
154
|
+
|
|
155
|
+
// Feature 3: validate before writing.
|
|
156
|
+
const validation = await validateEdit(abs, original, updated).catch(() => ({
|
|
157
|
+
introducedError: false,
|
|
158
|
+
detail: "",
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
if (
|
|
162
|
+
validation.introducedError &&
|
|
163
|
+
process.env.ASHLR_EDIT_VALIDATE === "block"
|
|
164
|
+
) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`ashlr__edit: refused — edit introduces a syntax error (${validation.detail}); ` +
|
|
167
|
+
`set ASHLR_EDIT_VALIDATE=warn to override`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await writeFile(abs, updated, "utf-8");
|
|
172
|
+
|
|
173
|
+
// Post-edit side-effects (order preserved from efficiency-server.ts:847-902):
|
|
174
|
+
|
|
175
|
+
// 1. Invalidate read-cache so subsequent ashlr__read calls see new content.
|
|
176
|
+
invalidateCached(abs);
|
|
177
|
+
|
|
178
|
+
// 2. best-effort: refreshGenomeAfterEdit already swallows internally; this
|
|
179
|
+
// outer catch guards against a pre-try sync throw.
|
|
180
|
+
refreshGenomeAfterEdit(abs, original, updated).catch(() => {});
|
|
181
|
+
|
|
182
|
+
// v1.18 Trust Pass: baseline is what Claude Code would have SENT for a
|
|
183
|
+
// native Edit — search + replace, NOT the full file twice.
|
|
184
|
+
// v1.22 refinement: multi-hunk strict=false edits replace ALL N matches in
|
|
185
|
+
// a single ashlr call. Native equivalent is one Edit with replace_all=true
|
|
186
|
+
// (still 1 call) but Claude must reason about all N occurrences when crafting
|
|
187
|
+
// the call — add a small file-context premium (+500 bytes) so multi-hunk
|
|
188
|
+
// savings reflect the cognitive overhead of the original LLM call without
|
|
189
|
+
// multiplying by `count` (which would inflate 5-10×).
|
|
190
|
+
const baseBytes = search.length + replace.length;
|
|
191
|
+
const naiveBytes = !strict && count > 1 ? baseBytes + 500 : baseBytes;
|
|
192
|
+
const compactSummary = summarizeEdit(relPath, search, replace, count, strict);
|
|
193
|
+
await recordSaving(naiveBytes, compactSummary.length, "ashlr__edit");
|
|
194
|
+
|
|
195
|
+
const hunksApplied = strict ? 1 : count;
|
|
196
|
+
|
|
197
|
+
// 3. Append to edit log (feeds ashlr__flush summary).
|
|
198
|
+
appendEdit({ relPath, hunksApplied });
|
|
199
|
+
|
|
200
|
+
let resultText = compactSummary;
|
|
201
|
+
|
|
202
|
+
// Feature 3: append warning if edit introduced a syntax error (warn mode).
|
|
203
|
+
if (validation.introducedError) {
|
|
204
|
+
resultText +=
|
|
205
|
+
`\n[ashlr__edit: ⚠ syntax — this edit introduces a parse error (${validation.detail}); review or fix in your next edit]`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return { text: resultText, hunksApplied };
|
|
209
|
+
}
|