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,313 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* cloud-smoke-test.ts — Automated smoke runner for the ashlr cloud backend.
|
|
4
|
+
*
|
|
5
|
+
* Reads:
|
|
6
|
+
* ASHLR_API_URL (default: https://api.ashlr.ai)
|
|
7
|
+
* ASHLR_PRO_TOKEN (optional — Pro-gated checks skip when absent)
|
|
8
|
+
*
|
|
9
|
+
* Runs 5 checks matching server/docs/cloud-smoke-tests.md:
|
|
10
|
+
* 1. /healthz + /readyz reachable
|
|
11
|
+
* 2. POST /v1/events round-trip
|
|
12
|
+
* 3. (Pro) GET /user/me Bearer validation
|
|
13
|
+
* 4. (Pro) POST /llm/summarize round-trip
|
|
14
|
+
* 5. (Pro) GET /stats/aggregate shape
|
|
15
|
+
*
|
|
16
|
+
* Each result: { name, status: "pass"|"fail"|"skip", ms, detail? }
|
|
17
|
+
* Exit 0 if all pass/skip. Exit 1 if any fail.
|
|
18
|
+
*
|
|
19
|
+
* CI usage:
|
|
20
|
+
* ASHLR_API_URL=https://staging.ashlr.ai bun run scripts/cloud-smoke-test.ts
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export interface CheckResult {
|
|
24
|
+
name: string;
|
|
25
|
+
status: "pass" | "fail" | "skip";
|
|
26
|
+
ms: number;
|
|
27
|
+
detail?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SmokeConfig {
|
|
31
|
+
apiUrl: string;
|
|
32
|
+
proToken?: string;
|
|
33
|
+
/** Override fetch for testing */
|
|
34
|
+
fetchImpl?: typeof fetch;
|
|
35
|
+
/** Suppress console output (used in tests) */
|
|
36
|
+
silent?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Internal helpers
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
async function timed<T>(fn: () => Promise<T>): Promise<{ result: T; ms: number }> {
|
|
44
|
+
const start = Date.now();
|
|
45
|
+
const result = await fn();
|
|
46
|
+
return { result, ms: Date.now() - start };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function skip(name: string): CheckResult {
|
|
50
|
+
return { name, status: "skip", ms: 0 };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Check 1 — /healthz + /readyz
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
export async function checkHealth(cfg: SmokeConfig): Promise<CheckResult> {
|
|
58
|
+
const name = "healthz+readyz";
|
|
59
|
+
const f = cfg.fetchImpl ?? fetch;
|
|
60
|
+
|
|
61
|
+
const { ms } = await timed(async () => {
|
|
62
|
+
for (const path of ["/healthz", "/readyz"]) {
|
|
63
|
+
const res = await f(`${cfg.apiUrl}${path}`, { signal: AbortSignal.timeout(5000) });
|
|
64
|
+
if (!res.ok) throw new Error(`${path} returned ${res.status}`);
|
|
65
|
+
// Liveness/readiness contract is HTTP 200, not a specific body shape.
|
|
66
|
+
// The healthz endpoint returns { status, version, uptimeSeconds }; the
|
|
67
|
+
// readyz endpoint returns { db, checks: {...} }. Accept anything that
|
|
68
|
+
// parses as JSON.
|
|
69
|
+
await res.json();
|
|
70
|
+
}
|
|
71
|
+
}).catch((err: unknown) => {
|
|
72
|
+
return { ms: 0, error: String(err) };
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Re-run cleanly to capture ms properly
|
|
76
|
+
let ms2 = 0;
|
|
77
|
+
let detail: string | undefined;
|
|
78
|
+
try {
|
|
79
|
+
const t = await timed(async () => {
|
|
80
|
+
for (const path of ["/healthz", "/readyz"]) {
|
|
81
|
+
const res = await f(`${cfg.apiUrl}${path}`, { signal: AbortSignal.timeout(5000) });
|
|
82
|
+
if (!res.ok) throw new Error(`${path} returned ${res.status}`);
|
|
83
|
+
await res.json();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
ms2 = t.ms;
|
|
87
|
+
} catch (err: unknown) {
|
|
88
|
+
detail = String(err);
|
|
89
|
+
return { name, status: "fail", ms: ms2, detail };
|
|
90
|
+
}
|
|
91
|
+
void ms;
|
|
92
|
+
return { name, status: "pass", ms: ms2 };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Check 2 — POST /v1/events round-trip
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
export async function checkTelemetry(cfg: SmokeConfig): Promise<CheckResult> {
|
|
100
|
+
const name = "POST /v1/events";
|
|
101
|
+
const f = cfg.fetchImpl ?? fetch;
|
|
102
|
+
|
|
103
|
+
let ms = 0;
|
|
104
|
+
try {
|
|
105
|
+
const t = await timed(async () => {
|
|
106
|
+
const res = await f(`${cfg.apiUrl}/v1/events`, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: { "content-type": "application/json" },
|
|
109
|
+
body: JSON.stringify({
|
|
110
|
+
sessionId: "smoke0000000000ff",
|
|
111
|
+
events: [
|
|
112
|
+
{
|
|
113
|
+
ts: Math.floor(Date.now() / 1000),
|
|
114
|
+
kind: "version",
|
|
115
|
+
sessionId: "smoke0000000000ff",
|
|
116
|
+
pluginVersion: "1.27.0",
|
|
117
|
+
bunVersion: "1.0.0",
|
|
118
|
+
platform: "darwin",
|
|
119
|
+
arch: "arm64",
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
}),
|
|
123
|
+
signal: AbortSignal.timeout(5000),
|
|
124
|
+
});
|
|
125
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
126
|
+
const body = await res.json() as Record<string, unknown>;
|
|
127
|
+
if (typeof body["accepted"] !== "number") {
|
|
128
|
+
throw new Error(`expected numeric 'accepted', got: ${JSON.stringify(body)}`);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
ms = t.ms;
|
|
132
|
+
} catch (err: unknown) {
|
|
133
|
+
return { name, status: "fail", ms, detail: String(err) };
|
|
134
|
+
}
|
|
135
|
+
return { name, status: "pass", ms };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// Check 3 — GET /user/me (Pro)
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
export async function checkUserMe(cfg: SmokeConfig): Promise<CheckResult> {
|
|
143
|
+
const name = "GET /user/me";
|
|
144
|
+
if (!cfg.proToken) return skip(name);
|
|
145
|
+
const f = cfg.fetchImpl ?? fetch;
|
|
146
|
+
|
|
147
|
+
let ms = 0;
|
|
148
|
+
try {
|
|
149
|
+
const t = await timed(async () => {
|
|
150
|
+
const res = await f(`${cfg.apiUrl}/user/me`, {
|
|
151
|
+
headers: { Authorization: `Bearer ${cfg.proToken}` },
|
|
152
|
+
signal: AbortSignal.timeout(5000),
|
|
153
|
+
});
|
|
154
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
155
|
+
const body = await res.json() as Record<string, unknown>;
|
|
156
|
+
if (!body["userId"] || !body["tier"]) {
|
|
157
|
+
throw new Error(`missing userId/tier in response: ${JSON.stringify(body)}`);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
ms = t.ms;
|
|
161
|
+
} catch (err: unknown) {
|
|
162
|
+
return { name, status: "fail", ms, detail: String(err) };
|
|
163
|
+
}
|
|
164
|
+
return { name, status: "pass", ms };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Check 4 — POST /llm/summarize (Pro)
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
export async function checkLlmSummarize(cfg: SmokeConfig): Promise<CheckResult> {
|
|
172
|
+
const name = "POST /llm/summarize";
|
|
173
|
+
if (!cfg.proToken) return skip(name);
|
|
174
|
+
const f = cfg.fetchImpl ?? fetch;
|
|
175
|
+
|
|
176
|
+
let ms = 0;
|
|
177
|
+
try {
|
|
178
|
+
const t = await timed(async () => {
|
|
179
|
+
const res = await f(`${cfg.apiUrl}/llm/summarize`, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: {
|
|
182
|
+
Authorization: `Bearer ${cfg.proToken}`,
|
|
183
|
+
"content-type": "application/json",
|
|
184
|
+
},
|
|
185
|
+
body: JSON.stringify({
|
|
186
|
+
text: "The quick brown fox jumps over the lazy dog. ".repeat(10),
|
|
187
|
+
systemPrompt: "Summarize in one sentence.",
|
|
188
|
+
toolName: "ashlr__read",
|
|
189
|
+
maxTokens: 80,
|
|
190
|
+
}),
|
|
191
|
+
signal: AbortSignal.timeout(30000),
|
|
192
|
+
});
|
|
193
|
+
if (res.status === 429) {
|
|
194
|
+
// Rate-limited or capped — not a failure; check code field
|
|
195
|
+
const body = await res.json() as Record<string, unknown>;
|
|
196
|
+
const code = body["code"];
|
|
197
|
+
if (!["rate_limit", "daily_cap", "cost_cap"].includes(String(code))) {
|
|
198
|
+
throw new Error(`unexpected 429 code: ${code}`);
|
|
199
|
+
}
|
|
200
|
+
// Treat expected cap codes as pass (system working correctly)
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
204
|
+
const body = await res.json() as Record<string, unknown>;
|
|
205
|
+
if (!body["summary"] || !body["modelUsed"]) {
|
|
206
|
+
throw new Error(`missing summary/modelUsed: ${JSON.stringify(body)}`);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
ms = t.ms;
|
|
210
|
+
} catch (err: unknown) {
|
|
211
|
+
return { name, status: "fail", ms, detail: String(err) };
|
|
212
|
+
}
|
|
213
|
+
return { name, status: "pass", ms };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Check 5 — GET /stats/aggregate (Pro)
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
export async function checkStatsAggregate(cfg: SmokeConfig): Promise<CheckResult> {
|
|
221
|
+
const name = "GET /stats/aggregate";
|
|
222
|
+
if (!cfg.proToken) return skip(name);
|
|
223
|
+
const f = cfg.fetchImpl ?? fetch;
|
|
224
|
+
|
|
225
|
+
let ms = 0;
|
|
226
|
+
try {
|
|
227
|
+
const t = await timed(async () => {
|
|
228
|
+
const res = await f(`${cfg.apiUrl}/stats/aggregate`, {
|
|
229
|
+
headers: { Authorization: `Bearer ${cfg.proToken}` },
|
|
230
|
+
signal: AbortSignal.timeout(5000),
|
|
231
|
+
});
|
|
232
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
233
|
+
const body = await res.json() as Record<string, unknown>;
|
|
234
|
+
if (typeof body["machine_count"] !== "number") {
|
|
235
|
+
throw new Error(`missing numeric machine_count: ${JSON.stringify(body)}`);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
ms = t.ms;
|
|
239
|
+
} catch (err: unknown) {
|
|
240
|
+
return { name, status: "fail", ms, detail: String(err) };
|
|
241
|
+
}
|
|
242
|
+
return { name, status: "pass", ms };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// Run all checks
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
export async function runAll(cfg: SmokeConfig): Promise<CheckResult[]> {
|
|
250
|
+
// Health must pass before we bother with the rest
|
|
251
|
+
const health = await checkHealth(cfg);
|
|
252
|
+
const results: CheckResult[] = [health];
|
|
253
|
+
|
|
254
|
+
if (health.status === "fail") {
|
|
255
|
+
// Skip remaining checks — server is unreachable
|
|
256
|
+
for (const name of [
|
|
257
|
+
"POST /v1/events",
|
|
258
|
+
"GET /user/me",
|
|
259
|
+
"POST /llm/summarize",
|
|
260
|
+
"GET /stats/aggregate",
|
|
261
|
+
]) {
|
|
262
|
+
results.push({ name, status: "skip", ms: 0, detail: "skipped: healthz failed" });
|
|
263
|
+
}
|
|
264
|
+
return results;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
results.push(
|
|
268
|
+
await checkTelemetry(cfg),
|
|
269
|
+
await checkUserMe(cfg),
|
|
270
|
+
await checkLlmSummarize(cfg),
|
|
271
|
+
await checkStatsAggregate(cfg),
|
|
272
|
+
);
|
|
273
|
+
return results;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// Formatting
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
export function formatResults(results: CheckResult[]): string {
|
|
281
|
+
const lines: string[] = ["", "ashlr cloud smoke test", "─".repeat(40)];
|
|
282
|
+
for (const r of results) {
|
|
283
|
+
const icon = r.status === "pass" ? "✓" : r.status === "skip" ? "–" : "✗";
|
|
284
|
+
const ms = r.status !== "skip" ? ` ${r.ms}ms` : "";
|
|
285
|
+
const detail = r.detail ? ` (${r.detail})` : "";
|
|
286
|
+
lines.push(` ${icon} ${r.name}${ms}${detail}`);
|
|
287
|
+
}
|
|
288
|
+
lines.push("─".repeat(40));
|
|
289
|
+
|
|
290
|
+
const passed = results.filter((r) => r.status === "pass").length;
|
|
291
|
+
const failed = results.filter((r) => r.status === "fail").length;
|
|
292
|
+
const skipped = results.filter((r) => r.status === "skip").length;
|
|
293
|
+
lines.push(` ${passed} passed · ${failed} failed · ${skipped} skipped`);
|
|
294
|
+
lines.push("");
|
|
295
|
+
return lines.join("\n");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
299
|
+
// Main
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
if (import.meta.main) {
|
|
303
|
+
const apiUrl = process.env["ASHLR_API_URL"] ?? "https://api.ashlr.ai";
|
|
304
|
+
const proToken = process.env["ASHLR_PRO_TOKEN"];
|
|
305
|
+
|
|
306
|
+
const cfg: SmokeConfig = { apiUrl, proToken };
|
|
307
|
+
const results = await runAll(cfg);
|
|
308
|
+
|
|
309
|
+
process.stdout.write(formatResults(results));
|
|
310
|
+
|
|
311
|
+
const anyFail = results.some((r) => r.status === "fail");
|
|
312
|
+
process.exit(anyFail ? 1 : 0);
|
|
313
|
+
}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* ashlr coach report — reads ~/.ashlr/session-log.jsonl (+ rotated .1)
|
|
4
|
+
* and emits proactive nudges about tokens that could have been saved.
|
|
5
|
+
*
|
|
6
|
+
* Exported surface:
|
|
7
|
+
* buildCoachReport(opts?) -> formatted string, never throws.
|
|
8
|
+
*
|
|
9
|
+
* CLI: `bun run scripts/coach-report.ts [--days N]`
|
|
10
|
+
* Exits 0 always.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync } from "fs";
|
|
14
|
+
import { homedir } from "os";
|
|
15
|
+
import { basename, join, relative } from "path";
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Schema (matches session-log-report.ts LogRecord shape)
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
interface LogRecord {
|
|
22
|
+
ts: string;
|
|
23
|
+
agent: string;
|
|
24
|
+
event: string;
|
|
25
|
+
tool: string;
|
|
26
|
+
cwd: string;
|
|
27
|
+
session: string;
|
|
28
|
+
input_size: number;
|
|
29
|
+
output_size: number;
|
|
30
|
+
// session_end / TodoWrite extras
|
|
31
|
+
calls?: number;
|
|
32
|
+
tokens_saved?: number;
|
|
33
|
+
started_at?: string;
|
|
34
|
+
reason?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Options
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
export interface BuildCoachReportOpts {
|
|
42
|
+
/** Override home dir — used by tests. */
|
|
43
|
+
home?: string;
|
|
44
|
+
/** How many days back to scan (default: 7). */
|
|
45
|
+
days?: number;
|
|
46
|
+
/** Injected now in ms — tests pin this. */
|
|
47
|
+
now?: number;
|
|
48
|
+
/** Max lines per file (default: unlimited). */
|
|
49
|
+
limitLines?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// JSONL reader (same pattern as session-log-report.ts)
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
function readLines(path: string, limit?: number): string[] {
|
|
57
|
+
try {
|
|
58
|
+
if (!existsSync(path)) return [];
|
|
59
|
+
const raw = readFileSync(path, "utf-8");
|
|
60
|
+
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
61
|
+
return limit ? lines.slice(0, limit) : lines;
|
|
62
|
+
} catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseRecords(lines: string[]): LogRecord[] {
|
|
68
|
+
const out: LogRecord[] = [];
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
try {
|
|
71
|
+
const r = JSON.parse(line) as Partial<LogRecord>;
|
|
72
|
+
if (typeof r.ts !== "string" || typeof r.event !== "string") continue;
|
|
73
|
+
out.push({
|
|
74
|
+
ts: r.ts,
|
|
75
|
+
agent: r.agent ?? "unknown",
|
|
76
|
+
event: r.event,
|
|
77
|
+
tool: r.tool ?? "unknown",
|
|
78
|
+
cwd: r.cwd ?? "",
|
|
79
|
+
session: r.session ?? "",
|
|
80
|
+
input_size: typeof r.input_size === "number" ? r.input_size : 0,
|
|
81
|
+
output_size: typeof r.output_size === "number" ? r.output_size : 0,
|
|
82
|
+
calls: r.calls,
|
|
83
|
+
tokens_saved: r.tokens_saved,
|
|
84
|
+
started_at: r.started_at,
|
|
85
|
+
reason: r.reason,
|
|
86
|
+
});
|
|
87
|
+
} catch {
|
|
88
|
+
// skip malformed lines silently
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Formatting helpers
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
const WIDTH = 78;
|
|
99
|
+
|
|
100
|
+
function rule(): string {
|
|
101
|
+
return "-".repeat(WIDTH);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Bullets are emitted as single lines; terminal/renderer handles soft wrap.
|
|
105
|
+
// This keeps toContain checks in tests unambiguous across word boundaries.
|
|
106
|
+
function wrap(text: string): string {
|
|
107
|
+
return text;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function fmtKtok(tokens: number): string {
|
|
111
|
+
if (tokens < 1000) return `${tokens}tok`;
|
|
112
|
+
return `${(tokens / 1000).toFixed(1)}Ktok`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Rule checks
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
interface Bullet {
|
|
120
|
+
text: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Rule 1: Native Read on large files.
|
|
125
|
+
* tool === "Read" AND input_size > 2048.
|
|
126
|
+
* Potential savings = sum(input_size) * 0.6 / 4 (bytes -> tokens @ 4 bytes/tok,
|
|
127
|
+
* 60% reduction from ashlr__read's snipCompact).
|
|
128
|
+
*/
|
|
129
|
+
function checkNativeReadLarge(records: LogRecord[]): Bullet | null {
|
|
130
|
+
const hits = records.filter(
|
|
131
|
+
(r) => r.event === "tool_call" && r.tool === "Read" && r.input_size > 2048,
|
|
132
|
+
);
|
|
133
|
+
if (hits.length === 0) return null;
|
|
134
|
+
const totalBytes = hits.reduce((s, r) => s + r.input_size, 0);
|
|
135
|
+
const wastedTokens = Math.round((totalBytes * 0.6) / 4);
|
|
136
|
+
const n = hits.length;
|
|
137
|
+
return {
|
|
138
|
+
text: wrap(
|
|
139
|
+
`Used native Read on ${n} large file${n === 1 ? "" : "s"} — ` +
|
|
140
|
+
`~${fmtKtok(wastedTokens)} wasted. ` +
|
|
141
|
+
`Try /ashlr-allow or set ASHLR_ENFORCE=1.`,
|
|
142
|
+
),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Rule 2: Native Grep usage.
|
|
148
|
+
* tool === "Grep".
|
|
149
|
+
*/
|
|
150
|
+
function checkNativeGrep(records: LogRecord[]): Bullet | null {
|
|
151
|
+
const hits = records.filter(
|
|
152
|
+
(r) => r.event === "tool_call" && r.tool === "Grep",
|
|
153
|
+
);
|
|
154
|
+
if (hits.length === 0) return null;
|
|
155
|
+
const n = hits.length;
|
|
156
|
+
return {
|
|
157
|
+
text: wrap(
|
|
158
|
+
`Native Grep fired ${n} time${n === 1 ? "" : "s"} — pays full rg output each call. ` +
|
|
159
|
+
`ashlr__grep averages 5x smaller.`,
|
|
160
|
+
),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Rule 3: Long Bash outputs uncompressed.
|
|
166
|
+
* tool === "Bash" AND output_size > 16384.
|
|
167
|
+
*/
|
|
168
|
+
function checkLargeBashOutput(records: LogRecord[]): Bullet | null {
|
|
169
|
+
const hits = records.filter(
|
|
170
|
+
(r) => r.event === "tool_call" && r.tool === "Bash" && r.output_size > 16_384,
|
|
171
|
+
);
|
|
172
|
+
if (hits.length === 0) return null;
|
|
173
|
+
const m = hits.length;
|
|
174
|
+
// Approximate token class: largest output / 4
|
|
175
|
+
const maxOut = Math.max(...hits.map((r) => r.output_size));
|
|
176
|
+
const tokClass = fmtKtok(Math.round(maxOut / 4));
|
|
177
|
+
return {
|
|
178
|
+
text: wrap(
|
|
179
|
+
`Native Bash returned ${tokClass}-class output ${m} time${m === 1 ? "" : "s"} — ` +
|
|
180
|
+
`ashlr__bash auto-compresses.`,
|
|
181
|
+
),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Rule 4: No genome but heavy ashlr__grep usage.
|
|
187
|
+
* Find the project (cwd) with the most ashlr__grep calls.
|
|
188
|
+
* If that project has no .ashlrcode/genome/manifest.json, fire the bullet.
|
|
189
|
+
*/
|
|
190
|
+
function checkNoGenome(records: LogRecord[]): Bullet | null {
|
|
191
|
+
const grepRecords = records.filter(
|
|
192
|
+
(r) => r.event === "tool_call" && r.tool === "ashlr__grep" && r.cwd,
|
|
193
|
+
);
|
|
194
|
+
if (grepRecords.length === 0) return null;
|
|
195
|
+
|
|
196
|
+
// Tally per-project
|
|
197
|
+
const byCwd = new Map<string, number>();
|
|
198
|
+
for (const r of grepRecords) {
|
|
199
|
+
byCwd.set(r.cwd, (byCwd.get(r.cwd) ?? 0) + 1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Top project by ashlr__grep calls
|
|
203
|
+
let topCwd = "";
|
|
204
|
+
let topCount = 0;
|
|
205
|
+
for (const [cwd, count] of byCwd) {
|
|
206
|
+
if (count > topCount) {
|
|
207
|
+
topCount = count;
|
|
208
|
+
topCwd = cwd;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (topCount < 3) return null; // not "heavy" usage
|
|
213
|
+
|
|
214
|
+
const manifestPath = join(topCwd, ".ashlrcode", "genome", "manifest.json");
|
|
215
|
+
if (existsSync(manifestPath)) return null;
|
|
216
|
+
|
|
217
|
+
// Project display name: last path component (platform-agnostic)
|
|
218
|
+
const projectName = basename(topCwd) || topCwd;
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
text: wrap(
|
|
222
|
+
`Project ${projectName} has ${topCount} ashlr__grep calls but no genome — ` +
|
|
223
|
+
`run /ashlr-genome-init for ~4x better retrieval.`,
|
|
224
|
+
),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Rule 5: Repeated reads of the same file within one session.
|
|
230
|
+
* Within same session field, find paths (from input parsed as file path)
|
|
231
|
+
* that appear >= 3 times across Read/ashlr__read tool calls.
|
|
232
|
+
* We use the record's cwd + a stable key derived from input_size grouping as
|
|
233
|
+
* a proxy — but more precisely we look for same (session, tool, cwd) groups
|
|
234
|
+
* where input_size repeats. However, the log doesn't store the path argument
|
|
235
|
+
* directly. We detect repetition by grouping on (session, tool, cwd) with
|
|
236
|
+
* identical input_size as a proxy for "same file".
|
|
237
|
+
*
|
|
238
|
+
* Actually the spec says "count records where a path appears >= 3 times" but
|
|
239
|
+
* the path isn't in the log schema. We instead track (session, cwd, tool,
|
|
240
|
+
* input_size) as a file fingerprint — same size + same cwd + same session
|
|
241
|
+
* implies the same file was re-read.
|
|
242
|
+
*/
|
|
243
|
+
function checkRepeatedReads(records: LogRecord[]): Bullet | null {
|
|
244
|
+
const readTools = new Set(["Read", "ashlr__read"]);
|
|
245
|
+
const readRecords = records.filter(
|
|
246
|
+
(r) => r.event === "tool_call" && readTools.has(r.tool) && r.input_size > 0,
|
|
247
|
+
);
|
|
248
|
+
if (readRecords.length === 0) return null;
|
|
249
|
+
|
|
250
|
+
// Group by (session, cwd, input_size) as file fingerprint
|
|
251
|
+
const counts = new Map<string, number>();
|
|
252
|
+
for (const r of readRecords) {
|
|
253
|
+
const key = `${r.session}|${r.cwd}|${r.input_size}`;
|
|
254
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Find worst offender
|
|
258
|
+
let maxCount = 0;
|
|
259
|
+
let maxKey = "";
|
|
260
|
+
for (const [key, count] of counts) {
|
|
261
|
+
if (count > maxCount) {
|
|
262
|
+
maxCount = count;
|
|
263
|
+
maxKey = key;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (maxCount < 3) return null;
|
|
268
|
+
|
|
269
|
+
const [, cwd, sizeStr] = maxKey.split("|");
|
|
270
|
+
const sizeBytes = parseInt(sizeStr ?? "0", 10);
|
|
271
|
+
// Use cwd basename as proxy for file location descriptor
|
|
272
|
+
const projectName = basename(cwd ?? "") || (cwd ?? "unknown");
|
|
273
|
+
const sizeDesc = sizeBytes > 1024
|
|
274
|
+
? `~${(sizeBytes / 1024).toFixed(1)}K`
|
|
275
|
+
: `~${sizeBytes}B`;
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
text: wrap(
|
|
279
|
+
`A ${sizeDesc} file in ${projectName} was re-read ${maxCount} times in one session — ` +
|
|
280
|
+
`the 2nd+ reads were cache hits (free) but ashlr__read's in-process cache ` +
|
|
281
|
+
`only fires for exact mtime matches.`,
|
|
282
|
+
),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
// Public API
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
export function buildCoachReport(opts: BuildCoachReportOpts = {}): string {
|
|
291
|
+
const homeDir = opts.home ?? homedir();
|
|
292
|
+
const now = opts.now ?? Date.now();
|
|
293
|
+
const days = opts.days ?? 7;
|
|
294
|
+
const windowMs = days * 24 * 60 * 60 * 1000;
|
|
295
|
+
const windowStart = now - windowMs;
|
|
296
|
+
|
|
297
|
+
const logPath = join(homeDir, ".ashlr", "session-log.jsonl");
|
|
298
|
+
const rotatedPath = join(homeDir, ".ashlr", "session-log.jsonl.1");
|
|
299
|
+
|
|
300
|
+
const lines1 = readLines(rotatedPath, opts.limitLines);
|
|
301
|
+
const lines2 = readLines(logPath, opts.limitLines);
|
|
302
|
+
const allLines = [...lines1, ...lines2];
|
|
303
|
+
|
|
304
|
+
const allRecords = parseRecords(allLines);
|
|
305
|
+
|
|
306
|
+
// Filter to window
|
|
307
|
+
const records = allRecords.filter((r) => {
|
|
308
|
+
const ts = Date.parse(r.ts);
|
|
309
|
+
return Number.isFinite(ts) && ts >= windowStart;
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const scanned = allRecords.length;
|
|
313
|
+
|
|
314
|
+
const header =
|
|
315
|
+
`ashlr coach · last ${days} day${days === 1 ? "" : "s"} · ` +
|
|
316
|
+
`session-log records scanned: ${scanned}`;
|
|
317
|
+
|
|
318
|
+
const bullets: Bullet[] = [];
|
|
319
|
+
|
|
320
|
+
const r1 = checkNativeReadLarge(records);
|
|
321
|
+
if (r1) bullets.push(r1);
|
|
322
|
+
|
|
323
|
+
const r2 = checkNativeGrep(records);
|
|
324
|
+
if (r2) bullets.push(r2);
|
|
325
|
+
|
|
326
|
+
const r3 = checkLargeBashOutput(records);
|
|
327
|
+
if (r3) bullets.push(r3);
|
|
328
|
+
|
|
329
|
+
const r4 = checkNoGenome(records);
|
|
330
|
+
if (r4) bullets.push(r4);
|
|
331
|
+
|
|
332
|
+
const r5 = checkRepeatedReads(records);
|
|
333
|
+
if (r5) bullets.push(r5);
|
|
334
|
+
|
|
335
|
+
const footer =
|
|
336
|
+
"Run /ashlr-usage for full tool breakdown · /ashlr-savings for totals.";
|
|
337
|
+
|
|
338
|
+
const parts: string[] = [header, rule()];
|
|
339
|
+
|
|
340
|
+
if (bullets.length === 0) {
|
|
341
|
+
parts.push("No obvious improvements — you're using the plugin well.");
|
|
342
|
+
} else {
|
|
343
|
+
for (const b of bullets.slice(0, 5)) {
|
|
344
|
+
parts.push(`- ${b.text}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
parts.push(rule());
|
|
349
|
+
parts.push(footer);
|
|
350
|
+
|
|
351
|
+
return parts.join("\n");
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
// CLI entry
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
357
|
+
|
|
358
|
+
if (import.meta.main) {
|
|
359
|
+
try {
|
|
360
|
+
// Parse --days N flag
|
|
361
|
+
const args = process.argv.slice(2);
|
|
362
|
+
const daysIdx = args.indexOf("--days");
|
|
363
|
+
const days = daysIdx !== -1 && args[daysIdx + 1]
|
|
364
|
+
? parseInt(args[daysIdx + 1]!, 10)
|
|
365
|
+
: 7;
|
|
366
|
+
|
|
367
|
+
process.stdout.write(buildCoachReport({ days: isNaN(days) ? 7 : days }) + "\n");
|
|
368
|
+
} catch (err) {
|
|
369
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
370
|
+
process.stdout.write(`coach-report failed: ${msg}\n`);
|
|
371
|
+
}
|
|
372
|
+
process.exit(0);
|
|
373
|
+
}
|