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,566 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* ashlr-diff-semantic MCP server.
|
|
4
|
+
*
|
|
5
|
+
* Exposes ashlr__diff_semantic — AST-aware (heuristic) diff summarization.
|
|
6
|
+
* A 200-line rename-refactor across 20 files renders as one line.
|
|
7
|
+
*
|
|
8
|
+
* Semantic analysis passes (in order, applied to each file's hunks):
|
|
9
|
+
* 1. Rename detection — if symbol X appears in deletions and Y appears at
|
|
10
|
+
* the same position in additions across >= 3 files, emit
|
|
11
|
+
* "renamed X -> Y (N occurrences across K files)"
|
|
12
|
+
* 2. Signature change — function/method declaration line changed but body
|
|
13
|
+
* lines are identical
|
|
14
|
+
* 3. Formatting-only — diff reduces to whitespace changes only
|
|
15
|
+
* 4. Other — falls through to a compact summary (same output
|
|
16
|
+
* as ashlr__diff summary mode)
|
|
17
|
+
*
|
|
18
|
+
* Savings are persisted to the shared ~/.ashlr/stats.json.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
22
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
23
|
+
import {
|
|
24
|
+
CallToolRequestSchema,
|
|
25
|
+
ListToolsRequestSchema,
|
|
26
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
27
|
+
import { existsSync } from "fs";
|
|
28
|
+
import { runWithTimeout } from "./_run-with-timeout";
|
|
29
|
+
import { recordSaving as recordSavingCore } from "./_stats";
|
|
30
|
+
import { summarizeIfLarge, PROMPTS } from "./_summarize";
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Savings wrapper
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
async function recordSaving(rawBytes: number, compactBytes: number): Promise<number> {
|
|
37
|
+
return recordSavingCore(rawBytes, compactBytes, "ashlr__diff_semantic");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Git helpers (mirrors diff-server.ts — no shared dependency to keep servers
|
|
42
|
+
// standalone)
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
async function runGit(
|
|
46
|
+
cwd: string,
|
|
47
|
+
args: string[],
|
|
48
|
+
): Promise<{ ok: boolean; stdout: string; stderr: string }> {
|
|
49
|
+
const res = await runWithTimeout({
|
|
50
|
+
command: "git",
|
|
51
|
+
args: ["-C", cwd, ...args],
|
|
52
|
+
timeoutMs: 15_000,
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
ok: res.exitCode === 0,
|
|
56
|
+
stdout: res.stdout || "",
|
|
57
|
+
stderr: res.stderr || "",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function isGitRepo(cwd: string): Promise<boolean> {
|
|
62
|
+
const r = await runGit(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
63
|
+
return r.ok && r.stdout.trim() === "true";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Input
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
export interface SemanticDiffArgs {
|
|
71
|
+
/** Skip LLM summarization of long semantic-diff output. */
|
|
72
|
+
bypassSummary?: boolean;
|
|
73
|
+
cwd?: string;
|
|
74
|
+
range?: string;
|
|
75
|
+
staged?: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function buildGitDiffArgs(args: SemanticDiffArgs): string[] {
|
|
79
|
+
if (args.staged) return ["--cached"];
|
|
80
|
+
if (args.range) return [args.range];
|
|
81
|
+
// Default: unstaged working tree
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Diff splitting
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
interface FileDiff {
|
|
90
|
+
path: string;
|
|
91
|
+
raw: string;
|
|
92
|
+
hunks: Hunk[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface Hunk {
|
|
96
|
+
header: string;
|
|
97
|
+
lines: string[]; // raw diff lines (+ / - / context), excluding hunk header
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function splitDiffIntoFiles(raw: string): FileDiff[] {
|
|
101
|
+
const files: FileDiff[] = [];
|
|
102
|
+
if (!raw.trim()) return files;
|
|
103
|
+
|
|
104
|
+
const parts = raw.split(/^diff --git /m);
|
|
105
|
+
for (const part of parts) {
|
|
106
|
+
if (!part.trim()) continue;
|
|
107
|
+
const firstLine = part.split("\n", 1)[0] ?? "";
|
|
108
|
+
const m = firstLine.match(/ b\/(.+)$/);
|
|
109
|
+
const path = m ? m[1]! : firstLine.trim();
|
|
110
|
+
const block = "diff --git " + part;
|
|
111
|
+
files.push({ path, raw: block, hunks: parseHunks(block) });
|
|
112
|
+
}
|
|
113
|
+
return files;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function parseHunks(fileBlock: string): Hunk[] {
|
|
117
|
+
const hunks: Hunk[] = [];
|
|
118
|
+
const lines = fileBlock.split("\n");
|
|
119
|
+
let cur: Hunk | null = null;
|
|
120
|
+
for (const ln of lines) {
|
|
121
|
+
if (ln.startsWith("@@")) {
|
|
122
|
+
if (cur) hunks.push(cur);
|
|
123
|
+
cur = { header: ln, lines: [] };
|
|
124
|
+
} else if (cur) {
|
|
125
|
+
cur.lines.push(ln);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (cur) hunks.push(cur);
|
|
129
|
+
return hunks;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Heuristic analysis
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
// -- 1. Formatting-only detection -------------------------------------------
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Returns true when every changed line (+ or -) is identical after stripping
|
|
140
|
+
* all whitespace — i.e. the diff is purely a reformatting.
|
|
141
|
+
*/
|
|
142
|
+
function isFormattingOnly(hunks: Hunk[]): boolean {
|
|
143
|
+
if (hunks.length === 0) return false;
|
|
144
|
+
const added: string[] = [];
|
|
145
|
+
const deleted: string[] = [];
|
|
146
|
+
for (const h of hunks) {
|
|
147
|
+
for (const ln of h.lines) {
|
|
148
|
+
if (ln.startsWith("+") && !ln.startsWith("+++")) {
|
|
149
|
+
added.push(ln.slice(1).replace(/\s/g, ""));
|
|
150
|
+
} else if (ln.startsWith("-") && !ln.startsWith("---")) {
|
|
151
|
+
deleted.push(ln.slice(1).replace(/\s/g, ""));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (added.length === 0 && deleted.length === 0) return false;
|
|
156
|
+
if (added.length !== deleted.length) return false;
|
|
157
|
+
for (let i = 0; i < added.length; i++) {
|
|
158
|
+
if (added[i] !== deleted[i]) return false;
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// -- 2. Signature-change detection ------------------------------------------
|
|
164
|
+
|
|
165
|
+
// Patterns that identify function/method/class declaration lines.
|
|
166
|
+
const DECL_RE =
|
|
167
|
+
/^\s*(export\s+)?(default\s+)?(async\s+)?(function\s+\w|class\s+\w|\w+\s*[=:]\s*(async\s+)?\(|def\s+\w|fn\s+\w|func\s+\w|public\s+|private\s+|protected\s+|static\s+)/;
|
|
168
|
+
|
|
169
|
+
interface SignatureChange {
|
|
170
|
+
file: string;
|
|
171
|
+
from: string;
|
|
172
|
+
to: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function detectSignatureChanges(files: FileDiff[]): SignatureChange[] {
|
|
176
|
+
const changes: SignatureChange[] = [];
|
|
177
|
+
for (const file of files) {
|
|
178
|
+
for (const hunk of file.hunks) {
|
|
179
|
+
const dels = hunk.lines.filter(
|
|
180
|
+
(l) => l.startsWith("-") && !l.startsWith("---"),
|
|
181
|
+
);
|
|
182
|
+
const adds = hunk.lines.filter(
|
|
183
|
+
(l) => l.startsWith("+") && !l.startsWith("+++"),
|
|
184
|
+
);
|
|
185
|
+
if (dels.length !== adds.length) continue;
|
|
186
|
+
// All deleted lines must be declaration lines; body lines must match
|
|
187
|
+
for (let i = 0; i < dels.length; i++) {
|
|
188
|
+
const delLine = dels[i]!.slice(1);
|
|
189
|
+
const addLine = adds[i]!.slice(1);
|
|
190
|
+
if (!DECL_RE.test(delLine) && !DECL_RE.test(addLine)) continue;
|
|
191
|
+
if (delLine.replace(/\s/g, "") === addLine.replace(/\s/g, "")) continue;
|
|
192
|
+
// Check context (non-+/- lines) are purely body — no extra changes
|
|
193
|
+
const contextLines = hunk.lines.filter(
|
|
194
|
+
(l) => !l.startsWith("+") && !l.startsWith("-"),
|
|
195
|
+
);
|
|
196
|
+
const changedLines = hunk.lines.filter(
|
|
197
|
+
(l) =>
|
|
198
|
+
(l.startsWith("+") && !l.startsWith("+++")) ||
|
|
199
|
+
(l.startsWith("-") && !l.startsWith("---")),
|
|
200
|
+
);
|
|
201
|
+
// Signature-only: only one pair of changed lines per hunk, rest is context
|
|
202
|
+
if (changedLines.length === 2 && contextLines.length > 0) {
|
|
203
|
+
changes.push({
|
|
204
|
+
file: file.path,
|
|
205
|
+
from: delLine.trim(),
|
|
206
|
+
to: addLine.trim(),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return changes;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// -- 3. Rename detection -----------------------------------------------------
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Rename detection heuristic:
|
|
219
|
+
*
|
|
220
|
+
* For each hunk, extract deleted tokens and added tokens (identifiers only —
|
|
221
|
+
* [A-Za-z_][A-Za-z0-9_]*). Build a positional diff: for each position i,
|
|
222
|
+
* if deleted[i] != added[i], record the pair (deleted[i], added[i]).
|
|
223
|
+
*
|
|
224
|
+
* Then, across ALL files, if the same (from, to) pair appears in >= 3 files,
|
|
225
|
+
* it's considered a rename. We pick the most-frequent pair and emit it.
|
|
226
|
+
*
|
|
227
|
+
* False-positive scenarios addressed:
|
|
228
|
+
* - Common English words / short tokens (< 3 chars) are skipped.
|
|
229
|
+
* - Pairs where `from === to` are ignored (no-op).
|
|
230
|
+
* - We require >= 3 *files* (not just occurrences) to call it a rename.
|
|
231
|
+
* - A single giant churn that replaces many unrelated tokens won't fire
|
|
232
|
+
* because positional matching limits scope to tokens at the same index.
|
|
233
|
+
*/
|
|
234
|
+
|
|
235
|
+
interface RenameCandidate {
|
|
236
|
+
from: string;
|
|
237
|
+
to: string;
|
|
238
|
+
files: Set<string>;
|
|
239
|
+
occurrences: number;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const IDENT_RE = /[A-Za-z_][A-Za-z0-9_]*/g;
|
|
243
|
+
const MIN_TOKEN_LEN = 3;
|
|
244
|
+
const MIN_FILES_FOR_RENAME = 3;
|
|
245
|
+
|
|
246
|
+
function extractTokens(line: string): string[] {
|
|
247
|
+
return Array.from(line.matchAll(IDENT_RE), (m) => m[0]).filter(
|
|
248
|
+
(t) => t.length >= MIN_TOKEN_LEN,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function detectRenames(files: FileDiff[]): RenameCandidate[] {
|
|
253
|
+
// Map: `${from}\0${to}` -> { from, to, files, occurrences }
|
|
254
|
+
const pairMap = new Map<string, RenameCandidate>();
|
|
255
|
+
|
|
256
|
+
for (const file of files) {
|
|
257
|
+
// Track pairs seen in this file to avoid double-counting per-file
|
|
258
|
+
const filePairs = new Set<string>();
|
|
259
|
+
|
|
260
|
+
for (const hunk of file.hunks) {
|
|
261
|
+
const delLines = hunk.lines.filter(
|
|
262
|
+
(l) => l.startsWith("-") && !l.startsWith("---"),
|
|
263
|
+
);
|
|
264
|
+
const addLines = hunk.lines.filter(
|
|
265
|
+
(l) => l.startsWith("+") && !l.startsWith("+++"),
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// Only process hunks where line counts match (substitutions, not pure insertions/deletions)
|
|
269
|
+
if (delLines.length === 0 || addLines.length === 0) continue;
|
|
270
|
+
const pairCount = Math.min(delLines.length, addLines.length);
|
|
271
|
+
|
|
272
|
+
for (let i = 0; i < pairCount; i++) {
|
|
273
|
+
const delTokens = extractTokens(delLines[i]!.slice(1));
|
|
274
|
+
const addTokens = extractTokens(addLines[i]!.slice(1));
|
|
275
|
+
|
|
276
|
+
// Positional matching: same index, different token
|
|
277
|
+
const limit = Math.min(delTokens.length, addTokens.length);
|
|
278
|
+
for (let j = 0; j < limit; j++) {
|
|
279
|
+
const from = delTokens[j]!;
|
|
280
|
+
const to = addTokens[j]!;
|
|
281
|
+
if (from === to) continue;
|
|
282
|
+
const key = `${from}\0${to}`;
|
|
283
|
+
filePairs.add(key);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Now credit each unique pair to this file
|
|
289
|
+
for (const key of filePairs) {
|
|
290
|
+
const [from, to] = key.split("\0") as [string, string];
|
|
291
|
+
let entry = pairMap.get(key);
|
|
292
|
+
if (!entry) {
|
|
293
|
+
entry = { from, to, files: new Set(), occurrences: 0 };
|
|
294
|
+
pairMap.set(key, entry);
|
|
295
|
+
}
|
|
296
|
+
entry.files.add(file.path);
|
|
297
|
+
entry.occurrences++;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Filter: must appear in >= MIN_FILES_FOR_RENAME distinct files
|
|
302
|
+
return Array.from(pairMap.values())
|
|
303
|
+
.filter((c) => c.files.size >= MIN_FILES_FOR_RENAME)
|
|
304
|
+
.sort((a, b) => b.files.size - a.files.size || b.occurrences - a.occurrences);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ---------------------------------------------------------------------------
|
|
308
|
+
// Numstat helpers (for footer)
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
interface FileStat {
|
|
312
|
+
added: number;
|
|
313
|
+
deleted: number;
|
|
314
|
+
path: string;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function parseNumstat(raw: string): FileStat[] {
|
|
318
|
+
const out: FileStat[] = [];
|
|
319
|
+
for (const line of raw.split("\n")) {
|
|
320
|
+
if (!line) continue;
|
|
321
|
+
const parts = line.split("\t");
|
|
322
|
+
if (parts.length < 3) continue;
|
|
323
|
+
const [a, d, ...rest] = parts;
|
|
324
|
+
out.push({
|
|
325
|
+
added: a === "-" ? 0 : Number(a) || 0,
|
|
326
|
+
deleted: d === "-" ? 0 : Number(d) || 0,
|
|
327
|
+
path: rest.join("\t"),
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return out;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function fmtKB(bytes: number): string {
|
|
334
|
+
return (bytes / 1024).toFixed(1) + " KB";
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
// Compact fallback summary (mirrors diff-server.ts summary mode logic)
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
|
|
341
|
+
function compactSummary(files: FileStat[], rawDiff: string): string {
|
|
342
|
+
if (files.length === 0) return "(no changes)";
|
|
343
|
+
const totalAdd = files.reduce((s, f) => s + f.added, 0);
|
|
344
|
+
const totalDel = files.reduce((s, f) => s + f.deleted, 0);
|
|
345
|
+
const lines: string[] = [
|
|
346
|
+
`${files.length} file${files.length === 1 ? "" : "s"} changed · +${totalAdd} -${totalDel} lines`,
|
|
347
|
+
];
|
|
348
|
+
// Show up to 5 files with their line counts
|
|
349
|
+
for (const f of files.slice(0, 5)) {
|
|
350
|
+
lines.push(` ${f.path} +${f.added} -${f.deleted}`);
|
|
351
|
+
}
|
|
352
|
+
if (files.length > 5) lines.push(` ... and ${files.length - 5} more file(s)`);
|
|
353
|
+
void rawDiff;
|
|
354
|
+
return lines.join("\n");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
// Main semantic analysis
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
|
|
361
|
+
export async function ashlrDiffSemantic(args: SemanticDiffArgs): Promise<string> {
|
|
362
|
+
const cwd = args.cwd ?? process.cwd();
|
|
363
|
+
if (!existsSync(cwd)) throw new Error(`cwd does not exist: ${cwd}`);
|
|
364
|
+
if (!await isGitRepo(cwd)) throw new Error(`not a git repository: ${cwd}`);
|
|
365
|
+
|
|
366
|
+
const diffArgs = buildGitDiffArgs(args);
|
|
367
|
+
|
|
368
|
+
// Raw diff — what Claude Code would have read without us
|
|
369
|
+
const rawResult = await runGit(cwd, ["diff", ...diffArgs]);
|
|
370
|
+
if (!rawResult.ok) {
|
|
371
|
+
throw new Error(`git diff failed: ${rawResult.stderr.trim() || "unknown error"}`);
|
|
372
|
+
}
|
|
373
|
+
const rawDiff = rawResult.stdout;
|
|
374
|
+
const rawBytes = Buffer.byteLength(rawDiff, "utf-8");
|
|
375
|
+
|
|
376
|
+
// Numstat for summary fallback and footer
|
|
377
|
+
const numstatResult = await runGit(cwd, ["diff", "--numstat", ...diffArgs]);
|
|
378
|
+
const fileStats = numstatResult.ok ? parseNumstat(numstatResult.stdout) : [];
|
|
379
|
+
|
|
380
|
+
// Parse diff into per-file structures
|
|
381
|
+
const fileDiffs = splitDiffIntoFiles(rawDiff);
|
|
382
|
+
|
|
383
|
+
// -- Analysis passes -------------------------------------------------------
|
|
384
|
+
|
|
385
|
+
const formattingFiles: string[] = [];
|
|
386
|
+
const otherFiles: FileStat[] = [];
|
|
387
|
+
|
|
388
|
+
for (const fd of fileDiffs) {
|
|
389
|
+
if (isFormattingOnly(fd.hunks)) {
|
|
390
|
+
formattingFiles.push(fd.path);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Files that are NOT formatting-only go through other passes
|
|
395
|
+
const nonFormattingDiffs = fileDiffs.filter(
|
|
396
|
+
(fd) => !formattingFiles.includes(fd.path),
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
const renames = detectRenames(nonFormattingDiffs);
|
|
400
|
+
const sigChanges = detectSignatureChanges(nonFormattingDiffs);
|
|
401
|
+
|
|
402
|
+
// Build the set of files "explained" by rename/signature analysis
|
|
403
|
+
const explainedPaths = new Set<string>();
|
|
404
|
+
for (const r of renames) {
|
|
405
|
+
for (const p of r.files) explainedPaths.add(p);
|
|
406
|
+
}
|
|
407
|
+
for (const s of sigChanges) {
|
|
408
|
+
explainedPaths.add(s.file);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Remaining files with actual changes not explained above
|
|
412
|
+
for (const fs of fileStats) {
|
|
413
|
+
if (!formattingFiles.includes(fs.path) && !explainedPaths.has(fs.path)) {
|
|
414
|
+
otherFiles.push(fs);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const hasSemanticContent =
|
|
419
|
+
renames.length > 0 ||
|
|
420
|
+
sigChanges.length > 0 ||
|
|
421
|
+
formattingFiles.length > 0;
|
|
422
|
+
|
|
423
|
+
// -- Render output ---------------------------------------------------------
|
|
424
|
+
|
|
425
|
+
const sections: string[] = [];
|
|
426
|
+
|
|
427
|
+
if (renames.length > 0) {
|
|
428
|
+
const lines: string[] = [`renames: ${renames.length} detected`];
|
|
429
|
+
for (const r of renames) {
|
|
430
|
+
lines.push(
|
|
431
|
+
` ${r.from} -> ${r.to} (${r.occurrences} occurrence${r.occurrences === 1 ? "" : "s"} across ${r.files.size} file${r.files.size === 1 ? "" : "s"})`,
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
sections.push(lines.join("\n"));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (sigChanges.length > 0) {
|
|
438
|
+
const lines: string[] = [`signature changes: ${sigChanges.length}`];
|
|
439
|
+
for (const sc of sigChanges) {
|
|
440
|
+
// Truncate long signatures for compactness
|
|
441
|
+
const fromShort =
|
|
442
|
+
sc.from.length > 80 ? sc.from.slice(0, 77) + "..." : sc.from;
|
|
443
|
+
const toShort = sc.to.length > 80 ? sc.to.slice(0, 77) + "..." : sc.to;
|
|
444
|
+
lines.push(` ${sc.file}: ${fromShort} -> ${toShort}`);
|
|
445
|
+
}
|
|
446
|
+
sections.push(lines.join("\n"));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (formattingFiles.length > 0) {
|
|
450
|
+
sections.push(
|
|
451
|
+
`formatting-only: ${formattingFiles.length} file${formattingFiles.length === 1 ? "" : "s"}\n` +
|
|
452
|
+
formattingFiles.map((f) => ` ${f}`).join("\n"),
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
let body: string;
|
|
457
|
+
|
|
458
|
+
if (!hasSemanticContent || otherFiles.length > 0) {
|
|
459
|
+
const otherSummary =
|
|
460
|
+
otherFiles.length > 0 ? compactSummary(otherFiles, rawDiff) : null;
|
|
461
|
+
|
|
462
|
+
if (!hasSemanticContent) {
|
|
463
|
+
// Full degradation: behave exactly like ashlr__diff compact output
|
|
464
|
+
body = compactSummary(fileStats, rawDiff);
|
|
465
|
+
} else {
|
|
466
|
+
// Mixed: semantic sections + compact summary for remainder
|
|
467
|
+
if (otherSummary) sections.push(`other changes:\n${otherSummary}`);
|
|
468
|
+
body = sections.join("\n\n");
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
body = sections.join("\n\n");
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// -- Footer ----------------------------------------------------------------
|
|
475
|
+
|
|
476
|
+
const compactBytes = Buffer.byteLength(body, "utf-8");
|
|
477
|
+
const footerRaw = fmtKB(rawBytes);
|
|
478
|
+
const footerCompact = fmtKB(compactBytes);
|
|
479
|
+
const footer = `\n\n[ashlr__diff_semantic] raw: ${footerRaw} · compact: ${footerCompact} · ${renames.length} rename${renames.length === 1 ? "" : "s"} detected`;
|
|
480
|
+
|
|
481
|
+
const fullOutput = body + footer;
|
|
482
|
+
|
|
483
|
+
// Pipe large semantic-diff output through the shared LLM summarizer so a
|
|
484
|
+
// 300-file refactor doesn't hand Claude 40KB of rename rows. Opt out via
|
|
485
|
+
// bypassSummary so tests and power users can still see the raw body.
|
|
486
|
+
const DIFF_SEMANTIC_LLM_THRESHOLD = 8 * 1024;
|
|
487
|
+
const summResult = await summarizeIfLarge(fullOutput, {
|
|
488
|
+
toolName: "ashlr__diff_semantic",
|
|
489
|
+
systemPrompt: PROMPTS.diff_semantic,
|
|
490
|
+
thresholdBytes: DIFF_SEMANTIC_LLM_THRESHOLD,
|
|
491
|
+
bypass: args.bypassSummary === true,
|
|
492
|
+
});
|
|
493
|
+
const outputText = summResult.text;
|
|
494
|
+
|
|
495
|
+
await recordSaving(rawBytes, Buffer.byteLength(outputText, "utf-8"));
|
|
496
|
+
|
|
497
|
+
return outputText;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ---------------------------------------------------------------------------
|
|
501
|
+
// MCP wiring
|
|
502
|
+
// ---------------------------------------------------------------------------
|
|
503
|
+
|
|
504
|
+
const server = new Server(
|
|
505
|
+
{ name: "ashlr-diff-semantic", version: "0.1.0" },
|
|
506
|
+
{ capabilities: { tools: {} } },
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
510
|
+
tools: [
|
|
511
|
+
{
|
|
512
|
+
name: "ashlr__diff_semantic",
|
|
513
|
+
description:
|
|
514
|
+
"Semantic git diff with AST-aware summarization. Use instead of ashlr__diff when " +
|
|
515
|
+
"reviewing refactors, renames, or large reformatting commits — a 200-line rename " +
|
|
516
|
+
"across 20 files renders as one line. Compresses by detecting symbol renames (>=3 " +
|
|
517
|
+
"files), signature-only changes, and formatting-only diffs; typical savings 85–99% " +
|
|
518
|
+
"on refactor commits. Degrades gracefully to compact diff when no semantic patterns " +
|
|
519
|
+
"are found. For ordinary diff viewing use ashlr__diff instead.",
|
|
520
|
+
inputSchema: {
|
|
521
|
+
type: "object",
|
|
522
|
+
properties: {
|
|
523
|
+
cwd: { type: "string", description: "Working directory (default: process.cwd())" },
|
|
524
|
+
range: {
|
|
525
|
+
type: "string",
|
|
526
|
+
description:
|
|
527
|
+
"Git range to diff, e.g. 'HEAD~1..HEAD'. Default: unstaged working tree changes.",
|
|
528
|
+
},
|
|
529
|
+
staged: {
|
|
530
|
+
type: "boolean",
|
|
531
|
+
description: "If true, diff staged changes (--cached). Overrides range.",
|
|
532
|
+
},
|
|
533
|
+
bypassSummary: {
|
|
534
|
+
type: "boolean",
|
|
535
|
+
description: "Skip LLM summarization of long semantic-diff output (default: false).",
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
}));
|
|
542
|
+
|
|
543
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
544
|
+
const { name, arguments: args } = req.params;
|
|
545
|
+
try {
|
|
546
|
+
if (name === "ashlr__diff_semantic") {
|
|
547
|
+
const text = await ashlrDiffSemantic((args ?? {}) as SemanticDiffArgs);
|
|
548
|
+
return { content: [{ type: "text", text }] };
|
|
549
|
+
}
|
|
550
|
+
return {
|
|
551
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
552
|
+
isError: true,
|
|
553
|
+
};
|
|
554
|
+
} catch (err) {
|
|
555
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
556
|
+
return {
|
|
557
|
+
content: [{ type: "text", text: `ashlr__diff_semantic error: ${message}` }],
|
|
558
|
+
isError: true,
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
if (import.meta.main) {
|
|
564
|
+
const transport = new StdioServerTransport();
|
|
565
|
+
await server.connect(transport);
|
|
566
|
+
}
|