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,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _weekly-digest.ts — Once-per-week Monday banner with relatable comparisons.
|
|
3
|
+
*
|
|
4
|
+
* Fires on the first Monday session of the week. Tracks in:
|
|
5
|
+
* ~/.ashlr/weekly-digest.json: { lastShownWeek: "YYYY-WW" }
|
|
6
|
+
*
|
|
7
|
+
* Opt-out via:
|
|
8
|
+
* ASHLR_DISABLE_DIGEST=1 (env var)
|
|
9
|
+
* ~/.ashlr/config.json :: { "digest": "off" }
|
|
10
|
+
*
|
|
11
|
+
* Banner format (plain/no-color):
|
|
12
|
+
* Last week: 12.4M tokens saved · ~$37.20
|
|
13
|
+
* That's like 9 cups of coffee, or 2 Spotify months.
|
|
14
|
+
* Streak: 5d · top tool: ashlr__grep (43% of savings)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
18
|
+
import { homedir } from "os";
|
|
19
|
+
import { join } from "path";
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Types
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
export interface DigestState {
|
|
26
|
+
lastShownWeek: string; // "YYYY-WW" — ISO week key
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Comparison bank
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
interface Comparison {
|
|
34
|
+
/** Minimum dollar value this item represents. */
|
|
35
|
+
minDollars: number;
|
|
36
|
+
/** Display label when quantity > 1 (e.g. "cups of coffee"). */
|
|
37
|
+
plural: string;
|
|
38
|
+
/** Display label when quantity === 1 (e.g. "cup of coffee"). */
|
|
39
|
+
singular: string;
|
|
40
|
+
/** Dollar value of one unit. */
|
|
41
|
+
unitCost: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Ordered ascending by unitCost. The selector picks the largest item that
|
|
45
|
+
// yields quantity >= 1 and rounds cleanly, then multiplies.
|
|
46
|
+
export const COMPARISONS: readonly Comparison[] = [
|
|
47
|
+
{ minDollars: 0, plural: "pencils", singular: "pencil", unitCost: 1 },
|
|
48
|
+
{ minDollars: 0, plural: "song downloads", singular: "song download", unitCost: 1.29 },
|
|
49
|
+
{ minDollars: 0, plural: "minutes of GitHub Actions", singular: "minute of GitHub Actions", unitCost: 0.008 },
|
|
50
|
+
{ minDollars: 3, plural: "cups of coffee", singular: "cup of coffee", unitCost: 4 },
|
|
51
|
+
{ minDollars: 3, plural: "sandwiches", singular: "sandwich", unitCost: 5 },
|
|
52
|
+
{ minDollars: 8, plural: "Spotify months", singular: "Spotify month", unitCost: 10 },
|
|
53
|
+
{ minDollars: 8, plural: "paperbacks", singular: "paperback", unitCost: 12 },
|
|
54
|
+
{ minDollars: 40, plural: "dinners out", singular: "dinner out", unitCost: 50 },
|
|
55
|
+
{ minDollars: 80, plural: "moderate side-gig hours", singular: "moderate side-gig hour", unitCost: 100 },
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Build a relatable comparison string for the given dollar amount.
|
|
60
|
+
*
|
|
61
|
+
* Algorithm:
|
|
62
|
+
* 1. Filter items whose minDollars <= dollars and unitCost <= dollars.
|
|
63
|
+
* 2. Among those, prefer items where qty = floor(dollars/unitCost) is in
|
|
64
|
+
* the "sweet spot" range [2, 15] — these make the most relatable strings
|
|
65
|
+
* ("9 cups of coffee" beats "3 paperbacks" or "0.5 dinners").
|
|
66
|
+
* 3. Within the sweet-spot candidates, pick the one with the highest unit
|
|
67
|
+
* cost (largest denomination that still gives a readable count).
|
|
68
|
+
* 4. Fall back to the largest qualifying item when no sweet-spot candidate
|
|
69
|
+
* exists (e.g. very large dollar amounts).
|
|
70
|
+
*
|
|
71
|
+
* Returns "" when dollars < 0.01 (nothing to compare).
|
|
72
|
+
*/
|
|
73
|
+
export function buildComparison(dollars: number): string {
|
|
74
|
+
if (!Number.isFinite(dollars) || dollars < 0.01) return "";
|
|
75
|
+
|
|
76
|
+
// Collect all eligible items (minDollars <= dollars, unitCost <= dollars).
|
|
77
|
+
const eligible = COMPARISONS.filter(
|
|
78
|
+
(item) => dollars >= item.minDollars && dollars >= item.unitCost,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (eligible.length === 0) {
|
|
82
|
+
// Degenerate: use smallest item regardless.
|
|
83
|
+
const item = COMPARISONS[0]!;
|
|
84
|
+
const qty = Math.floor(dollars / item.unitCost);
|
|
85
|
+
if (qty <= 0) return "";
|
|
86
|
+
return `${qty} ${qty === 1 ? item.singular : item.plural}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Target quantity: the "ideal" count that makes a comparison feel vivid.
|
|
90
|
+
// Quantities near 5-10 are most relatable ("9 cups of coffee" > "3 paperbacks").
|
|
91
|
+
// Algorithm: among eligible items, score each by how close its quantity is to
|
|
92
|
+
// TARGET_QTY (lower distance = better). Break ties by higher unitCost so we
|
|
93
|
+
// prefer recognizable real-world items over cheap ones.
|
|
94
|
+
const TARGET_QTY = 8;
|
|
95
|
+
|
|
96
|
+
// Compute qty for each eligible item.
|
|
97
|
+
const withQty = eligible.map((item) => ({
|
|
98
|
+
item,
|
|
99
|
+
qty: Math.floor(dollars / item.unitCost),
|
|
100
|
+
})).filter(({ qty }) => qty >= 1); // at least 1
|
|
101
|
+
|
|
102
|
+
let best: Comparison;
|
|
103
|
+
if (withQty.length === 0) {
|
|
104
|
+
best = eligible[0]!;
|
|
105
|
+
} else {
|
|
106
|
+
best = withQty.reduce((a, b) => {
|
|
107
|
+
const distA = Math.abs(a.qty - TARGET_QTY);
|
|
108
|
+
const distB = Math.abs(b.qty - TARGET_QTY);
|
|
109
|
+
if (distA !== distB) return distA < distB ? a : b;
|
|
110
|
+
// Tie-break: higher unit cost wins (more recognizable denomination).
|
|
111
|
+
return b.item.unitCost > a.item.unitCost ? b : a;
|
|
112
|
+
}).item;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const qty = Math.floor(dollars / best.unitCost);
|
|
116
|
+
if (qty <= 0) return "";
|
|
117
|
+
const label = qty === 1 ? best.singular : best.plural;
|
|
118
|
+
return `${qty} ${label}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// ISO week helper
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Returns "YYYY-WW" for the ISO week containing `date`.
|
|
127
|
+
* Week 1 = the week containing the first Thursday of the year.
|
|
128
|
+
*/
|
|
129
|
+
export function isoWeekKey(date: Date = new Date()): string {
|
|
130
|
+
// Copy and move to Thursday of the same week to determine the year.
|
|
131
|
+
const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
|
|
132
|
+
// Day of week: 0=Sun, 1=Mon … 6=Sat. ISO: Mon=1..Sun=7.
|
|
133
|
+
const dow = d.getUTCDay() || 7; // convert Sun(0) → 7
|
|
134
|
+
d.setUTCDate(d.getUTCDate() + 4 - dow); // move to Thursday
|
|
135
|
+
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
|
136
|
+
const weekNum = Math.ceil(((d.getTime() - yearStart.getTime()) / 86_400_000 + 1) / 7);
|
|
137
|
+
return `${d.getUTCFullYear()}-${String(weekNum).padStart(2, "0")}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Returns the ISO week key for the week prior to `date`.
|
|
142
|
+
*/
|
|
143
|
+
export function priorWeekKey(date: Date = new Date()): string {
|
|
144
|
+
const d = new Date(date);
|
|
145
|
+
d.setUTCDate(d.getUTCDate() - 7);
|
|
146
|
+
return isoWeekKey(d);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Returns the YYYY-MM-DD keys for the 7 days of the ISO week identified by
|
|
151
|
+
* `weekKey` (Mon..Sun).
|
|
152
|
+
*/
|
|
153
|
+
export function weekDayKeys(weekKey: string): string[] {
|
|
154
|
+
const [yearStr, weekStr] = weekKey.split("-");
|
|
155
|
+
const year = parseInt(yearStr!, 10);
|
|
156
|
+
const week = parseInt(weekStr!, 10);
|
|
157
|
+
// Jan 4 is always in week 1.
|
|
158
|
+
const jan4 = new Date(Date.UTC(year, 0, 4));
|
|
159
|
+
const dow4 = jan4.getUTCDay() || 7;
|
|
160
|
+
// Monday of week 1:
|
|
161
|
+
const monday1 = new Date(jan4);
|
|
162
|
+
monday1.setUTCDate(monday1.getUTCDate() - (dow4 - 1));
|
|
163
|
+
// Monday of our target week:
|
|
164
|
+
const targetMonday = new Date(monday1);
|
|
165
|
+
targetMonday.setUTCDate(targetMonday.getUTCDate() + (week - 1) * 7);
|
|
166
|
+
const keys: string[] = [];
|
|
167
|
+
for (let i = 0; i < 7; i++) {
|
|
168
|
+
const d = new Date(targetMonday);
|
|
169
|
+
d.setUTCDate(d.getUTCDate() + i);
|
|
170
|
+
keys.push(d.toISOString().slice(0, 10));
|
|
171
|
+
}
|
|
172
|
+
return keys;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Paths
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
export function digestStatePath(home: string = homedir()): string {
|
|
180
|
+
return join(home, ".ashlr", "weekly-digest.json");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// IO helpers
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
export function readDigestState(home: string = homedir()): DigestState {
|
|
188
|
+
try {
|
|
189
|
+
const p = digestStatePath(home);
|
|
190
|
+
if (!existsSync(p)) return { lastShownWeek: "" };
|
|
191
|
+
const raw = JSON.parse(readFileSync(p, "utf-8")) as Partial<DigestState>;
|
|
192
|
+
return {
|
|
193
|
+
lastShownWeek: typeof raw.lastShownWeek === "string" ? raw.lastShownWeek : "",
|
|
194
|
+
};
|
|
195
|
+
} catch {
|
|
196
|
+
return { lastShownWeek: "" };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function writeDigestState(data: DigestState, home: string = homedir()): void {
|
|
201
|
+
try {
|
|
202
|
+
mkdirSync(join(home, ".ashlr"), { recursive: true });
|
|
203
|
+
writeFileSync(digestStatePath(home), JSON.stringify(data, null, 2));
|
|
204
|
+
} catch {
|
|
205
|
+
/* best-effort */
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Opt-out helpers
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
export function isDigestDisabled(home: string = homedir()): boolean {
|
|
214
|
+
// Env var opt-out.
|
|
215
|
+
if (process.env.ASHLR_DISABLE_DIGEST === "1") return true;
|
|
216
|
+
// Config file opt-out.
|
|
217
|
+
try {
|
|
218
|
+
const cfgPath = join(home, ".ashlr", "config.json");
|
|
219
|
+
if (!existsSync(cfgPath)) return false;
|
|
220
|
+
const raw = JSON.parse(readFileSync(cfgPath, "utf-8")) as Record<string, unknown>;
|
|
221
|
+
return raw.digest === "off";
|
|
222
|
+
} catch {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
// Stats types (minimal — we only need byDay + byTool)
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
export interface DigestStats {
|
|
232
|
+
lifetime?: {
|
|
233
|
+
byDay?: Record<string, { tokensSaved?: number; calls?: number }>;
|
|
234
|
+
byTool?: Record<string, { tokensSaved?: number; calls?: number }>;
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// Banner building
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
export interface DigestBannerOpts {
|
|
243
|
+
home?: string;
|
|
244
|
+
now?: Date;
|
|
245
|
+
stats?: DigestStats;
|
|
246
|
+
/** Dollars saved (injected by caller; computed from costFor). */
|
|
247
|
+
dollarsSaved?: number;
|
|
248
|
+
/** Current streak length (from _streaks.ts). */
|
|
249
|
+
currentStreak?: number;
|
|
250
|
+
/**
|
|
251
|
+
* Test hook: when true, marks the digest as shown in state but skips the
|
|
252
|
+
* actual write so tests stay isolated.
|
|
253
|
+
*/
|
|
254
|
+
suppressStateWrite?: boolean;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export interface DigestBannerResult {
|
|
258
|
+
/** The rendered banner text, or null if the digest should not fire. */
|
|
259
|
+
banner: string | null;
|
|
260
|
+
/** Whether this is the first time we're showing this week's digest. */
|
|
261
|
+
fired: boolean;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface DigestBannerContent {
|
|
265
|
+
tokens: number;
|
|
266
|
+
dollars: number;
|
|
267
|
+
comparisonA: string;
|
|
268
|
+
comparisonB: string;
|
|
269
|
+
currentStreak: number;
|
|
270
|
+
topTool: string;
|
|
271
|
+
topToolPct: number;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Build the banner content fields without any IO side-effects.
|
|
276
|
+
* Pure function — used by tests and by buildDigestBanner.
|
|
277
|
+
*/
|
|
278
|
+
export function computeBannerContent(
|
|
279
|
+
opts: Required<Pick<DigestBannerOpts, "now" | "stats" | "dollarsSaved" | "currentStreak">>,
|
|
280
|
+
): DigestBannerContent {
|
|
281
|
+
const { now, stats, dollarsSaved, currentStreak } = opts;
|
|
282
|
+
const prior = priorWeekKey(now);
|
|
283
|
+
const dayKeys = weekDayKeys(prior);
|
|
284
|
+
const byDay = stats.lifetime?.byDay ?? {};
|
|
285
|
+
const tokens = dayKeys.reduce((sum, k) => sum + (byDay[k]?.tokensSaved ?? 0), 0);
|
|
286
|
+
const dollars = dollarsSaved ?? 0;
|
|
287
|
+
|
|
288
|
+
// Two independent comparisons for variety.
|
|
289
|
+
const compA = buildComparison(dollars);
|
|
290
|
+
// For the second comparison pick the next-size-up item if possible.
|
|
291
|
+
let compB = "";
|
|
292
|
+
const aIdx = COMPARISONS.findIndex((c) => compA.includes(c.plural) || compA.includes(c.singular));
|
|
293
|
+
if (aIdx > 0) {
|
|
294
|
+
const alt = COMPARISONS[aIdx - 1];
|
|
295
|
+
if (alt && dollars >= alt.unitCost) {
|
|
296
|
+
const qty = Math.floor(dollars / alt.unitCost);
|
|
297
|
+
compB = qty > 0 ? `${qty} ${qty === 1 ? alt.singular : alt.plural}` : "";
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Top tool by tokens saved last week.
|
|
302
|
+
const byTool = stats.lifetime?.byTool ?? {};
|
|
303
|
+
let topTool = "";
|
|
304
|
+
let topTok = 0;
|
|
305
|
+
for (const [name, t] of Object.entries(byTool)) {
|
|
306
|
+
const tok = t?.tokensSaved ?? 0;
|
|
307
|
+
if (tok > topTok) { topTok = tok; topTool = name; }
|
|
308
|
+
}
|
|
309
|
+
const topToolPct = tokens > 0 && topTok > 0 ? Math.round((topTok / tokens) * 100) : 0;
|
|
310
|
+
|
|
311
|
+
return { tokens, dollars, comparisonA: compA, comparisonB: compB, currentStreak, topTool, topToolPct };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Render the digest banner as a plain-text string (no ANSI — the session-greet
|
|
316
|
+
* layer will apply its own color if appropriate).
|
|
317
|
+
*/
|
|
318
|
+
export function renderDigestBanner(content: DigestBannerContent): string {
|
|
319
|
+
const { tokens, dollars, comparisonA, comparisonB, currentStreak, topTool, topToolPct } = content;
|
|
320
|
+
|
|
321
|
+
function fmtTokens(n: number): string {
|
|
322
|
+
if (n < 1000) return String(n);
|
|
323
|
+
if (n < 1_000_000) return (n / 1000).toFixed(1) + "K";
|
|
324
|
+
return (n / 1_000_000).toFixed(1) + "M";
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const dollarStr = dollars < 0.01 ? "~$0.00" : dollars < 100
|
|
328
|
+
? `~$${dollars.toFixed(2)}`
|
|
329
|
+
: `~$${Math.round(dollars)}`;
|
|
330
|
+
|
|
331
|
+
const lines: string[] = [];
|
|
332
|
+
lines.push(`Last week: ${fmtTokens(tokens)} tokens saved · ${dollarStr}`);
|
|
333
|
+
|
|
334
|
+
const comparisons: string[] = [];
|
|
335
|
+
if (comparisonA) comparisons.push(comparisonA);
|
|
336
|
+
if (comparisonB && comparisonB !== comparisonA) comparisons.push(comparisonB);
|
|
337
|
+
if (comparisons.length > 0) {
|
|
338
|
+
lines.push(`That's like ${comparisons.join(", or ")}.`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const meta: string[] = [];
|
|
342
|
+
if (currentStreak >= 3) meta.push(`${currentStreak}d streak`);
|
|
343
|
+
if (topTool && topToolPct > 0) meta.push(`top tool: ${topTool} (${topToolPct}% of savings)`);
|
|
344
|
+
if (meta.length > 0) {
|
|
345
|
+
lines.push(meta.join(" · "));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return lines.join("\n");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Main entry point: check if a weekly digest should fire, build + return the
|
|
353
|
+
* banner text, and mark the week as shown.
|
|
354
|
+
*
|
|
355
|
+
* Returns null when:
|
|
356
|
+
* - digest is disabled (env/config opt-out)
|
|
357
|
+
* - already shown this week
|
|
358
|
+
* - total tokens == 0 (nothing happened last week)
|
|
359
|
+
*/
|
|
360
|
+
export function buildDigestBanner(opts: DigestBannerOpts = {}): DigestBannerResult {
|
|
361
|
+
const home = opts.home ?? homedir();
|
|
362
|
+
const now = opts.now ?? new Date();
|
|
363
|
+
|
|
364
|
+
if (isDigestDisabled(home)) return { banner: null, fired: false };
|
|
365
|
+
|
|
366
|
+
const thisWeek = isoWeekKey(now);
|
|
367
|
+
const state = readDigestState(home);
|
|
368
|
+
if (state.lastShownWeek === thisWeek) return { banner: null, fired: false };
|
|
369
|
+
|
|
370
|
+
const stats = opts.stats ?? ({} as DigestStats);
|
|
371
|
+
const dollars = opts.dollarsSaved ?? 0;
|
|
372
|
+
|
|
373
|
+
const content = computeBannerContent({
|
|
374
|
+
now,
|
|
375
|
+
stats,
|
|
376
|
+
dollarsSaved: dollars,
|
|
377
|
+
currentStreak: opts.currentStreak ?? 0,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Suppress empty banners.
|
|
381
|
+
if (content.tokens <= 0) return { banner: null, fired: false };
|
|
382
|
+
|
|
383
|
+
const banner = renderDigestBanner(content);
|
|
384
|
+
|
|
385
|
+
// Mark as shown.
|
|
386
|
+
if (!opts.suppressStateWrite) {
|
|
387
|
+
writeDigestState({ lastShownWeek: thisWeek }, home);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return { banner, fired: true };
|
|
391
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ask-server-handlers — registers ashlr__ask on the shared registry.
|
|
3
|
+
*
|
|
4
|
+
* Routing decisions are delegated to _ask-router.ts (pure, no tool imports).
|
|
5
|
+
* Dispatch today still calls the underlying tool functions directly; once
|
|
6
|
+
* every downstream server (efficiency, orient, tree, glob) lives on the
|
|
7
|
+
* registry, the dispatch can switch to `getTool(...).handler(...)` and drop
|
|
8
|
+
* those imports entirely. Until then, this module imports ask-server's
|
|
9
|
+
* existing `askHandler` which does the direct calls.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { registerTool, toErrorResult, type ToolCallContext, type ToolResult } from "./_tool-base";
|
|
13
|
+
import { askHandler } from "./ask-server";
|
|
14
|
+
|
|
15
|
+
registerTool({
|
|
16
|
+
name: "ashlr__ask",
|
|
17
|
+
description:
|
|
18
|
+
"Single-tool entry point for ashlr. Accepts a natural-language question and " +
|
|
19
|
+
"auto-routes to the correct underlying tool (ashlr__read, ashlr__grep, " +
|
|
20
|
+
"ashlr__orient, ashlr__tree, or ashlr__glob) using deterministic rules — no " +
|
|
21
|
+
"LLM in the routing step. Output always starts with a one-line trace showing " +
|
|
22
|
+
"which tool fired and why.",
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
question: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "Natural-language question about the codebase",
|
|
29
|
+
},
|
|
30
|
+
cwd: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Working directory context (default: process.cwd())",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
required: ["question"],
|
|
36
|
+
},
|
|
37
|
+
handler: async (args: Record<string, unknown>, _ctx: ToolCallContext): Promise<ToolResult> => {
|
|
38
|
+
const a = (args ?? {}) as { question?: string; cwd?: string };
|
|
39
|
+
try {
|
|
40
|
+
const text = await askHandler({
|
|
41
|
+
question: typeof a.question === "string" ? a.question : "",
|
|
42
|
+
cwd: typeof a.cwd === "string" ? a.cwd : undefined,
|
|
43
|
+
});
|
|
44
|
+
return { content: [{ type: "text", text }] };
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return toErrorResult("ashlr__ask error", err);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* ashlr-ask MCP server.
|
|
4
|
+
*
|
|
5
|
+
* Exposes a single tool (ashlr__ask) that accepts a natural-language question
|
|
6
|
+
* and routes it to the correct underlying ashlr tool via deterministic rules —
|
|
7
|
+
* no LLM involved in routing.
|
|
8
|
+
*
|
|
9
|
+
* Routing table (first match wins):
|
|
10
|
+
* 1. glob token (e.g. **\/*.ts) → ashlr__glob
|
|
11
|
+
* 2. read/show-me/file + path token → ashlr__read
|
|
12
|
+
* 3. grep/find/search/where-is/which → ashlr__grep
|
|
13
|
+
* 4. structural (how does/explain/why) → ashlr__orient
|
|
14
|
+
* 5. list/tree/structure/directory → ashlr__tree
|
|
15
|
+
* fallback → ashlr__orient
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
19
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
20
|
+
import {
|
|
21
|
+
CallToolRequestSchema,
|
|
22
|
+
ListToolsRequestSchema,
|
|
23
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
24
|
+
|
|
25
|
+
// Register the four tools askHandler dispatches to. Import only the specific
|
|
26
|
+
// handler modules — not _router-handlers — to avoid the circular dep chain:
|
|
27
|
+
// _router-handlers → ask-server-handlers → ask-server → _router-handlers.
|
|
28
|
+
// v1.21 Track B: efficiency-server decomposed; import 5 per-tool handlers.
|
|
29
|
+
import "./read-server-handlers";
|
|
30
|
+
import "./grep-server-handlers";
|
|
31
|
+
import "./edit-server-handlers";
|
|
32
|
+
import "./flush-server-handlers";
|
|
33
|
+
import "./savings-server-handlers";
|
|
34
|
+
import "./orient-server-handlers";
|
|
35
|
+
import "./tree-server-handlers";
|
|
36
|
+
import "./glob-server-handlers";
|
|
37
|
+
import { logEvent } from "./_events";
|
|
38
|
+
import { getTool } from "./_tool-base";
|
|
39
|
+
import { extractKeywords } from "./_text-helpers";
|
|
40
|
+
import { routeQuestion, type RouteDecision, type RoutedTool } from "./_ask-router";
|
|
41
|
+
|
|
42
|
+
// Re-export for callers that historically imported these from ask-server.
|
|
43
|
+
export { routeQuestion, extractKeywords };
|
|
44
|
+
export type { RouteDecision, RoutedTool };
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Dispatch
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
export async function askHandler(input: { question: string; cwd?: string }): Promise<string> {
|
|
51
|
+
const { question, cwd } = input;
|
|
52
|
+
const decision = routeQuestion(question);
|
|
53
|
+
|
|
54
|
+
// Log the routing decision for /ashlr-usage analytics.
|
|
55
|
+
await logEvent("tool_call", {
|
|
56
|
+
tool: "ashlr__ask",
|
|
57
|
+
reason: `routed-to=${decision.tool}`,
|
|
58
|
+
extra: { routeReason: decision.reason, extracted: decision.extracted ?? null },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const trace = `[ashlr__ask] routed to ${decision.tool} (${decision.reason})`;
|
|
62
|
+
|
|
63
|
+
const ctx = {
|
|
64
|
+
sessionId: process.env.CLAUDE_SESSION_ID || process.env.ASHLR_SESSION_ID || undefined,
|
|
65
|
+
env: process.env,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
let result: string;
|
|
69
|
+
switch (decision.tool) {
|
|
70
|
+
case "ashlr__read": {
|
|
71
|
+
const toolH = getTool("ashlr__read");
|
|
72
|
+
if (!toolH) throw new Error("ashlr__read not registered");
|
|
73
|
+
const r = await toolH.handler({ path: decision.extracted ?? question }, ctx);
|
|
74
|
+
result = r.content.map((c) => c.text).join("\n");
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case "ashlr__grep": {
|
|
78
|
+
const toolH = getTool("ashlr__grep");
|
|
79
|
+
if (!toolH) throw new Error("ashlr__grep not registered");
|
|
80
|
+
const r = await toolH.handler({ pattern: decision.extracted ?? question, cwd }, ctx);
|
|
81
|
+
result = r.content.map((c) => c.text).join("\n");
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case "ashlr__orient": {
|
|
85
|
+
const toolH = getTool("ashlr__orient");
|
|
86
|
+
if (!toolH) throw new Error("ashlr__orient not registered");
|
|
87
|
+
const r = await toolH.handler({ query: question, dir: cwd }, ctx);
|
|
88
|
+
result = r.content.map((c) => c.text).join("\n");
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case "ashlr__tree": {
|
|
92
|
+
const toolH = getTool("ashlr__tree");
|
|
93
|
+
if (!toolH) throw new Error("ashlr__tree not registered");
|
|
94
|
+
const r = await toolH.handler({ path: cwd }, ctx);
|
|
95
|
+
result = r.content.map((c) => c.text).join("\n");
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case "ashlr__glob": {
|
|
99
|
+
const toolH = getTool("ashlr__glob");
|
|
100
|
+
if (!toolH) throw new Error("ashlr__glob not registered");
|
|
101
|
+
const r = await toolH.handler({ pattern: decision.extracted ?? question, cwd }, ctx);
|
|
102
|
+
result = r.content.map((c) => c.text).join("\n");
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
default: {
|
|
106
|
+
const toolH = getTool("ashlr__orient");
|
|
107
|
+
if (!toolH) throw new Error("ashlr__orient not registered");
|
|
108
|
+
const r = await toolH.handler({ query: question, dir: cwd }, ctx);
|
|
109
|
+
result = r.content.map((c) => c.text).join("\n");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return `${trace}\n${result}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// MCP server wiring
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
const server = new Server(
|
|
121
|
+
{ name: "ashlr-ask", version: "0.1.0" },
|
|
122
|
+
{ capabilities: { tools: {} } },
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
126
|
+
tools: [
|
|
127
|
+
{
|
|
128
|
+
name: "ashlr__ask",
|
|
129
|
+
description:
|
|
130
|
+
"Single-tool entry point for ashlr. Accepts a natural-language question and " +
|
|
131
|
+
"auto-routes to the correct underlying tool (ashlr__read, ashlr__grep, " +
|
|
132
|
+
"ashlr__orient, ashlr__tree, or ashlr__glob) using deterministic rules — no " +
|
|
133
|
+
"LLM in the routing step. Output always starts with a one-line trace showing " +
|
|
134
|
+
"which tool fired and why.",
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties: {
|
|
138
|
+
question: {
|
|
139
|
+
type: "string",
|
|
140
|
+
description: "Natural-language question about the codebase",
|
|
141
|
+
},
|
|
142
|
+
cwd: {
|
|
143
|
+
type: "string",
|
|
144
|
+
description: "Working directory context (default: process.cwd())",
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
required: ["question"],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
}));
|
|
152
|
+
|
|
153
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
154
|
+
const { name, arguments: args } = req.params;
|
|
155
|
+
try {
|
|
156
|
+
if (name !== "ashlr__ask") {
|
|
157
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
158
|
+
}
|
|
159
|
+
const a = (args ?? {}) as { question?: string; cwd?: string };
|
|
160
|
+
const text = await askHandler({
|
|
161
|
+
question: typeof a.question === "string" ? a.question : "",
|
|
162
|
+
cwd: typeof a.cwd === "string" ? a.cwd : undefined,
|
|
163
|
+
});
|
|
164
|
+
return { content: [{ type: "text", text }] };
|
|
165
|
+
} catch (err) {
|
|
166
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
167
|
+
return { content: [{ type: "text", text: `ashlr__ask error: ${message}` }], isError: true };
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (import.meta.main) {
|
|
172
|
+
const transport = new StdioServerTransport();
|
|
173
|
+
await server.connect(transport);
|
|
174
|
+
}
|