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,640 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _stats-sqlite.ts — SQLite-backed stats store.
|
|
3
|
+
*
|
|
4
|
+
* Drop-in replacement for the recordSaving / readStats / readCurrentSession
|
|
5
|
+
* surface in _stats.ts, backed by ~/.ashlr/stats.db instead of the
|
|
6
|
+
* tempfile+lockfile+JSON path that has been the source of 6 distinct
|
|
7
|
+
* regressions across v0.9.x → v1.0.x.
|
|
8
|
+
*
|
|
9
|
+
* Why SQLite:
|
|
10
|
+
* - bun:sqlite is already an accepted dep (see servers/_embedding-cache.ts).
|
|
11
|
+
* - WAL mode gives us multi-process concurrency for free — N terminals
|
|
12
|
+
* running the MCP server simultaneously no longer need a userland
|
|
13
|
+
* lockfile dance or cross-process mutex.
|
|
14
|
+
* - Each recordSaving becomes ONE transaction that touches 5 rows —
|
|
15
|
+
* atomic by construction, which kills the class of races the JSON
|
|
16
|
+
* path has been patching repeatedly.
|
|
17
|
+
*
|
|
18
|
+
* Schema version: 1 (initial). Consumers read `readStats()` and get the
|
|
19
|
+
* same StatsFile shape the JSON path emits, so callers don't change.
|
|
20
|
+
*
|
|
21
|
+
* Toggle: ASHLR_STATS_BACKEND=json falls back to the legacy _stats.ts.
|
|
22
|
+
* This module ignores that env var — the facade lives in the module that
|
|
23
|
+
* chooses between backends (W-C4).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { Database } from "bun:sqlite";
|
|
27
|
+
import { mkdirSync } from "fs";
|
|
28
|
+
import { homedir } from "os";
|
|
29
|
+
import { dirname, join } from "path";
|
|
30
|
+
|
|
31
|
+
import type {
|
|
32
|
+
ByDay,
|
|
33
|
+
ByTool,
|
|
34
|
+
LifetimeBucket,
|
|
35
|
+
PerTool,
|
|
36
|
+
SessionBucket,
|
|
37
|
+
StatsFile,
|
|
38
|
+
SummarizationStats,
|
|
39
|
+
} from "./_stats";
|
|
40
|
+
import { readSessionHint } from "./_stats";
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Paths + session id (kept in sync with _stats.ts so both backends can
|
|
44
|
+
// be safely switched at runtime without drift).
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
function home(): string { return process.env.HOME ?? homedir(); }
|
|
48
|
+
/**
|
|
49
|
+
* Path to ~/.ashlr/stats.db. Tests can override via ASHLR_STATS_DB_PATH
|
|
50
|
+
* without touching the module — the env var is re-read on every open so
|
|
51
|
+
* _resetConnection() + env swap is enough for isolation.
|
|
52
|
+
*/
|
|
53
|
+
export function dbPath(): string {
|
|
54
|
+
const override = process.env.ASHLR_STATS_DB_PATH;
|
|
55
|
+
return override && override.length > 0 ? override : join(home(), ".ashlr", "stats.db");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function ppidSessionId(): string {
|
|
59
|
+
const seed = `ppid:${typeof process.ppid === "number" ? process.ppid : "?"}:${process.env.HOME ?? ""}`;
|
|
60
|
+
let h = 0;
|
|
61
|
+
for (let i = 0; i < seed.length; i++) h = ((h << 5) - h + seed.charCodeAt(i)) | 0;
|
|
62
|
+
return `p${(h >>> 0).toString(16)}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function currentSessionId(): string {
|
|
66
|
+
const explicit = process.env.CLAUDE_SESSION_ID;
|
|
67
|
+
if (explicit && explicit.trim().length > 0) return explicit.trim();
|
|
68
|
+
const hint = readSessionHint();
|
|
69
|
+
if (hint) return hint;
|
|
70
|
+
return ppidSessionId();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function candidateSessionIds(): string[] {
|
|
74
|
+
const ids = new Set<string>();
|
|
75
|
+
const explicit = process.env.CLAUDE_SESSION_ID;
|
|
76
|
+
if (explicit && explicit.trim().length > 0) ids.add(explicit.trim());
|
|
77
|
+
const hint = readSessionHint();
|
|
78
|
+
if (hint) ids.add(hint);
|
|
79
|
+
ids.add(ppidSessionId());
|
|
80
|
+
return [...ids];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function todayKey(): string {
|
|
84
|
+
return new Date().toISOString().slice(0, 10);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Schema
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
const SCHEMA_VERSION = 1;
|
|
92
|
+
|
|
93
|
+
const DDL_V1 = `
|
|
94
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
95
|
+
key TEXT PRIMARY KEY,
|
|
96
|
+
value TEXT NOT NULL
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
100
|
+
session_id TEXT PRIMARY KEY,
|
|
101
|
+
started_at TEXT NOT NULL,
|
|
102
|
+
last_saving_at TEXT,
|
|
103
|
+
calls INTEGER NOT NULL DEFAULT 0,
|
|
104
|
+
tokens_saved INTEGER NOT NULL DEFAULT 0
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
CREATE TABLE IF NOT EXISTS session_tools (
|
|
108
|
+
session_id TEXT NOT NULL,
|
|
109
|
+
tool TEXT NOT NULL,
|
|
110
|
+
calls INTEGER NOT NULL DEFAULT 0,
|
|
111
|
+
tokens_saved INTEGER NOT NULL DEFAULT 0,
|
|
112
|
+
PRIMARY KEY (session_id, tool),
|
|
113
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
CREATE TABLE IF NOT EXISTS lifetime_tools (
|
|
117
|
+
tool TEXT PRIMARY KEY,
|
|
118
|
+
calls INTEGER NOT NULL DEFAULT 0,
|
|
119
|
+
tokens_saved INTEGER NOT NULL DEFAULT 0
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
CREATE TABLE IF NOT EXISTS lifetime_days (
|
|
123
|
+
day TEXT PRIMARY KEY,
|
|
124
|
+
calls INTEGER NOT NULL DEFAULT 0,
|
|
125
|
+
tokens_saved INTEGER NOT NULL DEFAULT 0
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
CREATE TABLE IF NOT EXISTS lifetime_totals (
|
|
129
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
130
|
+
calls INTEGER NOT NULL DEFAULT 0,
|
|
131
|
+
tokens_saved INTEGER NOT NULL DEFAULT 0,
|
|
132
|
+
raw_total INTEGER NOT NULL DEFAULT 0
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
CREATE TABLE IF NOT EXISTS summarization (
|
|
136
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
137
|
+
calls INTEGER NOT NULL DEFAULT 0,
|
|
138
|
+
cache_hits INTEGER NOT NULL DEFAULT 0
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
CREATE INDEX IF NOT EXISTS idx_lifetime_days_day_desc ON lifetime_days(day DESC);
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Connection (singleton, lazily opened)
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
let _db: Database | null = null;
|
|
149
|
+
|
|
150
|
+
/** Open or return the cached connection. Ensures schema and seed rows exist. */
|
|
151
|
+
function db(): Database {
|
|
152
|
+
if (_db) return _db;
|
|
153
|
+
|
|
154
|
+
const p = dbPath();
|
|
155
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
156
|
+
|
|
157
|
+
const conn = new Database(p);
|
|
158
|
+
// busy_timeout MUST be set first so every subsequent statement — including
|
|
159
|
+
// the WAL pragma and the DDL below — waits politely when another process
|
|
160
|
+
// holds the write lock. 30s is generous; per-statement wait is bounded by
|
|
161
|
+
// how long the longest in-flight tx takes (milliseconds in practice).
|
|
162
|
+
conn.run("PRAGMA busy_timeout = 30000");
|
|
163
|
+
// WAL mode is the whole point of migrating off JSON — concurrent writers
|
|
164
|
+
// from multiple MCP server processes no longer need userland locking.
|
|
165
|
+
conn.run("PRAGMA journal_mode = WAL");
|
|
166
|
+
conn.run("PRAGMA foreign_keys = ON");
|
|
167
|
+
// synchronous=NORMAL is the recommended pairing with WAL — durability
|
|
168
|
+
// is still crash-safe up to the last committed transaction and write
|
|
169
|
+
// throughput nearly doubles vs FULL.
|
|
170
|
+
conn.run("PRAGMA synchronous = NORMAL");
|
|
171
|
+
|
|
172
|
+
// Wrap schema + singleton seeds in a BEGIN IMMEDIATE so concurrent openers
|
|
173
|
+
// serialize across the DDL + seed-insert window. Without this, two
|
|
174
|
+
// processes could both pass CREATE TABLE IF NOT EXISTS but then race into
|
|
175
|
+
// INSERT OR IGNORE on a table that's still being created — observable as
|
|
176
|
+
// transient SQLITE_BUSY on first-time open.
|
|
177
|
+
conn.run("BEGIN IMMEDIATE");
|
|
178
|
+
try {
|
|
179
|
+
conn.exec(DDL_V1);
|
|
180
|
+
conn.run(
|
|
181
|
+
"INSERT OR IGNORE INTO meta (key, value) VALUES ('schemaVersion', ?)",
|
|
182
|
+
[String(SCHEMA_VERSION)],
|
|
183
|
+
);
|
|
184
|
+
conn.run("INSERT OR IGNORE INTO lifetime_totals (id, calls, tokens_saved, raw_total) VALUES (1, 0, 0, 0)");
|
|
185
|
+
conn.run("INSERT OR IGNORE INTO summarization (id, calls, cache_hits) VALUES (1, 0, 0)");
|
|
186
|
+
// v1.18 backward-compat: add raw_total column to pre-existing stats.db
|
|
187
|
+
// files. CREATE TABLE IF NOT EXISTS above creates it fresh, but an older
|
|
188
|
+
// db opened by this version will still be missing the column — ALTER
|
|
189
|
+
// TABLE ADD COLUMN is idempotent only via duplicate-column detection,
|
|
190
|
+
// so we swallow the expected SQLITE_ERROR on an already-migrated db.
|
|
191
|
+
try {
|
|
192
|
+
conn.run("ALTER TABLE lifetime_totals ADD COLUMN raw_total INTEGER NOT NULL DEFAULT 0");
|
|
193
|
+
} catch {
|
|
194
|
+
// Column already exists — expected on second open.
|
|
195
|
+
}
|
|
196
|
+
conn.run("COMMIT");
|
|
197
|
+
} catch (err) {
|
|
198
|
+
try { conn.run("ROLLBACK"); } catch { /* nothing to roll back */ }
|
|
199
|
+
throw err;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
_db = conn;
|
|
203
|
+
return conn;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Test helper: close + forget the connection so a fresh one is opened next. */
|
|
207
|
+
export function _resetConnection(): void {
|
|
208
|
+
if (_db) {
|
|
209
|
+
try { _db.close(); } catch { /* ok */ }
|
|
210
|
+
_db = null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Test helper: let tests point the store at a different path. */
|
|
215
|
+
export function _setDbPathForTests(override: string | null): void {
|
|
216
|
+
_resetConnection();
|
|
217
|
+
if (override === null) delete process.env.ASHLR_STATS_DB_PATH;
|
|
218
|
+
else process.env.ASHLR_STATS_DB_PATH = override;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Helpers
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
function emptySession(): SessionBucket {
|
|
226
|
+
return {
|
|
227
|
+
startedAt: new Date().toISOString(),
|
|
228
|
+
lastSavingAt: null,
|
|
229
|
+
calls: 0,
|
|
230
|
+
tokensSaved: 0,
|
|
231
|
+
byTool: {},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Note: `emptyStats()` lives in _stats.ts — the dispatcher re-exports it.
|
|
236
|
+
// No sqlite-specific variant is needed because readStats() always synthesizes
|
|
237
|
+
// a fresh StatsFile from the db rows.
|
|
238
|
+
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// Public API — mirrors _stats.ts
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Read the full stats file as an in-memory StatsFile. Called by
|
|
245
|
+
* scripts/savings-dashboard.ts, scripts/savings-status-line.ts, etc.
|
|
246
|
+
* One SQL round-trip per table; cheap compared to the JSON path's
|
|
247
|
+
* parse-migrate-validate cycle.
|
|
248
|
+
*/
|
|
249
|
+
export async function readStats(): Promise<StatsFile> {
|
|
250
|
+
const conn = db();
|
|
251
|
+
|
|
252
|
+
const sessionsRows = conn
|
|
253
|
+
.query<
|
|
254
|
+
{ session_id: string; started_at: string; last_saving_at: string | null; calls: number; tokens_saved: number },
|
|
255
|
+
[]
|
|
256
|
+
>("SELECT session_id, started_at, last_saving_at, calls, tokens_saved FROM sessions")
|
|
257
|
+
.all();
|
|
258
|
+
|
|
259
|
+
const sessionToolRows = conn
|
|
260
|
+
.query<
|
|
261
|
+
{ session_id: string; tool: string; calls: number; tokens_saved: number },
|
|
262
|
+
[]
|
|
263
|
+
>("SELECT session_id, tool, calls, tokens_saved FROM session_tools")
|
|
264
|
+
.all();
|
|
265
|
+
|
|
266
|
+
const sessions: { [sessionId: string]: SessionBucket } = {};
|
|
267
|
+
for (const row of sessionsRows) {
|
|
268
|
+
sessions[row.session_id] = {
|
|
269
|
+
startedAt: row.started_at,
|
|
270
|
+
lastSavingAt: row.last_saving_at,
|
|
271
|
+
calls: row.calls,
|
|
272
|
+
tokensSaved: row.tokens_saved,
|
|
273
|
+
byTool: {},
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
for (const row of sessionToolRows) {
|
|
277
|
+
const s = sessions[row.session_id];
|
|
278
|
+
if (!s) continue; // orphan (shouldn't exist given FK + CASCADE)
|
|
279
|
+
s.byTool[row.tool] = { calls: row.calls, tokensSaved: row.tokens_saved };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const totalsRow = conn
|
|
283
|
+
.query<{ calls: number; tokens_saved: number; raw_total: number }, []>(
|
|
284
|
+
// COALESCE guards against older schemas that pre-date raw_total;
|
|
285
|
+
// the ALTER TABLE in db() adds the column on open, but belt-and-braces
|
|
286
|
+
// for any path that bypasses init (shouldn't exist, but cheap).
|
|
287
|
+
"SELECT calls, tokens_saved, COALESCE(raw_total, 0) AS raw_total FROM lifetime_totals WHERE id = 1",
|
|
288
|
+
)
|
|
289
|
+
.get() ?? { calls: 0, tokens_saved: 0, raw_total: 0 };
|
|
290
|
+
|
|
291
|
+
const lifetimeToolRows = conn
|
|
292
|
+
.query<{ tool: string; calls: number; tokens_saved: number }, []>(
|
|
293
|
+
"SELECT tool, calls, tokens_saved FROM lifetime_tools",
|
|
294
|
+
)
|
|
295
|
+
.all();
|
|
296
|
+
const byTool: ByTool = {};
|
|
297
|
+
for (const r of lifetimeToolRows) byTool[r.tool] = { calls: r.calls, tokensSaved: r.tokens_saved };
|
|
298
|
+
|
|
299
|
+
const lifetimeDayRows = conn
|
|
300
|
+
.query<{ day: string; calls: number; tokens_saved: number }, []>(
|
|
301
|
+
"SELECT day, calls, tokens_saved FROM lifetime_days",
|
|
302
|
+
)
|
|
303
|
+
.all();
|
|
304
|
+
const byDay: ByDay = {};
|
|
305
|
+
for (const r of lifetimeDayRows) byDay[r.day] = { calls: r.calls, tokensSaved: r.tokens_saved };
|
|
306
|
+
|
|
307
|
+
const summaryRow = conn
|
|
308
|
+
.query<{ calls: number; cache_hits: number }, []>(
|
|
309
|
+
"SELECT calls, cache_hits FROM summarization WHERE id = 1",
|
|
310
|
+
)
|
|
311
|
+
.get() ?? { calls: 0, cache_hits: 0 };
|
|
312
|
+
|
|
313
|
+
const lifetime: LifetimeBucket = {
|
|
314
|
+
calls: totalsRow.calls,
|
|
315
|
+
tokensSaved: totalsRow.tokens_saved,
|
|
316
|
+
byTool,
|
|
317
|
+
byDay,
|
|
318
|
+
};
|
|
319
|
+
// v1.18: only emit rawTotal when non-zero so legacy fixtures that
|
|
320
|
+
// round-trip through importStatsFile → readStats stay byte-equal. Readers
|
|
321
|
+
// that care about the ratio coalesce missing rawTotal to 0 anyway.
|
|
322
|
+
if ((totalsRow.raw_total ?? 0) > 0) {
|
|
323
|
+
lifetime.rawTotal = totalsRow.raw_total;
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
schemaVersion: 2,
|
|
327
|
+
sessions,
|
|
328
|
+
lifetime,
|
|
329
|
+
summarization: { calls: summaryRow.calls, cacheHits: summaryRow.cache_hits },
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Record a tokens-saved event. Single SQL transaction touches up to 5 rows:
|
|
335
|
+
* sessions, session_tools, lifetime_tools, lifetime_days, lifetime_totals.
|
|
336
|
+
* Returns the computed savings so callers can surface an inline note.
|
|
337
|
+
*
|
|
338
|
+
* Never throws — errors are swallowed (observability is the crash-dump channel).
|
|
339
|
+
*/
|
|
340
|
+
export async function recordSaving(
|
|
341
|
+
rawBytes: number,
|
|
342
|
+
compactBytes: number,
|
|
343
|
+
toolName: string,
|
|
344
|
+
opts: { sessionId?: string } = {},
|
|
345
|
+
): Promise<number> {
|
|
346
|
+
// Defensive: clamp non-finite / negative inputs to 0 so SQL INTEGER columns
|
|
347
|
+
// never receive NaN (which bun:sqlite will refuse to bind).
|
|
348
|
+
const rawSafe = Number.isFinite(rawBytes) && rawBytes >= 0 ? rawBytes : 0;
|
|
349
|
+
const compactSafe = Number.isFinite(compactBytes) && compactBytes >= 0 ? compactBytes : 0;
|
|
350
|
+
const saved = Math.max(0, Math.ceil((rawSafe - compactSafe) / 4));
|
|
351
|
+
// Raw counterfactual tokens — denominator for the % savings ratio.
|
|
352
|
+
const rawTok = Math.max(0, Math.ceil(rawSafe / 4));
|
|
353
|
+
const sid = opts.sessionId ?? currentSessionId();
|
|
354
|
+
const now = new Date().toISOString();
|
|
355
|
+
const day = todayKey();
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
const conn = db();
|
|
359
|
+
const tx = conn.transaction(() => {
|
|
360
|
+
// sessions: upsert — insert if new (seed startedAt), else bump counters + lastSavingAt.
|
|
361
|
+
conn.run(
|
|
362
|
+
`INSERT INTO sessions (session_id, started_at, last_saving_at, calls, tokens_saved)
|
|
363
|
+
VALUES (?, ?, ?, 1, ?)
|
|
364
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
365
|
+
last_saving_at = excluded.last_saving_at,
|
|
366
|
+
calls = sessions.calls + 1,
|
|
367
|
+
tokens_saved = sessions.tokens_saved + excluded.tokens_saved`,
|
|
368
|
+
[sid, now, now, saved],
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
// session_tools: upsert per (session, tool).
|
|
372
|
+
conn.run(
|
|
373
|
+
`INSERT INTO session_tools (session_id, tool, calls, tokens_saved)
|
|
374
|
+
VALUES (?, ?, 1, ?)
|
|
375
|
+
ON CONFLICT(session_id, tool) DO UPDATE SET
|
|
376
|
+
calls = session_tools.calls + 1,
|
|
377
|
+
tokens_saved = session_tools.tokens_saved + excluded.tokens_saved`,
|
|
378
|
+
[sid, toolName, saved],
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
// lifetime_tools: same pattern.
|
|
382
|
+
conn.run(
|
|
383
|
+
`INSERT INTO lifetime_tools (tool, calls, tokens_saved)
|
|
384
|
+
VALUES (?, 1, ?)
|
|
385
|
+
ON CONFLICT(tool) DO UPDATE SET
|
|
386
|
+
calls = lifetime_tools.calls + 1,
|
|
387
|
+
tokens_saved = lifetime_tools.tokens_saved + excluded.tokens_saved`,
|
|
388
|
+
[toolName, saved],
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
// lifetime_days: today's bucket.
|
|
392
|
+
conn.run(
|
|
393
|
+
`INSERT INTO lifetime_days (day, calls, tokens_saved)
|
|
394
|
+
VALUES (?, 1, ?)
|
|
395
|
+
ON CONFLICT(day) DO UPDATE SET
|
|
396
|
+
calls = lifetime_days.calls + 1,
|
|
397
|
+
tokens_saved = lifetime_days.tokens_saved + excluded.tokens_saved`,
|
|
398
|
+
[day, saved],
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
// lifetime_totals: singleton. rawTotal is accumulated alongside
|
|
402
|
+
// tokens_saved so the dashboard can render `saved / rawTotal`.
|
|
403
|
+
conn.run(
|
|
404
|
+
"UPDATE lifetime_totals SET calls = calls + 1, tokens_saved = tokens_saved + ?, raw_total = raw_total + ? WHERE id = 1",
|
|
405
|
+
[saved, rawTok],
|
|
406
|
+
);
|
|
407
|
+
});
|
|
408
|
+
// IMMEDIATE mode: BEGIN IMMEDIATE acquires the write lock up front so
|
|
409
|
+
// concurrent writers queue on busy_timeout instead of racing into a
|
|
410
|
+
// deferred-transaction upgrade that aborts one side. Default DEFERRED
|
|
411
|
+
// dropped ~10% of writes under cross-process contention in tests.
|
|
412
|
+
tx.immediate();
|
|
413
|
+
} catch (err) {
|
|
414
|
+
// Best-effort: stats writes must never break tool execution. Log via
|
|
415
|
+
// stderr so test harnesses (and anyone tailing logs) can see BUSY-level
|
|
416
|
+
// contention; production can tail this via ASHLR_DEBUG=stats.
|
|
417
|
+
if (process.env.ASHLR_DEBUG) {
|
|
418
|
+
process.stderr.write(`[ashlr:stats] recordSaving failed: ${(err as Error).message}\n`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return saved;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/** Idempotently mint a session bucket (called by SessionStart hook). */
|
|
426
|
+
export async function initSessionBucket(
|
|
427
|
+
sessionId: string = currentSessionId(),
|
|
428
|
+
): Promise<void> {
|
|
429
|
+
try {
|
|
430
|
+
const conn = db();
|
|
431
|
+
conn.run(
|
|
432
|
+
`INSERT OR IGNORE INTO sessions (session_id, started_at) VALUES (?, ?)`,
|
|
433
|
+
[sessionId, new Date().toISOString()],
|
|
434
|
+
);
|
|
435
|
+
} catch { /* best-effort */ }
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Drop bucket(s) and return the combined SessionBucket for GC logging.
|
|
440
|
+
* Mirrors _stats.ts behavior: when sessionId is omitted, drops every
|
|
441
|
+
* candidate id so orphaned PPID-hash buckets get reaped too.
|
|
442
|
+
*/
|
|
443
|
+
export async function dropSessionBucket(
|
|
444
|
+
sessionId?: string,
|
|
445
|
+
): Promise<SessionBucket | null> {
|
|
446
|
+
const ids = sessionId ? [sessionId] : candidateSessionIds();
|
|
447
|
+
try {
|
|
448
|
+
const conn = db();
|
|
449
|
+
return conn.transaction((): SessionBucket | null => {
|
|
450
|
+
let calls = 0;
|
|
451
|
+
let tokensSaved = 0;
|
|
452
|
+
let startedAt = "";
|
|
453
|
+
let lastSavingAt: string | null = null;
|
|
454
|
+
const byTool: ByTool = {};
|
|
455
|
+
let hit = false;
|
|
456
|
+
|
|
457
|
+
const selectSession = conn.prepare<
|
|
458
|
+
{ started_at: string; last_saving_at: string | null; calls: number; tokens_saved: number },
|
|
459
|
+
[string]
|
|
460
|
+
>(
|
|
461
|
+
"SELECT started_at, last_saving_at, calls, tokens_saved FROM sessions WHERE session_id = ?",
|
|
462
|
+
);
|
|
463
|
+
const selectTools = conn.prepare<
|
|
464
|
+
{ tool: string; calls: number; tokens_saved: number },
|
|
465
|
+
[string]
|
|
466
|
+
>(
|
|
467
|
+
"SELECT tool, calls, tokens_saved FROM session_tools WHERE session_id = ?",
|
|
468
|
+
);
|
|
469
|
+
const deleteSession = conn.prepare<unknown, [string]>(
|
|
470
|
+
"DELETE FROM sessions WHERE session_id = ?",
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
for (const id of ids) {
|
|
474
|
+
const row = selectSession.get(id);
|
|
475
|
+
if (!row) continue;
|
|
476
|
+
hit = true;
|
|
477
|
+
calls += row.calls;
|
|
478
|
+
tokensSaved += row.tokens_saved;
|
|
479
|
+
if (!startedAt || row.started_at < startedAt) startedAt = row.started_at;
|
|
480
|
+
if (row.last_saving_at && (!lastSavingAt || row.last_saving_at > lastSavingAt)) {
|
|
481
|
+
lastSavingAt = row.last_saving_at;
|
|
482
|
+
}
|
|
483
|
+
for (const tr of selectTools.all(id)) {
|
|
484
|
+
const t = byTool[tr.tool] ?? (byTool[tr.tool] = { calls: 0, tokensSaved: 0 });
|
|
485
|
+
t.calls += tr.calls;
|
|
486
|
+
t.tokensSaved += tr.tokens_saved;
|
|
487
|
+
}
|
|
488
|
+
deleteSession.run(id); // CASCADE removes session_tools
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return hit
|
|
492
|
+
? {
|
|
493
|
+
startedAt: startedAt || new Date().toISOString(),
|
|
494
|
+
lastSavingAt,
|
|
495
|
+
calls,
|
|
496
|
+
tokensSaved,
|
|
497
|
+
byTool,
|
|
498
|
+
}
|
|
499
|
+
: null;
|
|
500
|
+
}).immediate();
|
|
501
|
+
} catch {
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/** Bump a summarization counter (calls | cacheHits). */
|
|
507
|
+
export async function bumpSummarization(
|
|
508
|
+
field: "calls" | "cacheHits",
|
|
509
|
+
): Promise<void> {
|
|
510
|
+
const col = field === "calls" ? "calls" : "cache_hits";
|
|
511
|
+
try {
|
|
512
|
+
// IMMEDIATE transaction so concurrent callers queue on busy_timeout
|
|
513
|
+
// instead of silently dropping the bump on SQLITE_BUSY. Same discipline
|
|
514
|
+
// as recordSaving; the summarization counter is heavily bumped during
|
|
515
|
+
// LLM-summarize bursts.
|
|
516
|
+
const conn = db();
|
|
517
|
+
const tx = conn.transaction(() => {
|
|
518
|
+
conn.run(
|
|
519
|
+
`UPDATE summarization SET ${col} = ${col} + 1 WHERE id = 1`,
|
|
520
|
+
);
|
|
521
|
+
});
|
|
522
|
+
tx.immediate();
|
|
523
|
+
} catch { /* best-effort */ }
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Read a single session's bucket (status-line / savings dashboard).
|
|
528
|
+
* Returns an empty bucket when the session hasn't been seeded yet so the
|
|
529
|
+
* status-line can render "0 calls" cleanly.
|
|
530
|
+
*/
|
|
531
|
+
export async function readCurrentSession(
|
|
532
|
+
sessionId?: string,
|
|
533
|
+
): Promise<SessionBucket> {
|
|
534
|
+
const conn = db();
|
|
535
|
+
const ids = sessionId ? [sessionId] : candidateSessionIds();
|
|
536
|
+
|
|
537
|
+
let best: SessionBucket | null = null;
|
|
538
|
+
for (const id of ids) {
|
|
539
|
+
const row = conn
|
|
540
|
+
.query<
|
|
541
|
+
{ started_at: string; last_saving_at: string | null; calls: number; tokens_saved: number },
|
|
542
|
+
[string]
|
|
543
|
+
>(
|
|
544
|
+
"SELECT started_at, last_saving_at, calls, tokens_saved FROM sessions WHERE session_id = ?",
|
|
545
|
+
)
|
|
546
|
+
.get(id);
|
|
547
|
+
if (!row) continue;
|
|
548
|
+
const byTool: ByTool = {};
|
|
549
|
+
const toolRows = conn
|
|
550
|
+
.query<{ tool: string; calls: number; tokens_saved: number }, [string]>(
|
|
551
|
+
"SELECT tool, calls, tokens_saved FROM session_tools WHERE session_id = ?",
|
|
552
|
+
)
|
|
553
|
+
.all(id);
|
|
554
|
+
for (const t of toolRows) byTool[t.tool] = { calls: t.calls, tokensSaved: t.tokens_saved };
|
|
555
|
+
const candidate: SessionBucket = {
|
|
556
|
+
startedAt: row.started_at,
|
|
557
|
+
lastSavingAt: row.last_saving_at,
|
|
558
|
+
calls: row.calls,
|
|
559
|
+
tokensSaved: row.tokens_saved,
|
|
560
|
+
byTool,
|
|
561
|
+
};
|
|
562
|
+
// Prefer the one with more activity (same tiebreak the JSON path uses).
|
|
563
|
+
if (!best || candidate.calls > best.calls) best = candidate;
|
|
564
|
+
}
|
|
565
|
+
return best ?? emptySession();
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// ---------------------------------------------------------------------------
|
|
569
|
+
// Migration helpers — used by scripts/migrate-stats-to-sqlite.ts
|
|
570
|
+
// ---------------------------------------------------------------------------
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Bulk-import a StatsFile (typically loaded from ~/.ashlr/stats.json) into
|
|
574
|
+
* the sqlite store. Single transaction. Idempotent if the db is already
|
|
575
|
+
* seeded from an earlier migration pass — INSERT OR REPLACE everywhere.
|
|
576
|
+
*/
|
|
577
|
+
export function importStatsFile(s: StatsFile): void {
|
|
578
|
+
const conn = db();
|
|
579
|
+
conn.transaction(() => {
|
|
580
|
+
// Lifetime totals — replace the singleton. v1.18 carries rawTotal too;
|
|
581
|
+
// older StatsFile shapes lack the field so we coerce via `?? 0`.
|
|
582
|
+
conn.run(
|
|
583
|
+
"UPDATE lifetime_totals SET calls = ?, tokens_saved = ?, raw_total = ? WHERE id = 1",
|
|
584
|
+
[s.lifetime.calls, s.lifetime.tokensSaved, s.lifetime.rawTotal ?? 0],
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
// Lifetime per-tool.
|
|
588
|
+
for (const [tool, pt] of Object.entries(s.lifetime.byTool)) {
|
|
589
|
+
conn.run(
|
|
590
|
+
"INSERT OR REPLACE INTO lifetime_tools (tool, calls, tokens_saved) VALUES (?, ?, ?)",
|
|
591
|
+
[tool, pt.calls, pt.tokensSaved],
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Lifetime per-day.
|
|
596
|
+
for (const [day, pd] of Object.entries(s.lifetime.byDay)) {
|
|
597
|
+
conn.run(
|
|
598
|
+
"INSERT OR REPLACE INTO lifetime_days (day, calls, tokens_saved) VALUES (?, ?, ?)",
|
|
599
|
+
[day, pd.calls, pd.tokensSaved],
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Summarization singleton.
|
|
604
|
+
const sm = s.summarization ?? { calls: 0, cacheHits: 0 };
|
|
605
|
+
conn.run(
|
|
606
|
+
"UPDATE summarization SET calls = ?, cache_hits = ? WHERE id = 1",
|
|
607
|
+
[sm.calls, sm.cacheHits],
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
// Sessions + their per-tool breakdowns.
|
|
611
|
+
for (const [sid, bucket] of Object.entries(s.sessions)) {
|
|
612
|
+
conn.run(
|
|
613
|
+
`INSERT OR REPLACE INTO sessions
|
|
614
|
+
(session_id, started_at, last_saving_at, calls, tokens_saved)
|
|
615
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
616
|
+
[sid, bucket.startedAt, bucket.lastSavingAt, bucket.calls, bucket.tokensSaved],
|
|
617
|
+
);
|
|
618
|
+
for (const [tool, pt] of Object.entries(bucket.byTool)) {
|
|
619
|
+
conn.run(
|
|
620
|
+
`INSERT OR REPLACE INTO session_tools
|
|
621
|
+
(session_id, tool, calls, tokens_saved)
|
|
622
|
+
VALUES (?, ?, ?, ?)`,
|
|
623
|
+
[sid, tool, pt.calls, pt.tokensSaved],
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}).immediate();
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Re-export shape types so consumers importing from _stats-sqlite get the
|
|
631
|
+
// same surface they'd get from _stats.
|
|
632
|
+
export type {
|
|
633
|
+
ByDay,
|
|
634
|
+
ByTool,
|
|
635
|
+
LifetimeBucket,
|
|
636
|
+
PerTool,
|
|
637
|
+
SessionBucket,
|
|
638
|
+
StatsFile,
|
|
639
|
+
SummarizationStats,
|
|
640
|
+
};
|