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,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _prefetch — Predictive prefetch MVP (Q3 supporting pillar).
|
|
3
|
+
*
|
|
4
|
+
* When ashlr__read succeeds on file X, we schedule a fire-and-forget
|
|
5
|
+
* background task that:
|
|
6
|
+
* 1. Scans X's import statements via lightweight regex (NOT AST) to
|
|
7
|
+
* identify neighbour files.
|
|
8
|
+
* 2. Picks the top-N most-imported neighbours (deduped, frequency-ranked).
|
|
9
|
+
* 3. Reads each, runs snipCompact, and stores the result in the existing
|
|
10
|
+
* _read-cache so the next ashlr__read on any of them is instant.
|
|
11
|
+
*
|
|
12
|
+
* Tier gates (enforced at top of schedulePrefetch):
|
|
13
|
+
* - free → return immediately, no work.
|
|
14
|
+
* - pro → maxNeighbors clamped to 3.
|
|
15
|
+
* - team → maxNeighbors clamped to 10.
|
|
16
|
+
*
|
|
17
|
+
* Kill switch:
|
|
18
|
+
* - ASHLR_PREFETCH=off → return immediately regardless of tier.
|
|
19
|
+
*
|
|
20
|
+
* Constraints:
|
|
21
|
+
* - NEVER blocks the calling read path. Fire-and-forget via setImmediate.
|
|
22
|
+
* - Hard 1.5s wallclock cap; remaining neighbours skipped after the cap.
|
|
23
|
+
* - cwd-clamped: never reads outside the project root.
|
|
24
|
+
* - Idempotent: re-prefetching a path that's already cached is a no-op.
|
|
25
|
+
* - No external deps. Pure stdlib + bun built-ins.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { readFile, stat } from "fs/promises";
|
|
29
|
+
import { existsSync, statSync } from "fs";
|
|
30
|
+
import { dirname, isAbsolute, resolve, sep } from "path";
|
|
31
|
+
import {
|
|
32
|
+
type Message,
|
|
33
|
+
snipCompact,
|
|
34
|
+
} from "@ashlr/core-efficiency";
|
|
35
|
+
import { getCached, setCached } from "./_read-cache";
|
|
36
|
+
import { clampToCwd } from "./_cwd-clamp";
|
|
37
|
+
import { logEvent } from "./_events";
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Constants
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
/** Hard wallclock cap for the entire background prefetch task. */
|
|
44
|
+
const HARD_BUDGET_MS = 1500;
|
|
45
|
+
|
|
46
|
+
/** Extensions tried when resolving a relative import without explicit extension. */
|
|
47
|
+
const RESOLVE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".h", ".hpp"];
|
|
48
|
+
|
|
49
|
+
/** Directories never followed for import resolution. */
|
|
50
|
+
const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", ".next"]);
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Types
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
export type PrefetchTier = "free" | "pro" | "team";
|
|
57
|
+
|
|
58
|
+
export interface PrefetchOptions {
|
|
59
|
+
tier: PrefetchTier;
|
|
60
|
+
/** Caller hint; will be clamped per tier. */
|
|
61
|
+
maxNeighbors: number;
|
|
62
|
+
/** Project cwd — used as the resolution + clamp root. */
|
|
63
|
+
cwd: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface InflightTracker {
|
|
67
|
+
/** Paths currently being prefetched (so duplicate schedules become no-ops). */
|
|
68
|
+
inflight: Set<string>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const tracker: InflightTracker = { inflight: new Set() };
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Tier gating
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
/** Resolve the per-tier neighbor cap. Free → 0 (disabled). */
|
|
78
|
+
export function tierCap(tier: PrefetchTier): number {
|
|
79
|
+
if (tier === "free") return 0;
|
|
80
|
+
if (tier === "pro") return 3;
|
|
81
|
+
if (tier === "team") return 10;
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** ASHLR_PREFETCH kill switch — "off" disables all prefetch work. */
|
|
86
|
+
export function isPrefetchDisabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
87
|
+
const raw = env["ASHLR_PREFETCH"];
|
|
88
|
+
if (raw == null) return false;
|
|
89
|
+
const v = String(raw).trim().toLowerCase();
|
|
90
|
+
return v === "off" || v === "0" || v === "false" || v === "no";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Import extraction
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Lightweight regex import extraction. Covers:
|
|
99
|
+
* - TS/JS ES imports: `import X from 'Y'`, `import { X } from 'Y'`
|
|
100
|
+
* - TS/JS `from 'Y'` bare matches (catches re-exports)
|
|
101
|
+
* - CommonJS `require('Y')`
|
|
102
|
+
* - Python `from X import` and `import X`
|
|
103
|
+
* - C/C++ `#include "X"` / `#include <X>`
|
|
104
|
+
*
|
|
105
|
+
* Returns frequency-ranked unique specifiers. Multiple matches of the same
|
|
106
|
+
* specifier are counted once per occurrence (so a file imported twice ranks
|
|
107
|
+
* higher than a file imported once) but the returned list is deduped.
|
|
108
|
+
*/
|
|
109
|
+
export function extractImports(source: string): string[] {
|
|
110
|
+
const patterns: RegExp[] = [
|
|
111
|
+
/import\s+[^'"`;]*?\s+from\s+['"]([^'"]+)['"]/g,
|
|
112
|
+
/from\s+['"]([^'"]+)['"]/g,
|
|
113
|
+
/require\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
114
|
+
// Python: `from X import …` / `import X[.Y]`
|
|
115
|
+
/^\s*from\s+([A-Za-z_][\w.]*)\s+import\b/gm,
|
|
116
|
+
/^\s*import\s+([A-Za-z_][\w.]*)/gm,
|
|
117
|
+
// C/C++ includes
|
|
118
|
+
/#include\s+["<]([^">]+)[">]/g,
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
const counts = new Map<string, number>();
|
|
122
|
+
for (const re of patterns) {
|
|
123
|
+
let m: RegExpExecArray | null;
|
|
124
|
+
while ((m = re.exec(source)) !== null) {
|
|
125
|
+
const spec = m[1];
|
|
126
|
+
if (!spec) continue;
|
|
127
|
+
counts.set(spec, (counts.get(spec) ?? 0) + 1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Highest count first; stable secondary by first-seen order.
|
|
132
|
+
return Array.from(counts.entries())
|
|
133
|
+
.sort((a, b) => b[1] - a[1])
|
|
134
|
+
.map(([spec]) => spec);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Resolution
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Resolve an import specifier to an absolute file path inside `cwd`, or null.
|
|
143
|
+
*
|
|
144
|
+
* Strategy (best-effort, no full resolver):
|
|
145
|
+
* - Skip bare specifiers (no leading "./" / "../" / "/") — those are
|
|
146
|
+
* usually node_modules or stdlib, which we don't want to prefetch.
|
|
147
|
+
* - Resolve relative paths against `fromFile`'s directory.
|
|
148
|
+
* - Try the path as-is; then with each RESOLVE_EXTENSIONS suffix; then
|
|
149
|
+
* as `path/index.<ext>`.
|
|
150
|
+
* - Refuse anything under SKIP_DIRS.
|
|
151
|
+
* - Final cwd-clamp guards against `../../../etc/passwd` escapes.
|
|
152
|
+
*/
|
|
153
|
+
export function resolveImport(
|
|
154
|
+
spec: string,
|
|
155
|
+
fromFile: string,
|
|
156
|
+
cwd: string,
|
|
157
|
+
): string | null {
|
|
158
|
+
// Bare specifier (npm pkg / stdlib) — skip; we only prefetch first-party files.
|
|
159
|
+
if (!spec.startsWith(".") && !spec.startsWith("/")) {
|
|
160
|
+
// Python dotted module — try as a relative-from-cwd path lookup.
|
|
161
|
+
if (/^[A-Za-z_][\w.]*$/.test(spec)) {
|
|
162
|
+
const candidatePy = resolve(cwd, spec.replace(/\./g, sep) + ".py");
|
|
163
|
+
if (existsSync(candidatePy)) return candidatePy;
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const base = isAbsolute(spec) ? spec : resolve(dirname(fromFile), spec);
|
|
169
|
+
|
|
170
|
+
const candidates: string[] = [base];
|
|
171
|
+
for (const ext of RESOLVE_EXTENSIONS) {
|
|
172
|
+
candidates.push(base + ext);
|
|
173
|
+
}
|
|
174
|
+
for (const ext of RESOLVE_EXTENSIONS) {
|
|
175
|
+
candidates.push(resolve(base, "index" + ext));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
for (const c of candidates) {
|
|
179
|
+
if (containsSkipDir(c)) continue;
|
|
180
|
+
try {
|
|
181
|
+
const st = statSync(c);
|
|
182
|
+
if (st.isFile()) {
|
|
183
|
+
// Clamp to project cwd as the security boundary.
|
|
184
|
+
const clamp = clampToCwd(c, "ashlr__prefetch");
|
|
185
|
+
if (!clamp.ok) return null;
|
|
186
|
+
return clamp.abs;
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
// not this candidate; continue
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function containsSkipDir(p: string): boolean {
|
|
196
|
+
const parts = p.split(sep);
|
|
197
|
+
for (const part of parts) {
|
|
198
|
+
if (SKIP_DIRS.has(part)) return true;
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// Public API
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Test hook: optional override for readFile so tests can simulate slow I/O.
|
|
209
|
+
* Production code never sets this.
|
|
210
|
+
*/
|
|
211
|
+
let _readImpl: (p: string) => Promise<string> = (p) => readFile(p, "utf-8");
|
|
212
|
+
export function _setReadImplForTest(fn: ((p: string) => Promise<string>) | null): void {
|
|
213
|
+
_readImpl = fn ?? ((p) => readFile(p, "utf-8"));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Test hook: drains the inflight set. Tests should call this in afterEach to
|
|
218
|
+
* isolate idempotency assertions.
|
|
219
|
+
*/
|
|
220
|
+
export function _resetInflightForTest(): void {
|
|
221
|
+
tracker.inflight.clear();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Schedule a fire-and-forget prefetch starting from `path`.
|
|
226
|
+
*
|
|
227
|
+
* IMPORTANT: This function MUST NOT be awaited by the calling read path.
|
|
228
|
+
* It returns immediately; all real work is deferred via setImmediate.
|
|
229
|
+
*
|
|
230
|
+
* Returns a Promise<PrefetchResult> for test instrumentation only; production
|
|
231
|
+
* callers should ignore the return value.
|
|
232
|
+
*/
|
|
233
|
+
export interface PrefetchResult {
|
|
234
|
+
scheduled: number;
|
|
235
|
+
completed: number;
|
|
236
|
+
durationMs: number;
|
|
237
|
+
skipped: "kill-switch" | "free-tier" | "idempotent" | "stat-failed" | null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function schedulePrefetch(
|
|
241
|
+
path: string,
|
|
242
|
+
opts: PrefetchOptions,
|
|
243
|
+
): Promise<PrefetchResult> {
|
|
244
|
+
// Kill switch — highest priority, before any work.
|
|
245
|
+
if (isPrefetchDisabled()) {
|
|
246
|
+
return Promise.resolve({ scheduled: 0, completed: 0, durationMs: 0, skipped: "kill-switch" });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Tier gate — free tier does nothing.
|
|
250
|
+
const cap = Math.min(opts.maxNeighbors, tierCap(opts.tier));
|
|
251
|
+
if (cap <= 0) {
|
|
252
|
+
return Promise.resolve({ scheduled: 0, completed: 0, durationMs: 0, skipped: "free-tier" });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Idempotency: already scheduled for this path → no-op.
|
|
256
|
+
if (tracker.inflight.has(path)) {
|
|
257
|
+
return Promise.resolve({ scheduled: 0, completed: 0, durationMs: 0, skipped: "idempotent" });
|
|
258
|
+
}
|
|
259
|
+
tracker.inflight.add(path);
|
|
260
|
+
|
|
261
|
+
// Defer all real work to the next tick so the caller's read response goes
|
|
262
|
+
// out first. The returned promise resolves when the background task finishes
|
|
263
|
+
// (or when the 1.5s budget elapses).
|
|
264
|
+
return new Promise<PrefetchResult>((resolvePromise) => {
|
|
265
|
+
setImmediate(() => {
|
|
266
|
+
const started = Date.now();
|
|
267
|
+
runPrefetch(path, cap, opts, started)
|
|
268
|
+
.then((res) => {
|
|
269
|
+
tracker.inflight.delete(path);
|
|
270
|
+
resolvePromise(res);
|
|
271
|
+
})
|
|
272
|
+
.catch(() => {
|
|
273
|
+
// Never propagate — prefetch is best-effort.
|
|
274
|
+
tracker.inflight.delete(path);
|
|
275
|
+
resolvePromise({
|
|
276
|
+
scheduled: 0,
|
|
277
|
+
completed: 0,
|
|
278
|
+
durationMs: Date.now() - started,
|
|
279
|
+
skipped: null,
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
// Internals
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
async function runPrefetch(
|
|
291
|
+
path: string,
|
|
292
|
+
cap: number,
|
|
293
|
+
opts: PrefetchOptions,
|
|
294
|
+
started: number,
|
|
295
|
+
): Promise<PrefetchResult> {
|
|
296
|
+
const remainingMs = () => HARD_BUDGET_MS - (Date.now() - started);
|
|
297
|
+
|
|
298
|
+
// Read the source file (sync stat first to skip non-files cheaply).
|
|
299
|
+
let stMtime = 0;
|
|
300
|
+
try {
|
|
301
|
+
const st = await stat(path);
|
|
302
|
+
if (!st.isFile()) {
|
|
303
|
+
return { scheduled: 0, completed: 0, durationMs: Date.now() - started, skipped: "stat-failed" };
|
|
304
|
+
}
|
|
305
|
+
stMtime = st.mtimeMs;
|
|
306
|
+
} catch {
|
|
307
|
+
return { scheduled: 0, completed: 0, durationMs: Date.now() - started, skipped: "stat-failed" };
|
|
308
|
+
}
|
|
309
|
+
void stMtime;
|
|
310
|
+
|
|
311
|
+
if (remainingMs() <= 0) {
|
|
312
|
+
return { scheduled: 0, completed: 0, durationMs: Date.now() - started, skipped: null };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
let source: string;
|
|
316
|
+
try {
|
|
317
|
+
source = await _readImpl(path);
|
|
318
|
+
} catch {
|
|
319
|
+
return { scheduled: 0, completed: 0, durationMs: Date.now() - started, skipped: null };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Extract & resolve neighbours.
|
|
323
|
+
const specs = extractImports(source);
|
|
324
|
+
const neighbours: string[] = [];
|
|
325
|
+
for (const spec of specs) {
|
|
326
|
+
if (neighbours.length >= cap) break;
|
|
327
|
+
const abs = resolveImport(spec, path, opts.cwd);
|
|
328
|
+
if (!abs) continue;
|
|
329
|
+
if (abs === path) continue; // self-import
|
|
330
|
+
if (neighbours.includes(abs)) continue;
|
|
331
|
+
neighbours.push(abs);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Fire telemetry: scheduled count up front so we observe it even if the
|
|
335
|
+
// budget cuts work short.
|
|
336
|
+
void logEvent("tool_call", {
|
|
337
|
+
tool: "ashlr__prefetch",
|
|
338
|
+
reason: "prefetch_scheduled",
|
|
339
|
+
extra: {
|
|
340
|
+
neighbors_scheduled: neighbours.length,
|
|
341
|
+
tier: opts.tier,
|
|
342
|
+
cap,
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
let completed = 0;
|
|
347
|
+
for (const neighbour of neighbours) {
|
|
348
|
+
if (remainingMs() <= 0) break;
|
|
349
|
+
|
|
350
|
+
// Already cached + fresh? Skip — idempotent at the neighbour level.
|
|
351
|
+
try {
|
|
352
|
+
const st = statSync(neighbour);
|
|
353
|
+
const hit = getCached(neighbour);
|
|
354
|
+
if (hit && hit.mtimeMs === st.mtimeMs) {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Race the read against the remaining budget.
|
|
359
|
+
const content = await raceWithBudget(_readImpl(neighbour), remainingMs());
|
|
360
|
+
if (content == null) break;
|
|
361
|
+
|
|
362
|
+
// Apply snipCompact via the same Message wrapper read-server uses.
|
|
363
|
+
const msgs: Message[] = [
|
|
364
|
+
{
|
|
365
|
+
role: "user",
|
|
366
|
+
content: [
|
|
367
|
+
{ type: "tool_result", tool_use_id: "ashlr-prefetch", content },
|
|
368
|
+
],
|
|
369
|
+
},
|
|
370
|
+
];
|
|
371
|
+
const compact = snipCompact(msgs);
|
|
372
|
+
const block = (compact[0]!.content as { type: string; content: string }[])[0]!;
|
|
373
|
+
const compactText = (block as { content: string }).content;
|
|
374
|
+
|
|
375
|
+
setCached(neighbour, {
|
|
376
|
+
mtimeMs: st.mtimeMs,
|
|
377
|
+
result: `(prefetched)\n${compactText}`,
|
|
378
|
+
sourceBytes: content.length,
|
|
379
|
+
});
|
|
380
|
+
completed++;
|
|
381
|
+
} catch {
|
|
382
|
+
// Skip this neighbour silently — best-effort.
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const durationMs = Date.now() - started;
|
|
388
|
+
void logEvent("tool_call", {
|
|
389
|
+
tool: "ashlr__prefetch",
|
|
390
|
+
reason: "prefetch_hit",
|
|
391
|
+
extra: {
|
|
392
|
+
neighbors_scheduled: neighbours.length,
|
|
393
|
+
neighbors_completed: completed,
|
|
394
|
+
duration_ms: durationMs,
|
|
395
|
+
tier: opts.tier,
|
|
396
|
+
budget_exhausted: remainingMs() <= 0,
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
return { scheduled: neighbours.length, completed, durationMs, skipped: null };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Race a promise against the remaining wallclock budget. Returns null on
|
|
405
|
+
* timeout. Used to enforce the 1.5s hard cap when a single neighbour read
|
|
406
|
+
* stalls (e.g., slow disk, network FS).
|
|
407
|
+
*/
|
|
408
|
+
function raceWithBudget<T>(p: Promise<T>, budgetMs: number): Promise<T | null> {
|
|
409
|
+
if (budgetMs <= 0) return Promise.resolve(null);
|
|
410
|
+
return new Promise<T | null>((resolveRace) => {
|
|
411
|
+
let settled = false;
|
|
412
|
+
const t = setTimeout(() => {
|
|
413
|
+
if (!settled) {
|
|
414
|
+
settled = true;
|
|
415
|
+
resolveRace(null);
|
|
416
|
+
}
|
|
417
|
+
}, budgetMs);
|
|
418
|
+
p.then(
|
|
419
|
+
(v) => {
|
|
420
|
+
if (!settled) {
|
|
421
|
+
settled = true;
|
|
422
|
+
clearTimeout(t);
|
|
423
|
+
resolveRace(v);
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
() => {
|
|
427
|
+
if (!settled) {
|
|
428
|
+
settled = true;
|
|
429
|
+
clearTimeout(t);
|
|
430
|
+
resolveRace(null);
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
);
|
|
434
|
+
});
|
|
435
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _pricing.ts — single source of truth for $/MTok pricing across the plugin.
|
|
3
|
+
*
|
|
4
|
+
* Prior state: `servers/efficiency-server.ts` used $3/MTok (sonnet-4.5 input
|
|
5
|
+
* only); `scripts/savings-dashboard.ts` used a hardcoded $5/MTok "blended"
|
|
6
|
+
* rate; `scripts/session-greet.ts` had its own copy; `scripts/generate-badge.ts`
|
|
7
|
+
* duplicated yet another PRICING map. Same token count produced three
|
|
8
|
+
* different dollar values depending on which surface rendered it.
|
|
9
|
+
*
|
|
10
|
+
* This module centralizes the table. Both the efficiency-server renderer
|
|
11
|
+
* and the dashboard import `pricing()` / `costFor()` here so a token count
|
|
12
|
+
* maps to exactly one USD value regardless of surface.
|
|
13
|
+
*
|
|
14
|
+
* Model selection: read `ASHLR_PRICING_MODEL` env var. Default is
|
|
15
|
+
* `sonnet-4.5` (Claude Code's default model). Unknown models fall back to
|
|
16
|
+
* the default rather than throw — pricing must never break rendering.
|
|
17
|
+
*
|
|
18
|
+
* Numbers reflect input-token pricing per million tokens, as of 2026-04.
|
|
19
|
+
* Savings reporting uses the input rate (we're measuring tokens *not sent*
|
|
20
|
+
* into the model's context window, so the input price is the right
|
|
21
|
+
* counterfactual).
|
|
22
|
+
*/
|
|
23
|
+
export interface ModelPrice {
|
|
24
|
+
/** USD per 1M input tokens. */
|
|
25
|
+
inUsd: number;
|
|
26
|
+
/** USD per 1M output tokens. */
|
|
27
|
+
outUsd: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Canonical pricing table. Keys are stable short names; aliases map to the
|
|
32
|
+
* same entry so callers can pass either `sonnet-4.5` or `claude-sonnet-4.5`.
|
|
33
|
+
*/
|
|
34
|
+
export const PRICING_TABLE: Record<string, ModelPrice> = {
|
|
35
|
+
"sonnet-4.5": { inUsd: 3.0, outUsd: 15.0 },
|
|
36
|
+
"sonnet-4.6": { inUsd: 2.5, outUsd: 12.5 },
|
|
37
|
+
"opus-4": { inUsd: 15.0, outUsd: 75.0 },
|
|
38
|
+
"opus-4.7": { inUsd: 18.0, outUsd: 90.0 },
|
|
39
|
+
"haiku-4.5": { inUsd: 0.8, outUsd: 4.0 },
|
|
40
|
+
// Local / offline providers — zero inference cost.
|
|
41
|
+
"onnx": { inUsd: 0, outUsd: 0 },
|
|
42
|
+
"local": { inUsd: 0, outUsd: 0 },
|
|
43
|
+
"none": { inUsd: 0, outUsd: 0 },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Default model for pricing when no override is specified. Updated to
|
|
48
|
+
* sonnet-4.6 (the current Claude Code default as of 2026-04); sonnet-4.5
|
|
49
|
+
* is retained in the table for back-compat with pinned configs.
|
|
50
|
+
*/
|
|
51
|
+
export const DEFAULT_PRICING_MODEL = "sonnet-4.6";
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the effective model name from the caller-supplied override,
|
|
55
|
+
* `ASHLR_PRICING_MODEL`, or `CLAUDE_CODE_MODEL` (the env var Claude Code
|
|
56
|
+
* exports to describe its active model). Returns the resolved name verbatim
|
|
57
|
+
* so test harnesses can pin it; does NOT validate that the model exists in
|
|
58
|
+
* the table — that check happens in `pricing()`.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/** Track whether we've already warned about an unknown model this process. */
|
|
62
|
+
let _unknownModelWarned: string | null = null;
|
|
63
|
+
|
|
64
|
+
export function pricingModel(override?: string): string {
|
|
65
|
+
if (override && override.trim().length > 0) return override.trim();
|
|
66
|
+
const explicit = process.env.ASHLR_PRICING_MODEL;
|
|
67
|
+
if (explicit && explicit.trim().length > 0) return explicit.trim();
|
|
68
|
+
// Auto-detect from Claude Code's model env var. Strip the "claude-" prefix
|
|
69
|
+
// if present so "claude-sonnet-4.6" resolves to "sonnet-4.6".
|
|
70
|
+
const ccModel = process.env.CLAUDE_CODE_MODEL;
|
|
71
|
+
if (ccModel && ccModel.trim().length > 0) {
|
|
72
|
+
const raw = ccModel.trim();
|
|
73
|
+
const normalized = raw.replace(/^claude-/, "");
|
|
74
|
+
return normalized;
|
|
75
|
+
}
|
|
76
|
+
return DEFAULT_PRICING_MODEL;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Return the price entry for a given model (or the resolved default).
|
|
81
|
+
* Unknown models log a one-time warning to stderr and fall back to the
|
|
82
|
+
* default — pricing lookups must never break a rendering path.
|
|
83
|
+
*/
|
|
84
|
+
export function pricing(model?: string): ModelPrice {
|
|
85
|
+
const name = pricingModel(model);
|
|
86
|
+
if (PRICING_TABLE[name]) return PRICING_TABLE[name]!;
|
|
87
|
+
// Unknown model: warn once per process, then use default.
|
|
88
|
+
if (_unknownModelWarned !== name) {
|
|
89
|
+
_unknownModelWarned = name;
|
|
90
|
+
process.stderr.write(`ashlr: unknown model "${name}", defaulting to ${DEFAULT_PRICING_MODEL} pricing\n`);
|
|
91
|
+
}
|
|
92
|
+
return PRICING_TABLE[DEFAULT_PRICING_MODEL]!;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Compute the USD cost for a given input-token count at the model's input
|
|
97
|
+
* rate. Always uses input pricing because savings reporting measures tokens
|
|
98
|
+
* that would have been *sent into* the model (see module docstring).
|
|
99
|
+
*
|
|
100
|
+
* Negative / non-finite inputs clamp to 0 so downstream formatters never
|
|
101
|
+
* see NaN.
|
|
102
|
+
*/
|
|
103
|
+
export function costFor(tokens: number, model?: string): number {
|
|
104
|
+
if (!Number.isFinite(tokens) || tokens <= 0) return 0;
|
|
105
|
+
const p = pricing(model);
|
|
106
|
+
return (tokens * p.inUsd) / 1_000_000;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Compute the USD cost of an LLM summarization call.
|
|
111
|
+
*
|
|
112
|
+
* Pricing per provider:
|
|
113
|
+
* anthropic — Haiku 4.5: $0.80/MTok in, $4.00/MTok out (user's own key)
|
|
114
|
+
* cloud — ashlr hosted proxy: same Haiku 4.5 rates (Pro subscription covers it)
|
|
115
|
+
* onnx — local inference: $0 (compute already paid)
|
|
116
|
+
* local — LM Studio / Ollama: $0 (local inference)
|
|
117
|
+
* none — no call made: $0
|
|
118
|
+
*
|
|
119
|
+
* Returns exact USD cost (may be fractional; callers format as needed).
|
|
120
|
+
* Always finite; clamps bad inputs to 0.
|
|
121
|
+
*/
|
|
122
|
+
export function costForLLM(
|
|
123
|
+
provider: "anthropic" | "cloud" | "onnx" | "local" | "none",
|
|
124
|
+
inTokens: number,
|
|
125
|
+
outTokens: number,
|
|
126
|
+
): number {
|
|
127
|
+
if (provider !== "anthropic" && provider !== "cloud") return 0;
|
|
128
|
+
const safeIn = Number.isFinite(inTokens) && inTokens > 0 ? inTokens : 0;
|
|
129
|
+
const safeOut = Number.isFinite(outTokens) && outTokens > 0 ? outTokens : 0;
|
|
130
|
+
// Haiku 4.5: $0.80/MTok in, $4.00/MTok out
|
|
131
|
+
return (safeIn * 0.8 + safeOut * 4.0) / 1_000_000;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Summarizer-model detection
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Map from LLM provider name to the actual model used for summarization.
|
|
140
|
+
* Used by getActiveSummarizerModel() to return a key resolvable in PRICING_TABLE.
|
|
141
|
+
*/
|
|
142
|
+
const PROVIDER_MODEL_MAP: Record<string, string> = {
|
|
143
|
+
off: "none", // ASHLR_LLM_PROVIDER=off
|
|
144
|
+
anthropic: "haiku-4.5", // anthropic.ts hardcodes claude-haiku-4-5-20251001
|
|
145
|
+
cloud: "haiku-4.5", // hosted proxy also uses Haiku
|
|
146
|
+
onnx: "onnx", // local ONNX inference — $0
|
|
147
|
+
local: "local", // LM Studio / Ollama — $0
|
|
148
|
+
none: "none", // no summarization — $0
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Detect which summarizer model is actually active based on the
|
|
153
|
+
* ASHLR_LLM_PROVIDER env var and ASHLR_LLM_URL heuristic.
|
|
154
|
+
*
|
|
155
|
+
* Returns a short model key resolvable in PRICING_TABLE (e.g. "haiku-4.5",
|
|
156
|
+
* "onnx", "local"). Does NOT do a network availability check — purely env-based
|
|
157
|
+
* so it stays synchronous and safe to call on hot paths.
|
|
158
|
+
*
|
|
159
|
+
* Order matches the selectProvider() logic in _llm-providers/index.ts:
|
|
160
|
+
* 1. ASHLR_LLM_PROVIDER explicit override
|
|
161
|
+
* 2. ASHLR_LLM_URL set → local
|
|
162
|
+
* 3. ANTHROPIC_API_KEY or ~/.claude/.credentials.json present → anthropic
|
|
163
|
+
* 4. Pro token present → cloud
|
|
164
|
+
* 5. default → none (snipCompact fallback)
|
|
165
|
+
*/
|
|
166
|
+
export function getActiveSummarizerModel(homeOverride?: string): string {
|
|
167
|
+
const pricingOverride = process.env["ASHLR_PRICING_MODEL"];
|
|
168
|
+
// If the user explicitly set ASHLR_PRICING_MODEL, honor it as-is (power-user
|
|
169
|
+
// override path — they want to peg to a specific price regardless of actual
|
|
170
|
+
// summarizer).
|
|
171
|
+
if (pricingOverride && pricingOverride.trim().length > 0) {
|
|
172
|
+
return pricingOverride.trim();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const llmProvider = (process.env["ASHLR_LLM_PROVIDER"] ?? "auto").toLowerCase().trim();
|
|
176
|
+
const mapped = PROVIDER_MODEL_MAP[llmProvider];
|
|
177
|
+
if (mapped) return mapped;
|
|
178
|
+
|
|
179
|
+
// "auto" — mirror selectProvider heuristic without awaiting.
|
|
180
|
+
if (process.env["ASHLR_LLM_URL"]) return "local";
|
|
181
|
+
|
|
182
|
+
// Anthropic key present (either env var or Claude Code credentials file)?
|
|
183
|
+
if (process.env["ANTHROPIC_API_KEY"]) return "haiku-4.5";
|
|
184
|
+
|
|
185
|
+
// Pro token present → cloud proxy (also Haiku).
|
|
186
|
+
try {
|
|
187
|
+
const { existsSync, statSync } = require("fs") as typeof import("fs");
|
|
188
|
+
const { homedir } = require("os") as typeof import("os");
|
|
189
|
+
const { join } = require("path") as typeof import("path");
|
|
190
|
+
const home = homeOverride ?? process.env["HOME"] ?? homedir();
|
|
191
|
+
const tokenPath = join(home, ".ashlr", "pro-token");
|
|
192
|
+
if (existsSync(tokenPath) && statSync(tokenPath).size > 0) {
|
|
193
|
+
return "haiku-4.5";
|
|
194
|
+
}
|
|
195
|
+
} catch {
|
|
196
|
+
/* best-effort — fall through to none */
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return "none";
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Return the input price (USD per million tokens) for the active summarizer
|
|
204
|
+
* model. Suitable for correcting the `$ saved` display to reflect actual
|
|
205
|
+
* inference costs rather than defaulting to Sonnet rates.
|
|
206
|
+
*
|
|
207
|
+
* Falls back to `pricing(override)` when a manual ASHLR_PRICING_MODEL override
|
|
208
|
+
* is set — preserving backward compat for power users who want Sonnet
|
|
209
|
+
* counterfactual pricing.
|
|
210
|
+
*/
|
|
211
|
+
export function getInputPriceForModel(model: string): number {
|
|
212
|
+
const entry = PRICING_TABLE[model];
|
|
213
|
+
if (entry) return entry.inUsd;
|
|
214
|
+
// Unknown model: fall back to default pricing table entry.
|
|
215
|
+
return PRICING_TABLE[DEFAULT_PRICING_MODEL]!.inUsd;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* costFor() using the detected summarizer model rather than the main LLM model.
|
|
220
|
+
*
|
|
221
|
+
* Use this for `$ saved` displays where the counterfactual is "what would it
|
|
222
|
+
* have cost to send these tokens through the summarizer model" — not through
|
|
223
|
+
* the user's primary model (Sonnet/Opus). The summarizer is typically Haiku,
|
|
224
|
+
* which is ~3-4x cheaper than Sonnet.
|
|
225
|
+
*
|
|
226
|
+
* Design intent — DO NOT "fix" the main-model fallback back to $0:
|
|
227
|
+
* This function exists specifically to avoid overclaiming against Sonnet
|
|
228
|
+
* rates when Haiku is the real summarizer (v1.x change). But when the
|
|
229
|
+
* auto-resolver lands on a zero-priced model ("none", "onnx", "local"),
|
|
230
|
+
* there IS no summarizer — the user is on the free-tier first-touch path
|
|
231
|
+
* (no Anthropic key, no Pro token, no local LLM URL). In that case the
|
|
232
|
+
* tokens we saved would have hit the MAIN model, so main-model pricing
|
|
233
|
+
* is the correct counterfactual. Showing $0 to those users is the bug
|
|
234
|
+
* (status-line "≈$0.00"); falling back to costFor() shows real savings.
|
|
235
|
+
*
|
|
236
|
+
* Power-user override: when `ASHLR_PRICING_MODEL` is explicitly set we honor
|
|
237
|
+
* it verbatim — even if it resolves to a $0-priced model. They asked to peg
|
|
238
|
+
* to that price and we respect it.
|
|
239
|
+
*/
|
|
240
|
+
export function costForSummarizer(tokens: number, homeOverride?: string): number {
|
|
241
|
+
if (!Number.isFinite(tokens) || tokens <= 0) return 0;
|
|
242
|
+
const model = getActiveSummarizerModel(homeOverride);
|
|
243
|
+
const pricePerM = getInputPriceForModel(model);
|
|
244
|
+
// If the resolved model carries a $0 price AND the user did NOT explicitly
|
|
245
|
+
// pin via ASHLR_PRICING_MODEL, fall back to main-model pricing. This is the
|
|
246
|
+
// free-tier first-touch case where no summarizer is configured — the
|
|
247
|
+
// counterfactual is the main model, not "free".
|
|
248
|
+
const explicitPricingOverride = process.env["ASHLR_PRICING_MODEL"];
|
|
249
|
+
const hasExplicitOverride =
|
|
250
|
+
explicitPricingOverride !== undefined &&
|
|
251
|
+
explicitPricingOverride.trim().length > 0;
|
|
252
|
+
if (pricePerM === 0 && !hasExplicitOverride) {
|
|
253
|
+
return costFor(tokens);
|
|
254
|
+
}
|
|
255
|
+
return (tokens * pricePerM) / 1_000_000;
|
|
256
|
+
}
|