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,261 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* stats-cloud-sync.ts — Delta push of lifetime stats to the ashlr Pro backend.
|
|
4
|
+
*
|
|
5
|
+
* Reads local stats (via _stats.ts readStats()), computes the delta since the
|
|
6
|
+
* last successful sync (cursor stored in ~/.ashlr/stats-sync-cursor.json), and
|
|
7
|
+
* POSTs to POST /stats/sync.
|
|
8
|
+
*
|
|
9
|
+
* Privacy:
|
|
10
|
+
* - machineId is a one-way SHA-256 hash of os.hostname() — the raw hostname
|
|
11
|
+
* is never sent. The hash lets the backend deduplicate machine uploads
|
|
12
|
+
* without learning the hostname.
|
|
13
|
+
* - Only pure counts are sent (calls, tokensSaved, byTool, byDay). No paths,
|
|
14
|
+
* no file content, no CWD.
|
|
15
|
+
*
|
|
16
|
+
* Cursor shape: { syncedAt: ISO, calls: number, tokensSaved: number }
|
|
17
|
+
* - `calls` and `tokensSaved` are the lifetime totals at the time of the
|
|
18
|
+
* last successful sync. The delta is (current - cursor). We send the
|
|
19
|
+
* full current lifetime snapshot (not just the delta numbers) because the
|
|
20
|
+
* server uses upsert/idempotent semantics — re-sending the same payload is
|
|
21
|
+
* safe and avoids a separate delta endpoint.
|
|
22
|
+
*
|
|
23
|
+
* Best-effort: never throws, never blocks. On network failure the cursor is left
|
|
24
|
+
* untouched so the next call will retry the full accumulation.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { createHash } from "crypto";
|
|
28
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
29
|
+
import { hostname } from "os";
|
|
30
|
+
import { join } from "path";
|
|
31
|
+
|
|
32
|
+
import { readStats } from "../servers/_stats.ts";
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Paths
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
function ashlrDir(): string {
|
|
39
|
+
return join(process.env.HOME ?? process.env.USERPROFILE ?? "~", ".ashlr");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function cursorPath(): string {
|
|
43
|
+
return join(ashlrDir(), "stats-sync-cursor.json");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function proTokenPath(): string {
|
|
47
|
+
return join(ashlrDir(), "pro-token");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Public-leaderboard opt-in from ~/.ashlr/config.json { "leaderboard": ... }.
|
|
52
|
+
* Returns true ("public"), false ("off"), or undefined (unset → server keeps
|
|
53
|
+
* the user's current setting). Default is therefore OFF.
|
|
54
|
+
*/
|
|
55
|
+
export function readLeaderboardOptIn(): boolean | undefined {
|
|
56
|
+
try {
|
|
57
|
+
const cfg = JSON.parse(readFileSync(join(ashlrDir(), "config.json"), "utf-8")) as {
|
|
58
|
+
leaderboard?: unknown;
|
|
59
|
+
};
|
|
60
|
+
if (cfg.leaderboard === "public") return true;
|
|
61
|
+
if (cfg.leaderboard === "off") return false;
|
|
62
|
+
return undefined;
|
|
63
|
+
} catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Pro-token resolution
|
|
70
|
+
// Priority: ASHLR_PRO_TOKEN env var → ~/.ashlr/pro-token file
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
export function resolveProToken(): string | null {
|
|
74
|
+
const env = process.env["ASHLR_PRO_TOKEN"];
|
|
75
|
+
if (env && env.trim().length > 0) return env.trim();
|
|
76
|
+
try {
|
|
77
|
+
const p = proTokenPath();
|
|
78
|
+
if (!existsSync(p)) return null;
|
|
79
|
+
const tok = readFileSync(p, "utf-8").trim();
|
|
80
|
+
return tok.length > 0 ? tok : null;
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// machineId — SHA-256 of hostname, first 16 hex chars.
|
|
88
|
+
// Stable per machine; one-way so the server never sees the raw hostname.
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
export function machineId(): string {
|
|
92
|
+
return createHash("sha256").update(hostname()).digest("hex").slice(0, 16);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Cursor
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
interface SyncCursor {
|
|
100
|
+
syncedAt: string;
|
|
101
|
+
calls: number;
|
|
102
|
+
tokensSaved: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function readCursor(): SyncCursor | null {
|
|
106
|
+
try {
|
|
107
|
+
const raw = readFileSync(cursorPath(), "utf-8");
|
|
108
|
+
const parsed = JSON.parse(raw) as Partial<SyncCursor>;
|
|
109
|
+
if (
|
|
110
|
+
typeof parsed.syncedAt === "string" &&
|
|
111
|
+
typeof parsed.calls === "number" &&
|
|
112
|
+
typeof parsed.tokensSaved === "number"
|
|
113
|
+
) {
|
|
114
|
+
return parsed as SyncCursor;
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
/* missing or corrupt — treat as first sync */
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function writeCursor(cursor: SyncCursor): void {
|
|
123
|
+
try {
|
|
124
|
+
mkdirSync(ashlrDir(), { recursive: true });
|
|
125
|
+
writeFileSync(cursorPath(), JSON.stringify(cursor));
|
|
126
|
+
} catch {
|
|
127
|
+
/* best-effort */
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Core push logic — exported so tests can call it directly
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
export interface SyncResult {
|
|
136
|
+
skipped: boolean;
|
|
137
|
+
reason?: string;
|
|
138
|
+
ok?: boolean;
|
|
139
|
+
status?: number;
|
|
140
|
+
error?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function pushStatsToCloud(opts: {
|
|
144
|
+
apiUrl?: string;
|
|
145
|
+
proToken?: string;
|
|
146
|
+
}): Promise<SyncResult> {
|
|
147
|
+
const token = opts.proToken ?? resolveProToken();
|
|
148
|
+
if (!token) return { skipped: true, reason: "no-pro-token" };
|
|
149
|
+
if (process.env["ASHLR_STATS_UPLOAD"] === "0") {
|
|
150
|
+
return { skipped: true, reason: "kill-switch" };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const apiUrl = opts.apiUrl ?? process.env["ASHLR_API_URL"] ?? "https://api.ashlr.ai";
|
|
154
|
+
|
|
155
|
+
let stats: Awaited<ReturnType<typeof readStats>>;
|
|
156
|
+
try {
|
|
157
|
+
stats = await readStats();
|
|
158
|
+
} catch {
|
|
159
|
+
return { skipped: false, error: "read-stats-failed" };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const cursor = readCursor();
|
|
163
|
+
|
|
164
|
+
// Skip if nothing has changed since last sync.
|
|
165
|
+
if (
|
|
166
|
+
cursor &&
|
|
167
|
+
cursor.calls === stats.lifetime.calls &&
|
|
168
|
+
cursor.tokensSaved === stats.lifetime.tokensSaved
|
|
169
|
+
) {
|
|
170
|
+
return { skipped: true, reason: "no-delta" };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Build a pure-counts payload — byTool and byDay collapse to tokensSaved-only
|
|
174
|
+
// maps to match the server's ByToolSchema / ByDaySchema.
|
|
175
|
+
const byTool: Record<string, number> = {};
|
|
176
|
+
for (const [k, v] of Object.entries(stats.lifetime.byTool)) {
|
|
177
|
+
byTool[k] = v.tokensSaved;
|
|
178
|
+
}
|
|
179
|
+
const byDay: Record<string, number> = {};
|
|
180
|
+
for (const [k, v] of Object.entries(stats.lifetime.byDay)) {
|
|
181
|
+
byDay[k] = v.tokensSaved;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const leaderboardOptIn = readLeaderboardOptIn();
|
|
185
|
+
|
|
186
|
+
const body = {
|
|
187
|
+
apiToken: token,
|
|
188
|
+
stats: {
|
|
189
|
+
lifetime: {
|
|
190
|
+
calls: stats.lifetime.calls,
|
|
191
|
+
tokensSaved: stats.lifetime.tokensSaved,
|
|
192
|
+
byTool,
|
|
193
|
+
byDay,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
// machineId is informational — the server deduplicates on apiToken+machineId.
|
|
197
|
+
machineId: machineId(),
|
|
198
|
+
// Public-leaderboard opt-in — sent only when explicitly set (else the
|
|
199
|
+
// server leaves the user's current setting unchanged). Default OFF.
|
|
200
|
+
...(leaderboardOptIn !== undefined ? { leaderboard_opt_in: leaderboardOptIn } : {}),
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const res = await fetch(`${apiUrl}/stats/sync`, {
|
|
205
|
+
method: "POST",
|
|
206
|
+
headers: { "Content-Type": "application/json" },
|
|
207
|
+
body: JSON.stringify(body),
|
|
208
|
+
signal: AbortSignal.timeout(10_000),
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
if (res.ok) {
|
|
212
|
+
// Persist cursor only on success. Failures leave cursor untouched → retry.
|
|
213
|
+
writeCursor({
|
|
214
|
+
syncedAt: new Date().toISOString(),
|
|
215
|
+
calls: stats.lifetime.calls,
|
|
216
|
+
tokensSaved: stats.lifetime.tokensSaved,
|
|
217
|
+
});
|
|
218
|
+
return { skipped: false, ok: true, status: res.status };
|
|
219
|
+
}
|
|
220
|
+
return { skipped: false, ok: false, status: res.status, error: `http-${res.status}` };
|
|
221
|
+
} catch (e) {
|
|
222
|
+
return { skipped: false, ok: false, error: e instanceof Error ? e.message : "network-error" };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
// Fire-and-forget entry point — called from session hooks
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Best-effort cloud sync. Gated on Pro token presence. Never throws, never
|
|
232
|
+
* blocks the caller. Rate-limited: at most once per 5 minutes (tracked via
|
|
233
|
+
* cursor syncedAt). For use at SessionEnd to flush the final stats.
|
|
234
|
+
*/
|
|
235
|
+
export function maybeCloudSync(): void {
|
|
236
|
+
const token = resolveProToken();
|
|
237
|
+
if (!token) return;
|
|
238
|
+
if (process.env["ASHLR_STATS_UPLOAD"] === "0") return;
|
|
239
|
+
|
|
240
|
+
// Rate-limit: if cursor is recent (< 5 min), skip.
|
|
241
|
+
const cursor = readCursor();
|
|
242
|
+
if (cursor) {
|
|
243
|
+
const age = Date.now() - new Date(cursor.syncedAt).getTime();
|
|
244
|
+
if (age < 5 * 60 * 1000) return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Detach — never await this
|
|
248
|
+
void pushStatsToCloud({ proToken: token });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (import.meta.main) {
|
|
252
|
+
const result = await pushStatsToCloud({});
|
|
253
|
+
if (result.skipped) {
|
|
254
|
+
process.stderr.write(`[ashlr:stats-sync] skipped (${result.reason})\n`);
|
|
255
|
+
} else if (result.ok) {
|
|
256
|
+
process.stderr.write(`[ashlr:stats-sync] pushed ok (HTTP ${result.status})\n`);
|
|
257
|
+
} else {
|
|
258
|
+
process.stderr.write(`[ashlr:stats-sync] failed: ${result.error}\n`);
|
|
259
|
+
}
|
|
260
|
+
process.exit(0);
|
|
261
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* telemetry-flush.ts — Read the local telemetry buffer and POST to the
|
|
4
|
+
* maintainer's collection endpoint.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* bun run scripts/telemetry-flush.ts # manual flush
|
|
8
|
+
* (also called from session-end hook and hourly background task)
|
|
9
|
+
*
|
|
10
|
+
* Endpoint: ASHLR_TELEMETRY_URL env (default: https://telemetry.ashlr.ai/v1/events)
|
|
11
|
+
*
|
|
12
|
+
* POST shape:
|
|
13
|
+
* { sessionId: <hashed-string>, events: TelemetryRecord[] }
|
|
14
|
+
*
|
|
15
|
+
* Response shape (2xx):
|
|
16
|
+
* { accepted: number }
|
|
17
|
+
*
|
|
18
|
+
* On success: truncate buffer to entries newer than the flush horizon.
|
|
19
|
+
* On error: leave buffer untouched, log to stderr (best-effort), retry next cycle.
|
|
20
|
+
*
|
|
21
|
+
* Privacy: only anonymized, bucketed event shapes are sent. No file paths,
|
|
22
|
+
* no patterns, no content. See docs/telemetry.md for the full contract.
|
|
23
|
+
*
|
|
24
|
+
* Design rules:
|
|
25
|
+
* - Never throws from main(). All errors are swallowed.
|
|
26
|
+
* - Never blocks tool functionality.
|
|
27
|
+
* - 10s network timeout to prevent indefinite hangs.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { existsSync, readFileSync } from "fs";
|
|
31
|
+
import { homedir } from "os";
|
|
32
|
+
import { join } from "path";
|
|
33
|
+
import {
|
|
34
|
+
isTelemetryEnabled,
|
|
35
|
+
readTelemetryBuffer,
|
|
36
|
+
truncateTelemetryBuffer,
|
|
37
|
+
getOrCreateTelemetrySessionId,
|
|
38
|
+
looksLikePath,
|
|
39
|
+
type TelemetryRecord,
|
|
40
|
+
} from "../servers/_telemetry";
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Config
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
const DEFAULT_ENDPOINT = "https://telemetry.ashlr.ai/v1/events";
|
|
47
|
+
const FLUSH_TIMEOUT_MS = 10_000;
|
|
48
|
+
const MAX_EVENTS_PER_FLUSH = 500; // avoid huge payloads
|
|
49
|
+
|
|
50
|
+
function home(): string {
|
|
51
|
+
return process.env.HOME ?? homedir();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function endpoint(): string {
|
|
55
|
+
return (process.env.ASHLR_TELEMETRY_URL ?? DEFAULT_ENDPOINT).trim();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Safety guard: strip any record with path-like strings before sending
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
function isSafeRecord(r: TelemetryRecord): boolean {
|
|
63
|
+
for (const v of Object.values(r)) {
|
|
64
|
+
if (typeof v === "string" && looksLikePath(v)) return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Flush
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
interface FlushResult {
|
|
74
|
+
skipped?: string;
|
|
75
|
+
sent?: number;
|
|
76
|
+
error?: string;
|
|
77
|
+
ok: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function flush(homeDir: string = home()): Promise<FlushResult> {
|
|
81
|
+
if (!isTelemetryEnabled(homeDir)) {
|
|
82
|
+
return { ok: true, skipped: "telemetry-off" };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const records = readTelemetryBuffer(homeDir);
|
|
86
|
+
if (records.length === 0) {
|
|
87
|
+
return { ok: true, skipped: "empty-buffer" };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Batch: take the oldest MAX_EVENTS_PER_FLUSH records.
|
|
91
|
+
const batch = records.slice(0, MAX_EVENTS_PER_FLUSH).filter(isSafeRecord);
|
|
92
|
+
if (batch.length === 0) {
|
|
93
|
+
return { ok: true, skipped: "no-safe-records" };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Flush horizon: the max ts in this batch. After a successful POST we drop
|
|
97
|
+
// everything <= this horizon so re-flush never double-counts.
|
|
98
|
+
const horizonTs = Math.max(...batch.map((r) => r.ts));
|
|
99
|
+
|
|
100
|
+
const sessionId = getOrCreateTelemetrySessionId(homeDir);
|
|
101
|
+
|
|
102
|
+
const body = JSON.stringify({ sessionId, events: batch });
|
|
103
|
+
|
|
104
|
+
// 10-second timeout via AbortController.
|
|
105
|
+
const controller = new AbortController();
|
|
106
|
+
const timeout = setTimeout(() => controller.abort(), FLUSH_TIMEOUT_MS);
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const res = await fetch(endpoint(), {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: { "Content-Type": "application/json" },
|
|
112
|
+
body,
|
|
113
|
+
signal: controller.signal,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
clearTimeout(timeout);
|
|
117
|
+
|
|
118
|
+
if (res.ok) {
|
|
119
|
+
// Success: prune flushed entries from buffer.
|
|
120
|
+
truncateTelemetryBuffer(horizonTs, homeDir);
|
|
121
|
+
return { ok: true, sent: batch.length };
|
|
122
|
+
} else {
|
|
123
|
+
// Non-2xx: leave buffer alone, log, retry next cycle.
|
|
124
|
+
return {
|
|
125
|
+
ok: false,
|
|
126
|
+
error: `HTTP ${res.status}: ${res.statusText}`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
} catch (err) {
|
|
130
|
+
clearTimeout(timeout);
|
|
131
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
132
|
+
return { ok: false, error: msg };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// CLI entry point
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
async function main(): Promise<void> {
|
|
141
|
+
const result = await flush();
|
|
142
|
+
if (result.skipped) {
|
|
143
|
+
process.stderr.write(`[ashlr-telemetry] flush skipped: ${result.skipped}\n`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (result.ok) {
|
|
147
|
+
process.stderr.write(`[ashlr-telemetry] flushed ${result.sent ?? 0} events\n`);
|
|
148
|
+
} else {
|
|
149
|
+
// Silent drop on error — never surface to the user.
|
|
150
|
+
process.stderr.write(`[ashlr-telemetry] flush failed (will retry): ${result.error ?? "unknown"}\n`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (import.meta.main) {
|
|
155
|
+
await main().catch(() => {
|
|
156
|
+
// Final safety net — main() itself should never throw, but just in case.
|
|
157
|
+
process.exit(0);
|
|
158
|
+
});
|
|
159
|
+
process.exit(0);
|
|
160
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* telemetry-status.ts — Telemetry snapshot for /ashlr-status --telemetry.
|
|
4
|
+
*
|
|
5
|
+
* Reports:
|
|
6
|
+
* - LLM provider that would be selected right now
|
|
7
|
+
* - Embedding cache: total entries + last-100 hit rate
|
|
8
|
+
* - Genome: sections present + last-50 grep fire-rate
|
|
9
|
+
* - Block→ashlr conversion ratio (24h rolling)
|
|
10
|
+
*
|
|
11
|
+
* Contract: always exit 0. No external dependencies beyond the ashlr data
|
|
12
|
+
* files already present on disk. Falls back to "—" for any missing data.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
16
|
+
import { homedir } from "os";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
import { isTelemetryEnabled, readTelemetryBuffer } from "../servers/_telemetry";
|
|
19
|
+
import { readHookTimingsDetailed } from "./hook-timing-reader.ts";
|
|
20
|
+
|
|
21
|
+
const HOME = process.env.HOME ?? homedir();
|
|
22
|
+
const ASHLR_DIR = join(HOME, ".ashlr");
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// LLM provider detection
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
function detectLlmProvider(): string {
|
|
29
|
+
// Anthropic: ANTHROPIC_API_KEY set in env, or ~/.claude/credentials.json
|
|
30
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
31
|
+
return "anthropic (ANTHROPIC_API_KEY set)";
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const credPath = join(HOME, ".claude", "credentials.json");
|
|
35
|
+
if (existsSync(credPath)) {
|
|
36
|
+
const creds = JSON.parse(readFileSync(credPath, "utf-8")) as Record<string, unknown>;
|
|
37
|
+
if (creds.anthropicApiKey || creds.ANTHROPIC_API_KEY) {
|
|
38
|
+
return "anthropic (credentials.json)";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// ignore
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ONNX: check for onnxruntime-node availability
|
|
46
|
+
try {
|
|
47
|
+
require.resolve("onnxruntime-node");
|
|
48
|
+
return "onnx (bundled runtime)";
|
|
49
|
+
} catch {
|
|
50
|
+
// not available
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Local: check for Ollama or LM Studio
|
|
54
|
+
const ollamaUrl = process.env.ASHLR_EMBED_URL ?? "http://localhost:11434";
|
|
55
|
+
if (ollamaUrl.includes("11434")) {
|
|
56
|
+
return "local/ollama (if running) → snipCompact fallback";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return "snipCompact-only (no LLM provider configured)";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Embedding cache stats
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
function readEmbedCacheStats(): { totalEntries: number; last100HitRate: string } {
|
|
67
|
+
let totalEntries = 0;
|
|
68
|
+
let last100HitRate = "—";
|
|
69
|
+
|
|
70
|
+
// Total entries from embed-calibration.jsonl (each line = one lookup)
|
|
71
|
+
try {
|
|
72
|
+
const calPath = join(ASHLR_DIR, "embed-calibration.jsonl");
|
|
73
|
+
if (existsSync(calPath)) {
|
|
74
|
+
const lines = readFileSync(calPath, "utf-8")
|
|
75
|
+
.split("\n")
|
|
76
|
+
.filter((l) => l.trim());
|
|
77
|
+
totalEntries = lines.length;
|
|
78
|
+
|
|
79
|
+
// Last 100 calls hit rate
|
|
80
|
+
const last100 = lines.slice(-100);
|
|
81
|
+
if (last100.length > 0) {
|
|
82
|
+
let hits = 0;
|
|
83
|
+
for (const line of last100) {
|
|
84
|
+
try {
|
|
85
|
+
const r = JSON.parse(line) as { hit?: boolean };
|
|
86
|
+
if (r.hit === true) hits++;
|
|
87
|
+
} catch {
|
|
88
|
+
// skip
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const rate = Math.round((hits / last100.length) * 100);
|
|
92
|
+
last100HitRate = `${rate}%`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// ignore
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { totalEntries, last100HitRate };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Genome stats
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
function readGenomeStats(): { sections: string; fireRate: string } {
|
|
107
|
+
// Count sections in .ashlrcode/genome/ relative to cwd
|
|
108
|
+
let sections = "—";
|
|
109
|
+
const genomePath = join(process.cwd(), ".ashlrcode", "genome");
|
|
110
|
+
if (existsSync(genomePath)) {
|
|
111
|
+
try {
|
|
112
|
+
// Count .md files in the genome directory
|
|
113
|
+
const files = readdirSync(genomePath, { withFileTypes: true });
|
|
114
|
+
const mdCount = files.filter((f) => f.isFile() && f.name.endsWith(".md")).length;
|
|
115
|
+
sections = `${mdCount}`;
|
|
116
|
+
} catch {
|
|
117
|
+
sections = "present (count unavailable)";
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
sections = "0 (no genome — run /ashlr-genome-init)";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Fire rate from last 50 ashlr__grep calls in session-log.jsonl
|
|
124
|
+
let fireRate = "—";
|
|
125
|
+
try {
|
|
126
|
+
const logPath = join(ASHLR_DIR, "session-log.jsonl");
|
|
127
|
+
if (existsSync(logPath)) {
|
|
128
|
+
const lines = readFileSync(logPath, "utf-8")
|
|
129
|
+
.split("\n")
|
|
130
|
+
.filter((l) => l.trim());
|
|
131
|
+
|
|
132
|
+
// Find last 50 ashlr__grep tool_call events + genome_route_taken events
|
|
133
|
+
const grepRelated = lines
|
|
134
|
+
.map((l) => {
|
|
135
|
+
try { return JSON.parse(l) as { event?: string; tool?: string }; }
|
|
136
|
+
catch { return null; }
|
|
137
|
+
})
|
|
138
|
+
.filter((r): r is { event: string; tool?: string } =>
|
|
139
|
+
r !== null && typeof r.event === "string" &&
|
|
140
|
+
(r.tool === "ashlr__grep" || r.event === "genome_route_taken")
|
|
141
|
+
)
|
|
142
|
+
.slice(-100); // look at recent history
|
|
143
|
+
|
|
144
|
+
// Count total grep calls and genome-routed ones in last 50 grep-related events
|
|
145
|
+
const last50 = grepRelated.slice(-50);
|
|
146
|
+
const totalGreps = last50.filter((r) => r.tool === "ashlr__grep").length;
|
|
147
|
+
const genomeRouted = last50.filter((r) => r.event === "genome_route_taken").length;
|
|
148
|
+
|
|
149
|
+
if (totalGreps > 0) {
|
|
150
|
+
const rate = Math.round((genomeRouted / totalGreps) * 100);
|
|
151
|
+
fireRate = `${rate}%`;
|
|
152
|
+
} else if (grepRelated.length > 0) {
|
|
153
|
+
fireRate = "0% (no recent grep calls)";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
// ignore
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { sections, fireRate };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Block→ashlr conversion ratio (24h)
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
export function readConversionRatio(
|
|
168
|
+
homeDir: string = HOME,
|
|
169
|
+
nowMs: number = Date.now(),
|
|
170
|
+
): string {
|
|
171
|
+
const windowMs = 24 * 60 * 60 * 1000;
|
|
172
|
+
const cutoff = nowMs - windowMs;
|
|
173
|
+
let blocks = 0;
|
|
174
|
+
let converted = 0;
|
|
175
|
+
|
|
176
|
+
// Blocks from hook-timings.jsonl (outcome === "block")
|
|
177
|
+
const timingRead = readHookTimingsDetailed({
|
|
178
|
+
path: join(homeDir, ".ashlr", "hook-timings.jsonl"),
|
|
179
|
+
sinceMs: cutoff,
|
|
180
|
+
});
|
|
181
|
+
for (const r of timingRead.rows) {
|
|
182
|
+
const ts = typeof r.ts === "string" ? Date.parse(r.ts) : 0;
|
|
183
|
+
if (Number.isFinite(ts) && ts >= cutoff && r.outcome === "block") blocks++;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Conversions from session-log.jsonl (event === "tool_called_after_block")
|
|
187
|
+
try {
|
|
188
|
+
const logPath = join(homeDir, ".ashlr", "session-log.jsonl");
|
|
189
|
+
if (existsSync(logPath)) {
|
|
190
|
+
const lines = readFileSync(logPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
191
|
+
for (const line of lines) {
|
|
192
|
+
try {
|
|
193
|
+
const r = JSON.parse(line) as { ts?: string; event?: string };
|
|
194
|
+
const ts = typeof r.ts === "string" ? Date.parse(r.ts) : 0;
|
|
195
|
+
if (Number.isFinite(ts) && ts >= cutoff && r.event === "tool_called_after_block") {
|
|
196
|
+
converted++;
|
|
197
|
+
}
|
|
198
|
+
} catch {
|
|
199
|
+
// skip
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
// ignore
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (timingRead.coverage === "partial") {
|
|
208
|
+
return "— unavailable (partial hook-timing coverage; 24h)";
|
|
209
|
+
}
|
|
210
|
+
if (blocks === 0 && converted === 0) return "— (no data yet)";
|
|
211
|
+
const rate = blocks > 0 ? Math.round((converted / blocks) * 100) : 0;
|
|
212
|
+
return `${blocks} blocks / ${converted} converted = ${rate}% (24h)`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// Main
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// Opt-in telemetry status
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
function readOptInTelemetryStatus(): string {
|
|
224
|
+
const enabled = isTelemetryEnabled(HOME);
|
|
225
|
+
if (!enabled) {
|
|
226
|
+
return "OFF (default) · to enable: ASHLR_TELEMETRY=on or config.json { \"telemetry\": \"opt-in\" }";
|
|
227
|
+
}
|
|
228
|
+
// Count buffered events.
|
|
229
|
+
let bufferCount = 0;
|
|
230
|
+
try {
|
|
231
|
+
const records = readTelemetryBuffer(HOME);
|
|
232
|
+
bufferCount = records.length;
|
|
233
|
+
} catch {
|
|
234
|
+
/* ignore */
|
|
235
|
+
}
|
|
236
|
+
return `ON · buffer: ${bufferCount} events · to disable: ASHLR_TELEMETRY=off`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function main(): Promise<void> {
|
|
240
|
+
const provider = detectLlmProvider();
|
|
241
|
+
const { totalEntries, last100HitRate } = readEmbedCacheStats();
|
|
242
|
+
const { sections, fireRate } = readGenomeStats();
|
|
243
|
+
const conversionRatio = readConversionRatio();
|
|
244
|
+
const optInStatus = readOptInTelemetryStatus();
|
|
245
|
+
|
|
246
|
+
const embedStr = totalEntries > 0
|
|
247
|
+
? `${totalEntries.toLocaleString()} entries · last-100 hit rate ${last100HitRate}`
|
|
248
|
+
: `0 entries · ${last100HitRate}`;
|
|
249
|
+
|
|
250
|
+
const genomeStr = `${sections} sections · last-50 grep fire-rate ${fireRate}`;
|
|
251
|
+
|
|
252
|
+
process.stdout.write([
|
|
253
|
+
"## Telemetry snapshot",
|
|
254
|
+
` llm-provider: ${provider}`,
|
|
255
|
+
` embed-cache: ${embedStr}`,
|
|
256
|
+
` genome: ${genomeStr}`,
|
|
257
|
+
` block→ashlr: ${conversionRatio}`,
|
|
258
|
+
` opt-in telemetry: ${optInStatus}`,
|
|
259
|
+
"",
|
|
260
|
+
].join("\n"));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (import.meta.main) {
|
|
264
|
+
await main();
|
|
265
|
+
process.exit(0);
|
|
266
|
+
}
|