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,750 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* scripts/run-benchmark.ts
|
|
4
|
+
*
|
|
5
|
+
* Reproducible benchmark runner for the ashlr-plugin token-efficiency layer.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* bun run scripts/run-benchmark.ts
|
|
9
|
+
* bun run scripts/run-benchmark.ts --repo /path/to/repo
|
|
10
|
+
* bun run scripts/run-benchmark.ts --out docs/benchmarks-v2.json
|
|
11
|
+
* bun run scripts/run-benchmark.ts --dry-run
|
|
12
|
+
*
|
|
13
|
+
* Measures actual byte/token reduction for ashlr__read, ashlr__grep, and
|
|
14
|
+
* ashlr__edit by calling the handler functions directly — no MCP layer.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
18
|
+
import { resolve, relative } from "path";
|
|
19
|
+
import { spawnSync } from "child_process";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
estimateTokensFromString,
|
|
23
|
+
snipCompact,
|
|
24
|
+
type Message,
|
|
25
|
+
} from "@ashlr/core-efficiency";
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// CLI args
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
interface Args {
|
|
32
|
+
repo: string;
|
|
33
|
+
out: string;
|
|
34
|
+
dryRun: boolean;
|
|
35
|
+
validateTokenizer: boolean;
|
|
36
|
+
compare: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseArgs(argv: string[]): Args {
|
|
40
|
+
const args: Args = {
|
|
41
|
+
repo: process.cwd(),
|
|
42
|
+
out: resolve(process.cwd(), "docs/benchmarks-v2.json"),
|
|
43
|
+
dryRun: false,
|
|
44
|
+
validateTokenizer: false,
|
|
45
|
+
compare: false,
|
|
46
|
+
};
|
|
47
|
+
for (let i = 0; i < argv.length; i++) {
|
|
48
|
+
if (argv[i] === "--repo" && argv[i + 1]) args.repo = resolve(argv[++i]!);
|
|
49
|
+
else if (argv[i] === "--out" && argv[i + 1]) args.out = resolve(argv[++i]!);
|
|
50
|
+
else if (argv[i] === "--dry-run") args.dryRun = true;
|
|
51
|
+
else if (argv[i] === "--validate-tokenizer") args.validateTokenizer = true;
|
|
52
|
+
else if (argv[i] === "--compare") args.compare = true;
|
|
53
|
+
}
|
|
54
|
+
return args;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Seeded PRNG (mulberry32) — deterministic from repo commit SHA
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
function mulberry32(seed: number): () => number {
|
|
62
|
+
let s = seed >>> 0;
|
|
63
|
+
return function () {
|
|
64
|
+
s += 0x6d2b79f5;
|
|
65
|
+
let z = s;
|
|
66
|
+
z = Math.imul(z ^ (z >>> 15), z | 1);
|
|
67
|
+
z ^= z + Math.imul(z ^ (z >>> 7), z | 61);
|
|
68
|
+
return ((z ^ (z >>> 14)) >>> 0) / 0x100000000;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function seedFromCommit(sha: string): number {
|
|
73
|
+
// fold 40-char hex SHA into a 32-bit int
|
|
74
|
+
let n = 0;
|
|
75
|
+
for (let i = 0; i < sha.length; i++) {
|
|
76
|
+
n = (Math.imul(n, 31) + sha.charCodeAt(i)) >>> 0;
|
|
77
|
+
}
|
|
78
|
+
return n;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function seededSample<T>(arr: T[], n: number, rand: () => number): T[] {
|
|
82
|
+
const copy = arr.slice();
|
|
83
|
+
const result: T[] = [];
|
|
84
|
+
for (let i = 0; i < n && copy.length > 0; i++) {
|
|
85
|
+
const idx = Math.floor(rand() * copy.length);
|
|
86
|
+
result.push(copy.splice(idx, 1)[0]!);
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Bootstrap confidence interval (deterministic seeded PRNG)
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Compute 95% bootstrap CI (2.5/97.5 percentile of resampled means).
|
|
97
|
+
* Uses mulberry32 seeded from the data — no Math.random, fully deterministic.
|
|
98
|
+
*/
|
|
99
|
+
export function bootstrapCI(ratios: number[], n = 1000): { lo: number; hi: number } {
|
|
100
|
+
if (ratios.length === 0) return { lo: 1, hi: 1 };
|
|
101
|
+
if (ratios.length === 1) return { lo: ratios[0]!, hi: ratios[0]! };
|
|
102
|
+
|
|
103
|
+
// Seed from data: fold all values into uint32
|
|
104
|
+
let seedVal = 0;
|
|
105
|
+
for (const r of ratios) {
|
|
106
|
+
seedVal = (Math.imul(seedVal, 31) + Math.round(r * 1e9)) >>> 0;
|
|
107
|
+
}
|
|
108
|
+
const rand = mulberry32(seedVal === 0 ? 0xdeadbeef : seedVal);
|
|
109
|
+
|
|
110
|
+
const means: number[] = new Array(n);
|
|
111
|
+
const len = ratios.length;
|
|
112
|
+
for (let i = 0; i < n; i++) {
|
|
113
|
+
let sum = 0;
|
|
114
|
+
for (let j = 0; j < len; j++) {
|
|
115
|
+
sum += ratios[Math.floor(rand() * len)]!;
|
|
116
|
+
}
|
|
117
|
+
means[i] = sum / len;
|
|
118
|
+
}
|
|
119
|
+
means.sort((a, b) => a - b);
|
|
120
|
+
return {
|
|
121
|
+
lo: means[Math.floor(n * 0.025)]!,
|
|
122
|
+
hi: means[Math.floor(n * 0.975)]!,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Repo metadata
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
interface RepoMeta {
|
|
131
|
+
url: string;
|
|
132
|
+
commit: string;
|
|
133
|
+
files: number;
|
|
134
|
+
loc: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function gitOutput(args: string[], cwd: string): string {
|
|
138
|
+
const res = spawnSync("git", args, { encoding: "utf-8", cwd });
|
|
139
|
+
return (res.stdout ?? "").trim();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getRepoMeta(repoPath: string): RepoMeta {
|
|
143
|
+
const commit = gitOutput(["rev-parse", "HEAD"], repoPath) || "unknown";
|
|
144
|
+
const url = gitOutput(["remote", "get-url", "origin"], repoPath) || "local";
|
|
145
|
+
const lsFiles = spawnSync("git", ["ls-files"], { encoding: "utf-8", cwd: repoPath });
|
|
146
|
+
const allFiles = (lsFiles.stdout ?? "")
|
|
147
|
+
.split("\n")
|
|
148
|
+
.map((f) => f.trim())
|
|
149
|
+
.filter(Boolean);
|
|
150
|
+
|
|
151
|
+
// Count LOC via wc -l on git-tracked files (best-effort)
|
|
152
|
+
let loc = 0;
|
|
153
|
+
try {
|
|
154
|
+
const wcRes = spawnSync("git", ["ls-files", "-z"], {
|
|
155
|
+
encoding: "buffer",
|
|
156
|
+
cwd: repoPath,
|
|
157
|
+
timeout: 10_000,
|
|
158
|
+
});
|
|
159
|
+
if (wcRes.stdout) {
|
|
160
|
+
const files = wcRes.stdout
|
|
161
|
+
.toString("utf-8")
|
|
162
|
+
.split("\0")
|
|
163
|
+
.filter(Boolean)
|
|
164
|
+
.map((f) => resolve(repoPath, f));
|
|
165
|
+
for (const f of files) {
|
|
166
|
+
try {
|
|
167
|
+
const content = readFileSync(f, "utf-8");
|
|
168
|
+
loc += content.split("\n").length;
|
|
169
|
+
} catch {
|
|
170
|
+
// skip unreadable files
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
loc = allFiles.length * 30; // rough fallback
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return { url, commit, files: allFiles.length, loc };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// File enumeration + size-bucket sampling
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
type SizeBucket = "2-5KB" | "5-15KB" | "15-50KB" | "50+KB";
|
|
186
|
+
|
|
187
|
+
interface FileSample {
|
|
188
|
+
path: string;
|
|
189
|
+
bucket: SizeBucket;
|
|
190
|
+
bytes: number;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function sizeKB(bytes: number): SizeBucket {
|
|
194
|
+
const kb = bytes / 1024;
|
|
195
|
+
if (kb < 5) return "2-5KB";
|
|
196
|
+
if (kb < 15) return "5-15KB";
|
|
197
|
+
if (kb < 50) return "15-50KB";
|
|
198
|
+
return "50+KB";
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function enumerateFiles(repoPath: string): FileSample[] {
|
|
202
|
+
const lsFiles = spawnSync("git", ["ls-files"], {
|
|
203
|
+
encoding: "utf-8",
|
|
204
|
+
cwd: repoPath,
|
|
205
|
+
timeout: 30_000,
|
|
206
|
+
});
|
|
207
|
+
if (lsFiles.status !== 0) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`git ls-files failed in ${repoPath}: ${lsFiles.stderr ?? "unknown error"}`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
const files = (lsFiles.stdout ?? "")
|
|
213
|
+
.split("\n")
|
|
214
|
+
.map((f) => f.trim())
|
|
215
|
+
.filter(Boolean);
|
|
216
|
+
|
|
217
|
+
const result: FileSample[] = [];
|
|
218
|
+
for (const f of files) {
|
|
219
|
+
const abs = resolve(repoPath, f);
|
|
220
|
+
try {
|
|
221
|
+
const content = readFileSync(abs);
|
|
222
|
+
const bytes = content.length;
|
|
223
|
+
if (bytes < 2048) continue; // snipCompact only fires on > 2KB
|
|
224
|
+
result.push({ path: abs, bucket: sizeKB(bytes), bytes });
|
|
225
|
+
} catch {
|
|
226
|
+
// skip locked/binary files
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function selectSample(files: FileSample[], rand: () => number): FileSample[] {
|
|
233
|
+
const buckets: Record<SizeBucket, FileSample[]> = {
|
|
234
|
+
"2-5KB": [],
|
|
235
|
+
"5-15KB": [],
|
|
236
|
+
"15-50KB": [],
|
|
237
|
+
"50+KB": [],
|
|
238
|
+
};
|
|
239
|
+
for (const f of files) buckets[f.bucket].push(f);
|
|
240
|
+
|
|
241
|
+
// Pick up to 4 from each bucket, targeting ~16 total
|
|
242
|
+
const picked: FileSample[] = [];
|
|
243
|
+
for (const bucket of ["2-5KB", "5-15KB", "15-50KB", "50+KB"] as SizeBucket[]) {
|
|
244
|
+
const n = Math.min(4, buckets[bucket].length);
|
|
245
|
+
picked.push(...seededSample(buckets[bucket], n, rand));
|
|
246
|
+
}
|
|
247
|
+
return picked;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// ashlr__read measurement
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
interface ReadSample {
|
|
255
|
+
path: string;
|
|
256
|
+
bucket: SizeBucket;
|
|
257
|
+
rawBytes: number;
|
|
258
|
+
rawTokens: number;
|
|
259
|
+
ashlrBytes: number;
|
|
260
|
+
ashlrTokens: number;
|
|
261
|
+
ratio: number; // 0–1, lower = more savings; 0.3 means 70% reduction
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function measureRead(file: FileSample): ReadSample | null {
|
|
265
|
+
try {
|
|
266
|
+
const content = readFileSync(file.path, "utf-8");
|
|
267
|
+
const rawBytes = Buffer.byteLength(content, "utf-8");
|
|
268
|
+
const rawTokens = estimateTokensFromString(content);
|
|
269
|
+
|
|
270
|
+
// Replicate ashlrRead logic: wrap as tool_result, apply snipCompact
|
|
271
|
+
const msgs: Message[] = [
|
|
272
|
+
{
|
|
273
|
+
role: "user",
|
|
274
|
+
content: [{ type: "tool_result", tool_use_id: "bench-read", content }],
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
const compact = snipCompact(msgs);
|
|
278
|
+
const block = (compact[0]!.content as { type: string; content: string }[])[0]!;
|
|
279
|
+
const out = (block as { content: string }).content;
|
|
280
|
+
|
|
281
|
+
const ashlrBytes = Buffer.byteLength(out, "utf-8");
|
|
282
|
+
const ashlrTokens = estimateTokensFromString(out);
|
|
283
|
+
const ratio = rawTokens > 0 ? ashlrTokens / rawTokens : 1;
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
path: file.path,
|
|
287
|
+
bucket: file.bucket,
|
|
288
|
+
rawBytes,
|
|
289
|
+
rawTokens,
|
|
290
|
+
ashlrBytes,
|
|
291
|
+
ashlrTokens,
|
|
292
|
+
ratio,
|
|
293
|
+
};
|
|
294
|
+
} catch {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// ashlr__grep measurement
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
const GREP_PATTERNS = ["function ", "import ", "TODO", "class ", "interface "];
|
|
304
|
+
|
|
305
|
+
interface GrepSample {
|
|
306
|
+
pattern: string;
|
|
307
|
+
rawBytes: number;
|
|
308
|
+
rawTokens: number;
|
|
309
|
+
ashlrBytes: number;
|
|
310
|
+
ashlrTokens: number;
|
|
311
|
+
ratio: number;
|
|
312
|
+
method: "rg-truncated";
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function resolveRg(): string {
|
|
316
|
+
// Try Bun.which first (respects PATH without shell aliases)
|
|
317
|
+
const bunWhich =
|
|
318
|
+
typeof (globalThis as { Bun?: { which(s: string): string | null } }).Bun !== "undefined"
|
|
319
|
+
? (globalThis as { Bun: { which(s: string): string | null } }).Bun.which("rg")
|
|
320
|
+
: null;
|
|
321
|
+
if (bunWhich) return bunWhich;
|
|
322
|
+
|
|
323
|
+
// Common binary locations (ordered by likelihood); include codex-vendored rg
|
|
324
|
+
const candidates = [
|
|
325
|
+
"/opt/homebrew/bin/rg",
|
|
326
|
+
"/usr/local/bin/rg",
|
|
327
|
+
"/usr/bin/rg",
|
|
328
|
+
// codex vendor path — actual executable, not the dotslash wrapper
|
|
329
|
+
"/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/path/rg",
|
|
330
|
+
"/opt/homebrew/lib/node_modules/@openai/codex/bin/rg",
|
|
331
|
+
];
|
|
332
|
+
for (const p of candidates) {
|
|
333
|
+
if (existsSync(p)) return p;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Cross-platform fallback: `where` on Windows, `which` on POSIX.
|
|
337
|
+
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
338
|
+
const typeRes = spawnSync(whichCmd, ["rg"], { encoding: "utf-8" });
|
|
339
|
+
const typePath = (typeRes.stdout ?? "").split(/[\r\n]/)[0]?.trim() ?? "";
|
|
340
|
+
if (typePath && existsSync(typePath)) return typePath;
|
|
341
|
+
|
|
342
|
+
return "rg";
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function measureGrep(pattern: string, repoPath: string): GrepSample | null {
|
|
346
|
+
try {
|
|
347
|
+
const rgBin = resolveRg();
|
|
348
|
+
const res = spawnSync(rgBin, ["--json", "-n", "--", pattern, repoPath], {
|
|
349
|
+
encoding: "utf-8",
|
|
350
|
+
timeout: 15_000,
|
|
351
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
352
|
+
});
|
|
353
|
+
const raw = res.stdout ?? "";
|
|
354
|
+
if (!raw.trim()) return null;
|
|
355
|
+
|
|
356
|
+
const rawBytes = Buffer.byteLength(raw, "utf-8");
|
|
357
|
+
const rawTokens = estimateTokensFromString(raw);
|
|
358
|
+
|
|
359
|
+
// Replicate rg-fallback path in ashlrGrep: truncate to 4000 chars head+tail
|
|
360
|
+
const truncated =
|
|
361
|
+
raw.length > 4000
|
|
362
|
+
? raw.slice(0, 2000) + "\n\n[... truncated ...]\n\n" + raw.slice(-1000)
|
|
363
|
+
: raw;
|
|
364
|
+
|
|
365
|
+
const ashlrBytes = Buffer.byteLength(truncated, "utf-8");
|
|
366
|
+
const ashlrTokens = estimateTokensFromString(truncated);
|
|
367
|
+
const ratio = rawTokens > 0 ? ashlrTokens / rawTokens : 1;
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
pattern,
|
|
371
|
+
rawBytes,
|
|
372
|
+
rawTokens,
|
|
373
|
+
ashlrBytes,
|
|
374
|
+
ashlrTokens,
|
|
375
|
+
ratio,
|
|
376
|
+
method: "rg-truncated",
|
|
377
|
+
};
|
|
378
|
+
} catch {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ---------------------------------------------------------------------------
|
|
384
|
+
// ashlr__edit measurement
|
|
385
|
+
// ---------------------------------------------------------------------------
|
|
386
|
+
|
|
387
|
+
type EditSize = "small" | "medium" | "large";
|
|
388
|
+
|
|
389
|
+
interface EditSample {
|
|
390
|
+
size: EditSize;
|
|
391
|
+
searchChars: number;
|
|
392
|
+
replaceChars: number;
|
|
393
|
+
naiveBytes: number; // search + replace as raw text
|
|
394
|
+
naiveTokens: number;
|
|
395
|
+
ashlrBytes: number; // diff-summary representation
|
|
396
|
+
ashlrTokens: number;
|
|
397
|
+
ratio: number;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function summarizeEditForBench(
|
|
401
|
+
search: string,
|
|
402
|
+
replace: string,
|
|
403
|
+
fileSize: number,
|
|
404
|
+
): string {
|
|
405
|
+
// Replicate the summarizeEdit output from efficiency-server.ts
|
|
406
|
+
const first = (s: string) => s.split("\n")[0]?.slice(0, 72) ?? "";
|
|
407
|
+
return [
|
|
408
|
+
`[ashlr__edit] file.ts · 1 of 1 hunks applied`,
|
|
409
|
+
` - removed (${estimateTokensFromString(search)} tok): ${first(search)}${search.length > 72 ? "…" : ""}`,
|
|
410
|
+
` + added (${estimateTokensFromString(replace)} tok): ${first(replace)}${replace.length > 72 ? "…" : ""}`,
|
|
411
|
+
].join("\n");
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function measureEdit(size: EditSize): EditSample {
|
|
415
|
+
// Synthetic edits of known sizes
|
|
416
|
+
const scenarios: Record<EditSize, { search: string; replace: string; fileSize: number }> = {
|
|
417
|
+
small: {
|
|
418
|
+
search: "const foo = 1;\n",
|
|
419
|
+
replace: "const foo = 2;\n",
|
|
420
|
+
fileSize: 3_000,
|
|
421
|
+
},
|
|
422
|
+
medium: {
|
|
423
|
+
search: [
|
|
424
|
+
"function processItems(items: string[]): string[] {",
|
|
425
|
+
" return items.filter(Boolean).map(s => s.trim());",
|
|
426
|
+
"}",
|
|
427
|
+
"",
|
|
428
|
+
"function formatOutput(result: string[]): string {",
|
|
429
|
+
" return result.join(', ');",
|
|
430
|
+
"}",
|
|
431
|
+
].join("\n"),
|
|
432
|
+
replace: [
|
|
433
|
+
"function processItems(items: string[]): string[] {",
|
|
434
|
+
" return items",
|
|
435
|
+
" .filter(Boolean)",
|
|
436
|
+
" .map(s => s.trim())",
|
|
437
|
+
" .sort();",
|
|
438
|
+
"}",
|
|
439
|
+
"",
|
|
440
|
+
"function formatOutput(result: string[]): string {",
|
|
441
|
+
" return result.join(', ');",
|
|
442
|
+
"}",
|
|
443
|
+
].join("\n"),
|
|
444
|
+
fileSize: 12_000,
|
|
445
|
+
},
|
|
446
|
+
large: {
|
|
447
|
+
search: Array.from({ length: 60 }, (_, i) =>
|
|
448
|
+
i % 3 === 0
|
|
449
|
+
? ` // step ${i}: process batch`
|
|
450
|
+
: i % 3 === 1
|
|
451
|
+
? ` const result${i} = items.slice(${i * 10}, ${i * 10 + 10}).map(transform);`
|
|
452
|
+
: ` output.push(...result${i - 1});`,
|
|
453
|
+
).join("\n"),
|
|
454
|
+
replace: Array.from({ length: 60 }, (_, i) =>
|
|
455
|
+
i % 3 === 0
|
|
456
|
+
? ` // step ${i}: process batch (optimized)`
|
|
457
|
+
: i % 3 === 1
|
|
458
|
+
? ` const result${i} = batchTransform(items, ${i * 10}, ${i * 10 + 10});`
|
|
459
|
+
: ` output.push(...result${i - 1});`,
|
|
460
|
+
).join("\n"),
|
|
461
|
+
fileSize: 40_000,
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const { search, replace, fileSize } = scenarios[size];
|
|
466
|
+
|
|
467
|
+
// Naive: ship full before+after (2× file approximated as search+replace + surrounding context)
|
|
468
|
+
const naiveText = `BEFORE:\n${search}\n\nAFTER:\n${replace}`;
|
|
469
|
+
const naiveBytes = Buffer.byteLength(naiveText, "utf-8");
|
|
470
|
+
const naiveTokens = estimateTokensFromString(naiveText);
|
|
471
|
+
|
|
472
|
+
// ashlr__edit: send only the diff summary
|
|
473
|
+
const summary = summarizeEditForBench(search, replace, fileSize);
|
|
474
|
+
const ashlrBytes = Buffer.byteLength(summary, "utf-8");
|
|
475
|
+
const ashlrTokens = estimateTokensFromString(summary);
|
|
476
|
+
const ratio = naiveTokens > 0 ? ashlrTokens / naiveTokens : 1;
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
size,
|
|
480
|
+
searchChars: search.length,
|
|
481
|
+
replaceChars: replace.length,
|
|
482
|
+
naiveBytes,
|
|
483
|
+
naiveTokens,
|
|
484
|
+
ashlrBytes,
|
|
485
|
+
ashlrTokens,
|
|
486
|
+
ratio,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// ---------------------------------------------------------------------------
|
|
491
|
+
// Aggregation
|
|
492
|
+
// ---------------------------------------------------------------------------
|
|
493
|
+
|
|
494
|
+
interface ToolAggregate {
|
|
495
|
+
mean: number;
|
|
496
|
+
p50: number;
|
|
497
|
+
p90: number;
|
|
498
|
+
confidenceInterval: { lo: number; hi: number };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function aggregate(ratios: number[]): ToolAggregate {
|
|
502
|
+
if (ratios.length === 0) return { mean: 1, p50: 1, p90: 1, confidenceInterval: { lo: 1, hi: 1 } };
|
|
503
|
+
const sorted = ratios.slice().sort((a, b) => a - b);
|
|
504
|
+
const mean = ratios.reduce((s, r) => s + r, 0) / ratios.length;
|
|
505
|
+
const p50 = sorted[Math.floor(sorted.length * 0.5)]!;
|
|
506
|
+
const p90 = sorted[Math.floor(sorted.length * 0.9)]!;
|
|
507
|
+
const confidenceInterval = bootstrapCI(ratios);
|
|
508
|
+
return { mean, p50, p90, confidenceInterval };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ---------------------------------------------------------------------------
|
|
512
|
+
// Output schema
|
|
513
|
+
// ---------------------------------------------------------------------------
|
|
514
|
+
|
|
515
|
+
interface BenchmarkOutput {
|
|
516
|
+
version: 2;
|
|
517
|
+
measuredAt: string;
|
|
518
|
+
repo: RepoMeta;
|
|
519
|
+
samples: {
|
|
520
|
+
"ashlr__read": ReadSample[];
|
|
521
|
+
"ashlr__grep": GrepSample[];
|
|
522
|
+
"ashlr__edit": EditSample[];
|
|
523
|
+
};
|
|
524
|
+
aggregate: {
|
|
525
|
+
"ashlr__read": ToolAggregate;
|
|
526
|
+
"ashlr__grep": ToolAggregate;
|
|
527
|
+
"ashlr__edit": ToolAggregate;
|
|
528
|
+
overall: { mean: number };
|
|
529
|
+
};
|
|
530
|
+
methodology: string;
|
|
531
|
+
heuristicError?: number | null;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const METHODOLOGY = `
|
|
535
|
+
Measurement methodology (version 2):
|
|
536
|
+
|
|
537
|
+
**ashlr__read**: For each sampled source file, we measure raw file bytes and
|
|
538
|
+
token count (chars/4 heuristic). We then apply the same snipCompact
|
|
539
|
+
transformation used at runtime — wrapping the content in a tool_result message
|
|
540
|
+
and calling snipCompact() — and measure the resulting byte/token count. The
|
|
541
|
+
ratio is ashlrTokens / rawTokens. Files below 2 KB are excluded because
|
|
542
|
+
snipCompact only fires on tool results > 2 000 chars; savings are zero by
|
|
543
|
+
design for small files.
|
|
544
|
+
|
|
545
|
+
Files are selected deterministically: the repo HEAD commit SHA is folded into a
|
|
546
|
+
32-bit seed (mulberry32 PRNG), then up to 4 files are sampled from each of four
|
|
547
|
+
size buckets (2–5 KB, 5–15 KB, 15–50 KB, 50+ KB). Re-running on the same
|
|
548
|
+
commit always picks the same files.
|
|
549
|
+
|
|
550
|
+
**ashlr__grep**: Five common patterns (function, import, TODO, class, interface)
|
|
551
|
+
are run via rg --json against the repo root. Raw output bytes are measured
|
|
552
|
+
directly. The ashlr__grep fallback path (no genome) truncates output to 4 000
|
|
553
|
+
chars (head 2 000 + tail 1 000). The ratio is truncated/raw.
|
|
554
|
+
|
|
555
|
+
Note: when a .ashlrcode/genome/ index is present, real-world grep savings are
|
|
556
|
+
substantially higher. This benchmark measures only the conservative
|
|
557
|
+
no-genome baseline.
|
|
558
|
+
|
|
559
|
+
**ashlr__edit**: Three synthetic edits (small ~15 chars, medium ~300 chars,
|
|
560
|
+
large ~3 000 chars) compare the naive "ship before+after as text" approach
|
|
561
|
+
against ashlr__edit's diff-summary format (one header line + removed/added
|
|
562
|
+
first-lines). The ratio is summary tokens / naive tokens.
|
|
563
|
+
|
|
564
|
+
**Aggregation**: per-tool mean/p50/p90 are computed over each tool's ratio
|
|
565
|
+
values (lower ratio = more savings). The \`overall.mean\` is pooled across
|
|
566
|
+
every individual sample regardless of tool — so tools with more samples (read
|
|
567
|
+
has 15, grep has 5, edit has 3) weight the overall figure proportionally.
|
|
568
|
+
That makes the headline number reflect the workload mix, not a uniform
|
|
569
|
+
per-tool average. The unweighted mean of per-tool means is intentionally NOT
|
|
570
|
+
published because it gives equal weight to a 3-sample tool and a 15-sample
|
|
571
|
+
tool, which over-weights the synthetic edit overhead.
|
|
572
|
+
|
|
573
|
+
Token counts use the chars/4 heuristic, the same estimator the plugin uses at
|
|
574
|
+
runtime for savings accounting.
|
|
575
|
+
`.trim();
|
|
576
|
+
|
|
577
|
+
// ---------------------------------------------------------------------------
|
|
578
|
+
// Main
|
|
579
|
+
// ---------------------------------------------------------------------------
|
|
580
|
+
|
|
581
|
+
export async function runBenchmark(opts: {
|
|
582
|
+
repo: string;
|
|
583
|
+
out: string;
|
|
584
|
+
dryRun: boolean;
|
|
585
|
+
validateTokenizer?: boolean;
|
|
586
|
+
compare?: boolean;
|
|
587
|
+
}): Promise<BenchmarkOutput> {
|
|
588
|
+
const { repo, out, dryRun, validateTokenizer = false, compare = false } = opts;
|
|
589
|
+
|
|
590
|
+
if (!existsSync(repo)) {
|
|
591
|
+
throw new Error(`Repo path does not exist: ${repo}`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
console.log(`[run-benchmark] repo: ${repo}`);
|
|
595
|
+
const meta = getRepoMeta(repo);
|
|
596
|
+
console.log(`[run-benchmark] commit: ${meta.commit} files: ${meta.files} loc: ${meta.loc}`);
|
|
597
|
+
|
|
598
|
+
// Seed PRNG from commit SHA
|
|
599
|
+
const rand = mulberry32(seedFromCommit(meta.commit));
|
|
600
|
+
|
|
601
|
+
// --- ashlr__read ---
|
|
602
|
+
console.log("[run-benchmark] enumerating files...");
|
|
603
|
+
const allFiles = enumerateFiles(repo);
|
|
604
|
+
if (allFiles.length === 0) {
|
|
605
|
+
console.warn("[run-benchmark] WARNING: no files >= 2KB found — read samples will be empty");
|
|
606
|
+
}
|
|
607
|
+
const sampled = selectSample(allFiles, rand);
|
|
608
|
+
console.log(`[run-benchmark] sampled ${sampled.length} files for read benchmark`);
|
|
609
|
+
|
|
610
|
+
const readSamples: ReadSample[] = [];
|
|
611
|
+
for (const file of sampled) {
|
|
612
|
+
const s = measureRead(file);
|
|
613
|
+
if (s) {
|
|
614
|
+
readSamples.push(s);
|
|
615
|
+
const pct = ((1 - s.ratio) * 100).toFixed(1);
|
|
616
|
+
console.log(
|
|
617
|
+
`[run-benchmark] read ${relative(repo, file.path).padEnd(50)} ${file.bucket.padEnd(8)} ${pct}% saved`,
|
|
618
|
+
);
|
|
619
|
+
} else {
|
|
620
|
+
console.warn(`[run-benchmark] read SKIP ${file.path}`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// --- ashlr__grep ---
|
|
625
|
+
console.log("[run-benchmark] measuring grep...");
|
|
626
|
+
const grepSamples: GrepSample[] = [];
|
|
627
|
+
for (const pattern of GREP_PATTERNS) {
|
|
628
|
+
const s = measureGrep(pattern, repo);
|
|
629
|
+
if (s) {
|
|
630
|
+
grepSamples.push(s);
|
|
631
|
+
const pct = ((1 - s.ratio) * 100).toFixed(1);
|
|
632
|
+
const label = `"${pattern.trim()}"`.padEnd(12);
|
|
633
|
+
console.log(
|
|
634
|
+
`[run-benchmark] grep ${label} ${pct}% saved (${s.rawBytes} raw → ${s.ashlrBytes} ashlr bytes)`,
|
|
635
|
+
);
|
|
636
|
+
} else {
|
|
637
|
+
console.warn(`[run-benchmark] grep no matches for "${pattern}" — skipped`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// --- ashlr__edit ---
|
|
642
|
+
console.log("[run-benchmark] measuring edit...");
|
|
643
|
+
const formatSampleSavings = (pct: number) => pct >= 0
|
|
644
|
+
? `${pct.toFixed(1)}% saved`
|
|
645
|
+
: `${Math.abs(pct).toFixed(1)}% overhead`;
|
|
646
|
+
const editSamples: EditSample[] = [];
|
|
647
|
+
for (const size of ["small", "medium", "large"] as EditSize[]) {
|
|
648
|
+
const s = measureEdit(size);
|
|
649
|
+
editSamples.push(s);
|
|
650
|
+
const pct = (1 - s.ratio) * 100;
|
|
651
|
+
console.log(`[run-benchmark] edit ${size.padEnd(8)} ${formatSampleSavings(pct)}`);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// --- Aggregates ---
|
|
655
|
+
const readAgg = aggregate(readSamples.map((s) => s.ratio));
|
|
656
|
+
const grepAgg = aggregate(grepSamples.map((s) => s.ratio));
|
|
657
|
+
const editAgg = aggregate(editSamples.map((s) => s.ratio));
|
|
658
|
+
const allRatios = [
|
|
659
|
+
...readSamples.map((s) => s.ratio),
|
|
660
|
+
...grepSamples.map((s) => s.ratio),
|
|
661
|
+
...editSamples.map((s) => s.ratio),
|
|
662
|
+
];
|
|
663
|
+
const overallMean = allRatios.length > 0
|
|
664
|
+
? allRatios.reduce((s, r) => s + r, 0) / allRatios.length
|
|
665
|
+
: 1;
|
|
666
|
+
|
|
667
|
+
const output: BenchmarkOutput = {
|
|
668
|
+
version: 2,
|
|
669
|
+
measuredAt: new Date().toISOString(),
|
|
670
|
+
repo: meta,
|
|
671
|
+
samples: {
|
|
672
|
+
"ashlr__read": readSamples,
|
|
673
|
+
"ashlr__grep": grepSamples,
|
|
674
|
+
"ashlr__edit": editSamples,
|
|
675
|
+
},
|
|
676
|
+
aggregate: {
|
|
677
|
+
"ashlr__read": readAgg,
|
|
678
|
+
"ashlr__grep": grepAgg,
|
|
679
|
+
"ashlr__edit": editAgg,
|
|
680
|
+
overall: { mean: overallMean },
|
|
681
|
+
},
|
|
682
|
+
methodology: METHODOLOGY,
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
const formatSavings = (ratio: number) => {
|
|
686
|
+
const pct = (1 - ratio) * 100;
|
|
687
|
+
return pct >= 0 ? `−${pct.toFixed(1)}% token savings` : `+${Math.abs(pct).toFixed(1)}% token overhead`;
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
console.log(`\n[run-benchmark] RESULT: overall mean ${formatSavings(overallMean)}`);
|
|
691
|
+
console.log(`[run-benchmark] read mean ${formatSavings(readAgg.mean)}`);
|
|
692
|
+
console.log(`[run-benchmark] grep mean ${formatSavings(grepAgg.mean)}`);
|
|
693
|
+
console.log(`[run-benchmark] edit mean ${formatSavings(editAgg.mean)}`);
|
|
694
|
+
console.log(`[run-benchmark] read CI [${((1-readAgg.confidenceInterval.hi)*100).toFixed(1)}%–${((1-readAgg.confidenceInterval.lo)*100).toFixed(1)}%]`);
|
|
695
|
+
|
|
696
|
+
// --compare: print A/B table
|
|
697
|
+
if (compare && readSamples.length > 0) {
|
|
698
|
+
console.log("\n[run-benchmark] --compare: native vs ashlr (read samples)");
|
|
699
|
+
console.log(" file native-tok ashlr-tok ratio");
|
|
700
|
+
for (const s of readSamples) {
|
|
701
|
+
const name = s.path.split("/").slice(-2).join("/").padEnd(41);
|
|
702
|
+
console.log(` ${name} ${String(s.rawTokens).padStart(10)} ${String(s.ashlrTokens).padStart(9)} ${(s.ratio * 100).toFixed(1)}%`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// --validate-tokenizer: sample 5% through measureTokens
|
|
707
|
+
let heuristicError: number | null = null;
|
|
708
|
+
if (validateTokenizer && process.env.ANTHROPIC_API_KEY) {
|
|
709
|
+
console.log("[run-benchmark] --validate-tokenizer: sampling API token counts...");
|
|
710
|
+
const { measureTokens } = await import("../servers/_token-measure.ts");
|
|
711
|
+
const sampleN = Math.max(1, Math.ceil(readSamples.length * 0.05));
|
|
712
|
+
const toValidate = seededSample(readSamples, sampleN, rand);
|
|
713
|
+
let totalEstimate = 0;
|
|
714
|
+
let totalMeasured = 0;
|
|
715
|
+
for (const s of toValidate) {
|
|
716
|
+
try {
|
|
717
|
+
const content = readFileSync(s.path, "utf-8");
|
|
718
|
+
const measured = await measureTokens(content);
|
|
719
|
+
if (measured !== null) { totalEstimate += s.rawTokens; totalMeasured += measured; }
|
|
720
|
+
} catch { /* skip unreadable */ }
|
|
721
|
+
}
|
|
722
|
+
if (totalMeasured > 0) {
|
|
723
|
+
heuristicError = Math.abs(totalEstimate - totalMeasured) / totalMeasured * 100;
|
|
724
|
+
console.log(`[run-benchmark] heuristicError: ${heuristicError.toFixed(2)}%`);
|
|
725
|
+
}
|
|
726
|
+
} else if (validateTokenizer) {
|
|
727
|
+
console.log("[run-benchmark] --validate-tokenizer: ANTHROPIC_API_KEY not set, skipping");
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (heuristicError !== null) output.heuristicError = heuristicError;
|
|
731
|
+
|
|
732
|
+
if (!dryRun) {
|
|
733
|
+
writeFileSync(out, JSON.stringify(output, null, 2), "utf-8");
|
|
734
|
+
console.log(`[run-benchmark] wrote ${out}`);
|
|
735
|
+
} else {
|
|
736
|
+
console.log("[run-benchmark] --dry-run: skipping file write");
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return output;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (import.meta.main) {
|
|
743
|
+
const args = parseArgs(process.argv.slice(2));
|
|
744
|
+
try {
|
|
745
|
+
await runBenchmark(args);
|
|
746
|
+
} catch (err) {
|
|
747
|
+
console.error("[run-benchmark] ERROR:", err instanceof Error ? err.message : String(err));
|
|
748
|
+
process.exit(1);
|
|
749
|
+
}
|
|
750
|
+
}
|