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,96 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* pretooluse-grep.ts — Cross-platform replacement for pretooluse-grep.sh.
|
|
4
|
+
*
|
|
5
|
+
* v1.18: by default, this hook BLOCKS the native Grep tool and routes the
|
|
6
|
+
* agent to ashlr__grep (genome-aware RAG or truncated rg fallback). Set
|
|
7
|
+
* `ASHLR_HOOK_MODE=nudge` to downgrade to a soft `additionalContext`
|
|
8
|
+
* suggestion (the old v1.17 tool-redirect.ts behavior, absorbed into this
|
|
9
|
+
* hook after tool-redirect.ts was retired). Set `ASHLR_HOOK_MODE=off` — or
|
|
10
|
+
* `~/.ashlr/settings.json { toolRedirect: false }` — for total pass-through.
|
|
11
|
+
*
|
|
12
|
+
* Legacy: `ASHLR_ENFORCE=1` continues to use the exit-2 + stderr protocol
|
|
13
|
+
* for back-compat with existing harness configs and the hook-timings tests.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
buildNudgeContext,
|
|
18
|
+
buildPassThrough,
|
|
19
|
+
buildToolRedirectBlock,
|
|
20
|
+
enforcementDisabled,
|
|
21
|
+
flushHookTimings,
|
|
22
|
+
getHookModeFor,
|
|
23
|
+
isInsideCwd,
|
|
24
|
+
isInsidePluginRoot,
|
|
25
|
+
parsePayload,
|
|
26
|
+
pluginRootFrom,
|
|
27
|
+
readStdin,
|
|
28
|
+
recordHookTiming,
|
|
29
|
+
} from "./pretooluse-common";
|
|
30
|
+
import { installHookTimeout } from "./pretooluse-common";
|
|
31
|
+
|
|
32
|
+
if (import.meta.main) installHookTimeout("pretooluse-grep");
|
|
33
|
+
import { recordBlock } from "./_recent-blocks";
|
|
34
|
+
|
|
35
|
+
const hookStartedAt = Date.now();
|
|
36
|
+
|
|
37
|
+
async function exit(code: number, outcome: "ok" | "bypass" | "block" | "error", tool?: string): Promise<never> {
|
|
38
|
+
recordHookTiming({ hook: "pretooluse-grep", tool, durationMs: Date.now() - hookStartedAt, outcome });
|
|
39
|
+
await flushHookTimings();
|
|
40
|
+
process.exit(code);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const raw = await readStdin();
|
|
44
|
+
const payload = parsePayload(raw);
|
|
45
|
+
if (!payload) await exit(0, "ok");
|
|
46
|
+
|
|
47
|
+
const tool = payload!.tool_name || undefined;
|
|
48
|
+
if (payload!.tool_name !== "Grep") await exit(0, "ok", tool);
|
|
49
|
+
if (payload!.bypass) await exit(0, "bypass", tool);
|
|
50
|
+
|
|
51
|
+
const pluginRoot = pluginRootFrom(import.meta.url);
|
|
52
|
+
// Never redirect a Grep that's explicitly scoped inside the plugin tree —
|
|
53
|
+
// agents editing the plugin itself need direct access to rg behavior.
|
|
54
|
+
if (payload!.search_path && isInsidePluginRoot(payload!.search_path, pluginRoot)) {
|
|
55
|
+
await exit(0, "ok", tool);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Legacy back-compat: `ASHLR_ENFORCE=1` → exit-code-based block on stderr.
|
|
59
|
+
if (!enforcementDisabled()) {
|
|
60
|
+
const safePattern = payload!.pattern.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
61
|
+
process.stderr.write(
|
|
62
|
+
`ashlr: routing Grep through ashlr__grep for genome-aware retrieval (saves tokens when genome exists, truncates otherwise). Call ashlr__grep with pattern="${safePattern}". Set ASHLR_NO_ENFORCE=1 to disable this guard.\n`,
|
|
63
|
+
);
|
|
64
|
+
await exit(2, "block", tool);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// v1.18: default redirect mode. If the caller supplied a search_path that
|
|
68
|
+
// lies outside cwd, fall back to nudge — never block on paths the user
|
|
69
|
+
// didn't explicitly bring into scope. Grep without a path implicitly runs
|
|
70
|
+
// in cwd, which is fine to redirect.
|
|
71
|
+
const mode = getHookModeFor("grep");
|
|
72
|
+
const outOfScope = !!payload!.search_path && !isInsideCwd(payload!.search_path);
|
|
73
|
+
if (mode === "off") {
|
|
74
|
+
process.stdout.write(JSON.stringify(buildPassThrough()));
|
|
75
|
+
await exit(0, "ok", tool);
|
|
76
|
+
}
|
|
77
|
+
if (mode === "nudge" || outOfScope) {
|
|
78
|
+
const nudge = buildNudgeContext("Grep", { pattern: payload!.pattern });
|
|
79
|
+
process.stdout.write(JSON.stringify(nudge ?? buildPassThrough()));
|
|
80
|
+
await exit(0, "ok", tool);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Track G: record block for posttooluse-correlate (best-effort, never throws).
|
|
84
|
+
recordBlock({ ts: Date.now(), toolName: "Grep", pattern: payload!.pattern });
|
|
85
|
+
// JSON.stringify so Windows path backslashes (search_path) and any
|
|
86
|
+
// special characters in the pattern are escaped properly.
|
|
87
|
+
const argsJson = payload!.search_path
|
|
88
|
+
? JSON.stringify({ pattern: payload!.pattern, path: payload!.search_path })
|
|
89
|
+
: JSON.stringify({ pattern: payload!.pattern });
|
|
90
|
+
process.stdout.write(JSON.stringify(buildToolRedirectBlock({
|
|
91
|
+
mcpToolName: "mcp__plugin_ashlr_ashlr__ashlr__grep",
|
|
92
|
+
argsJson,
|
|
93
|
+
why: "native Grep returns ~10× more bytes; ashlr__grep is genome-aware and uses LLM summarization for large result sets.",
|
|
94
|
+
savingsPct: 80,
|
|
95
|
+
})));
|
|
96
|
+
await exit(0, "block", tool);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* pretooluse-notebookedit.ts — PreToolUse hook for the NotebookEdit tool.
|
|
4
|
+
*
|
|
5
|
+
* v1.22 (Track C): promoted from audit-only to redirect. The redirect target
|
|
6
|
+
* `ashlr__notebook_edit` now exists and returns a compressed cell-window
|
|
7
|
+
* response instead of echoing the full notebook state.
|
|
8
|
+
*
|
|
9
|
+
* Redirect applies only when:
|
|
10
|
+
* - hook mode is "redirect" (default)
|
|
11
|
+
* - the notebook path is inside cwd (same safety rule as pretooluse-edit)
|
|
12
|
+
*
|
|
13
|
+
* Pass-through when:
|
|
14
|
+
* - hook mode is "nudge" or "off"
|
|
15
|
+
* - path is outside cwd (can't redirect safely)
|
|
16
|
+
* - NotebookEdit is called on a non-existent file (edge case, let Claude Code handle it)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
buildNudgeContext,
|
|
21
|
+
buildPassThrough,
|
|
22
|
+
buildRedirectBlock,
|
|
23
|
+
flushHookTimings,
|
|
24
|
+
getHookModeFor,
|
|
25
|
+
isInsideCwd,
|
|
26
|
+
isInsidePluginRoot,
|
|
27
|
+
parsePayload,
|
|
28
|
+
pluginRootFrom,
|
|
29
|
+
readStdin,
|
|
30
|
+
recordHookTiming,
|
|
31
|
+
} from "./pretooluse-common";
|
|
32
|
+
import { installHookTimeout } from "./pretooluse-common";
|
|
33
|
+
|
|
34
|
+
if (import.meta.main) installHookTimeout("pretooluse-notebookedit");
|
|
35
|
+
|
|
36
|
+
const hookStartedAt = Date.now();
|
|
37
|
+
|
|
38
|
+
async function exit(code: number, outcome: "ok" | "bypass" | "block" | "error", tool?: string): Promise<never> {
|
|
39
|
+
recordHookTiming({ hook: "pretooluse-notebookedit", tool, durationMs: Date.now() - hookStartedAt, outcome });
|
|
40
|
+
await flushHookTimings();
|
|
41
|
+
process.exit(code);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const raw = await readStdin();
|
|
45
|
+
const payload = parsePayload(raw);
|
|
46
|
+
if (!payload) await exit(0, "ok");
|
|
47
|
+
|
|
48
|
+
const tool = payload!.tool_name || undefined;
|
|
49
|
+
if (payload!.tool_name !== "NotebookEdit") await exit(0, "ok", tool);
|
|
50
|
+
if (payload!.bypass) await exit(0, "bypass", tool);
|
|
51
|
+
|
|
52
|
+
// NotebookEdit uses `notebook_path` in its input — check both field names for
|
|
53
|
+
// robustness since the harness payload shape may vary across Claude Code versions.
|
|
54
|
+
const rawInput = (() => {
|
|
55
|
+
try {
|
|
56
|
+
const p = JSON.parse(raw);
|
|
57
|
+
return p?.tool_input ?? {};
|
|
58
|
+
} catch {
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
})();
|
|
62
|
+
const notebookPath: string =
|
|
63
|
+
typeof rawInput.notebook_path === "string"
|
|
64
|
+
? rawInput.notebook_path
|
|
65
|
+
: typeof rawInput.file_path === "string"
|
|
66
|
+
? rawInput.file_path
|
|
67
|
+
: "";
|
|
68
|
+
|
|
69
|
+
const pluginRoot = pluginRootFrom(import.meta.url);
|
|
70
|
+
if (isInsidePluginRoot(notebookPath, pluginRoot)) await exit(0, "ok", tool);
|
|
71
|
+
|
|
72
|
+
// NotebookEdit redirect target `ashlr__notebook_edit` exists as of v1.22. In
|
|
73
|
+
// redirect mode we block and route. In nudge mode we hint at the canonical
|
|
74
|
+
// MCP name. In off mode we pass through silently.
|
|
75
|
+
const mode = getHookModeFor("notebookedit");
|
|
76
|
+
if (mode === "off") {
|
|
77
|
+
process.stdout.write(JSON.stringify(buildPassThrough()));
|
|
78
|
+
await exit(0, "ok", tool);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (mode === "nudge") {
|
|
82
|
+
process.stdout.write(
|
|
83
|
+
JSON.stringify({
|
|
84
|
+
hookSpecificOutput: {
|
|
85
|
+
hookEventName: "PreToolUse",
|
|
86
|
+
additionalContext:
|
|
87
|
+
`[ashlr] For notebook cell edits, prefer mcp__plugin_ashlr_ashlr__ashlr__notebook_edit — ` +
|
|
88
|
+
`it applies the edit and returns only the edited cell and its immediate neighbors ` +
|
|
89
|
+
`instead of echoing the full notebook state. ` +
|
|
90
|
+
`Call shape: { "notebookPath": "<path>", "cellIndex": <n>, "newSource": "..." }.`,
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
await exit(0, "ok", tool);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// redirect mode: block and route to ashlr__notebook_edit.
|
|
98
|
+
if (!notebookPath || !isInsideCwd(notebookPath)) {
|
|
99
|
+
// Outside cwd or path unknown — nudge only, don't block.
|
|
100
|
+
process.stdout.write(
|
|
101
|
+
JSON.stringify({
|
|
102
|
+
hookSpecificOutput: {
|
|
103
|
+
hookEventName: "PreToolUse",
|
|
104
|
+
additionalContext:
|
|
105
|
+
`[ashlr] For notebook cell edits, prefer \`ashlr__notebook_edit\` — ` +
|
|
106
|
+
`it returns only a cell-window summary instead of the full notebook.`,
|
|
107
|
+
},
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
await exit(0, "ok", tool);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const mcp = "mcp__plugin_ashlr_ashlr__ashlr__notebook_edit";
|
|
114
|
+
const reason =
|
|
115
|
+
`[ashlr] To bypass: set ASHLR_HOOK_MODE=nudge in ~/.ashlr/config.json. ` +
|
|
116
|
+
`Current rule: blocking built-in NotebookEdit on ${notebookPath || "<notebook>"} — ` +
|
|
117
|
+
`call ${mcp} instead, which applies the cell edit and returns only the edited cell ` +
|
|
118
|
+
`plus one cell above and below (full notebook never echoed). ` +
|
|
119
|
+
`Equivalent call: { "notebookPath": "${notebookPath}", "cellIndex": <n>, "newSource": "..." }.`;
|
|
120
|
+
process.stdout.write(JSON.stringify(buildRedirectBlock(reason)));
|
|
121
|
+
await exit(0, "block", tool);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ashlr-plugin PreToolUse:Read enforcement hook.
|
|
3
|
+
#
|
|
4
|
+
# Blocks the built-in Read tool on files larger than 2KB and redirects the
|
|
5
|
+
# agent to ashlr__read, which returns a snipCompact-truncated view (head +
|
|
6
|
+
# tail, elided middle) saving ~60-95% of tokens on large files.
|
|
7
|
+
#
|
|
8
|
+
# Contract (Claude Code PreToolUse):
|
|
9
|
+
# stdin → JSON { tool_name, tool_input: { file_path, ... }, ... }
|
|
10
|
+
# exit 0 → allow the built-in call
|
|
11
|
+
# exit 2 → block; stderr is shown to the agent as a tool error
|
|
12
|
+
#
|
|
13
|
+
# Exceptions (pass-through):
|
|
14
|
+
# - File < 2 KB (no meaningful savings)
|
|
15
|
+
# - File does not exist or cannot be stat'd (let built-in produce the real error)
|
|
16
|
+
# - Path is inside this plugin's own source tree (dogfooding / self-edits)
|
|
17
|
+
# - $ASHLR_NO_ENFORCE=1 (global escape hatch)
|
|
18
|
+
# - tool_input.bypassSummary === true (explicit override by agent)
|
|
19
|
+
#
|
|
20
|
+
# Design notes:
|
|
21
|
+
# - Uses `bun -e` for JSON parsing (bun is already a plugin dependency).
|
|
22
|
+
# - Single stat call, no other subprocess work, typical runtime <30ms.
|
|
23
|
+
|
|
24
|
+
set -u
|
|
25
|
+
|
|
26
|
+
THRESHOLD=2048
|
|
27
|
+
|
|
28
|
+
# Enforcement is OFF by default. The hard-block via `exit 2` was too
|
|
29
|
+
# aggressive — it interrupted the user in `bypassPermissions` mode when all
|
|
30
|
+
# they wanted was a silent suggestion. The .ts replacement (pretooluse-read.ts)
|
|
31
|
+
# now covers both the redirect (JSON deny) and nudge (additionalContext)
|
|
32
|
+
# paths; this .sh is kept only for environments that can't run bun hooks.
|
|
33
|
+
#
|
|
34
|
+
# To re-enable hard enforcement (e.g. on CI where you want zero tolerance
|
|
35
|
+
# for full-file reads), set ASHLR_ENFORCE=1. `ASHLR_NO_ENFORCE=1` remains
|
|
36
|
+
# honored for backwards compatibility with anyone scripting against the
|
|
37
|
+
# old flag name.
|
|
38
|
+
if [ "${ASHLR_ENFORCE:-0}" != "1" ]; then
|
|
39
|
+
exit 0
|
|
40
|
+
fi
|
|
41
|
+
if [ "${ASHLR_NO_ENFORCE:-0}" = "1" ]; then
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Slurp stdin JSON; malformed or empty → pass-through.
|
|
46
|
+
STDIN_JSON=$(cat)
|
|
47
|
+
if [ -z "${STDIN_JSON// }" ]; then
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Parse tool_name, file_path, bypassSummary via bun. Output is tab-separated:
|
|
52
|
+
# <tool_name>\t<file_path>\t<bypassSummary>
|
|
53
|
+
PARSED=$(printf '%s' "$STDIN_JSON" | bun -e '
|
|
54
|
+
let raw = "";
|
|
55
|
+
process.stdin.on("data", (c) => (raw += c));
|
|
56
|
+
process.stdin.on("end", () => {
|
|
57
|
+
try {
|
|
58
|
+
const p = JSON.parse(raw);
|
|
59
|
+
const name = p?.tool_name ?? "";
|
|
60
|
+
const input = p?.tool_input ?? {};
|
|
61
|
+
const fp = typeof input.file_path === "string" ? input.file_path : "";
|
|
62
|
+
const bypass = input.bypassSummary === true ? "1" : "0";
|
|
63
|
+
process.stdout.write(`${name}\t${fp}\t${bypass}`);
|
|
64
|
+
} catch {
|
|
65
|
+
process.stdout.write("\t\t0");
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
' 2>/dev/null)
|
|
69
|
+
|
|
70
|
+
TOOL_NAME="${PARSED%%$'\t'*}"
|
|
71
|
+
REST="${PARSED#*$'\t'}"
|
|
72
|
+
FILE_PATH="${REST%%$'\t'*}"
|
|
73
|
+
BYPASS="${REST##*$'\t'}"
|
|
74
|
+
|
|
75
|
+
# Only enforce on Read. Any other tool name → pass-through (defensive).
|
|
76
|
+
if [ "$TOOL_NAME" != "Read" ]; then
|
|
77
|
+
exit 0
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# Missing or empty path → let built-in handle it.
|
|
81
|
+
if [ -z "$FILE_PATH" ]; then
|
|
82
|
+
exit 0
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Explicit bypass by caller.
|
|
86
|
+
if [ "$BYPASS" = "1" ]; then
|
|
87
|
+
exit 0
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# Plugin's own source tree is exempt (users editing the plugin itself).
|
|
91
|
+
PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
92
|
+
case "$FILE_PATH" in
|
|
93
|
+
"$PLUGIN_ROOT"/*) exit 0 ;;
|
|
94
|
+
esac
|
|
95
|
+
|
|
96
|
+
# File must exist and be a regular file to enforce; else pass-through.
|
|
97
|
+
if [ ! -f "$FILE_PATH" ]; then
|
|
98
|
+
exit 0
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# Portable stat: macOS uses -f%z, Linux uses -c%s.
|
|
102
|
+
SIZE=$(stat -f%z "$FILE_PATH" 2>/dev/null || stat -c%s "$FILE_PATH" 2>/dev/null || echo 0)
|
|
103
|
+
|
|
104
|
+
if [ -z "$SIZE" ] || [ "$SIZE" -le "$THRESHOLD" ] 2>/dev/null; then
|
|
105
|
+
exit 0
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# Rough token-savings estimate: snipCompact keeps ~head+tail (~1KB), so savings
|
|
109
|
+
# are approximately (SIZE - 1024) / 4 tokens (4 bytes/token avg).
|
|
110
|
+
SAVED_BYTES=$(( SIZE - 1024 ))
|
|
111
|
+
SAVED_TOKENS=$(( SAVED_BYTES / 4 ))
|
|
112
|
+
if [ "$SAVED_TOKENS" -lt 0 ]; then SAVED_TOKENS=0; fi
|
|
113
|
+
|
|
114
|
+
# Block and redirect.
|
|
115
|
+
cat >&2 <<EOF
|
|
116
|
+
ashlr: refusing full Read of $FILE_PATH ($SIZE bytes). Call ashlr__read instead for snipCompact truncation — saves ~${SAVED_TOKENS} tokens. Pass bypassSummary: true on ashlr__read if you truly need the raw file. Set ASHLR_NO_ENFORCE=1 to disable this guard.
|
|
117
|
+
EOF
|
|
118
|
+
exit 2
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* pretooluse-read.ts — Cross-platform replacement for pretooluse-read.sh.
|
|
4
|
+
*
|
|
5
|
+
* v1.18: by default, this hook BLOCKS the native Read tool for files larger
|
|
6
|
+
* than the snipCompact threshold and routes the agent to ashlr__read instead
|
|
7
|
+
* ("redirect" mode). Set `ASHLR_HOOK_MODE=nudge` to fall back to a soft
|
|
8
|
+
* `additionalContext` suggestion (the old v1.17 tool-redirect.ts behavior,
|
|
9
|
+
* since absorbed into this hook after tool-redirect.ts was retired). Set
|
|
10
|
+
* `ASHLR_HOOK_MODE=off` — or `~/.ashlr/settings.json { toolRedirect: false }`
|
|
11
|
+
* — for total pass-through with no nudge at all.
|
|
12
|
+
*
|
|
13
|
+
* Contract (Claude Code PreToolUse):
|
|
14
|
+
* stdin -> JSON { tool_name, tool_input: { file_path, ... }, ... }
|
|
15
|
+
* stdout -> JSON { hookSpecificOutput: {
|
|
16
|
+
* hookEventName: "PreToolUse",
|
|
17
|
+
* permissionDecision: "deny",
|
|
18
|
+
* permissionDecisionReason: "..."
|
|
19
|
+
* } } (redirect mode, blocks)
|
|
20
|
+
* stdout -> JSON { hookSpecificOutput: {
|
|
21
|
+
* hookEventName: "PreToolUse",
|
|
22
|
+
* additionalContext: "..."
|
|
23
|
+
* } } (nudge mode)
|
|
24
|
+
* stdout -> JSON { hookSpecificOutput: { hookEventName: "PreToolUse" } }
|
|
25
|
+
* (off mode / pass-through)
|
|
26
|
+
* exit 0 -> allow / proceed (with stdout JSON above, if any)
|
|
27
|
+
* exit 2 -> legacy block (stderr is shown as a tool error). Only taken
|
|
28
|
+
* when `ASHLR_ENFORCE=1` is set — preserved for back-compat with
|
|
29
|
+
* harness configurations and the hook-timings test suite.
|
|
30
|
+
*
|
|
31
|
+
* Safety: we never block on files outside `cwd` or inside CLAUDE_PLUGIN_ROOT.
|
|
32
|
+
* Those fall through to a silent pass-through even in redirect mode, so the
|
|
33
|
+
* agent can edit/read plugin internals and other-project files without being
|
|
34
|
+
* rerouted into ashlr__*.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import {
|
|
38
|
+
buildNudgeContext,
|
|
39
|
+
buildPassThrough,
|
|
40
|
+
buildToolRedirectBlock,
|
|
41
|
+
enforcementDisabled,
|
|
42
|
+
fileSize,
|
|
43
|
+
flushHookTimings,
|
|
44
|
+
getHookModeFor,
|
|
45
|
+
isInsideCwd,
|
|
46
|
+
isInsidePluginRoot,
|
|
47
|
+
parsePayload,
|
|
48
|
+
pluginRootFrom,
|
|
49
|
+
readStdin,
|
|
50
|
+
recordHookTiming,
|
|
51
|
+
} from "./pretooluse-common";
|
|
52
|
+
import { installHookTimeout } from "./pretooluse-common";
|
|
53
|
+
|
|
54
|
+
if (import.meta.main) installHookTimeout("pretooluse-read");
|
|
55
|
+
import { recordBlock } from "./_recent-blocks";
|
|
56
|
+
|
|
57
|
+
const THRESHOLD = 2048;
|
|
58
|
+
const hookStartedAt = Date.now();
|
|
59
|
+
|
|
60
|
+
async function exit(code: number, outcome: "ok" | "bypass" | "block" | "error", tool?: string): Promise<never> {
|
|
61
|
+
recordHookTiming({ hook: "pretooluse-read", tool, durationMs: Date.now() - hookStartedAt, outcome });
|
|
62
|
+
await flushHookTimings();
|
|
63
|
+
process.exit(code);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const raw = await readStdin();
|
|
67
|
+
const payload = parsePayload(raw);
|
|
68
|
+
if (!payload) await exit(0, "ok");
|
|
69
|
+
|
|
70
|
+
const tool = payload!.tool_name || undefined;
|
|
71
|
+
if (payload!.tool_name !== "Read") await exit(0, "ok", tool);
|
|
72
|
+
if (!payload!.file_path) await exit(0, "ok", tool);
|
|
73
|
+
if (payload!.bypass) await exit(0, "bypass", tool);
|
|
74
|
+
|
|
75
|
+
const pluginRoot = pluginRootFrom(import.meta.url);
|
|
76
|
+
// Never redirect reads inside the plugin's own tree — agents editing the
|
|
77
|
+
// ashlr-plugin itself need direct access without being rerouted.
|
|
78
|
+
if (isInsidePluginRoot(payload!.file_path, pluginRoot)) await exit(0, "ok", tool);
|
|
79
|
+
|
|
80
|
+
const size = fileSize(payload!.file_path);
|
|
81
|
+
if (size === null) await exit(0, "ok", tool);
|
|
82
|
+
if (size! <= THRESHOLD) await exit(0, "ok", tool);
|
|
83
|
+
|
|
84
|
+
// Legacy back-compat: `ASHLR_ENFORCE=1` → exit-code-based block on stderr.
|
|
85
|
+
if (!enforcementDisabled()) {
|
|
86
|
+
const savedTokens = Math.max(0, Math.floor((size! - 1024) / 4));
|
|
87
|
+
process.stderr.write(
|
|
88
|
+
`ashlr: refusing full Read of ${payload!.file_path} (${size} bytes). Call ashlr__read instead for snipCompact truncation — saves ~${savedTokens} tokens. Pass bypassSummary: true on ashlr__read if you truly need the raw file. Set ASHLR_NO_ENFORCE=1 to disable this guard.\n`,
|
|
89
|
+
);
|
|
90
|
+
await exit(2, "block", tool);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// v1.18: default redirect mode. Fall back to nudge when the file lies
|
|
94
|
+
// outside cwd (e.g. /tmp, unrelated repos) — we only block paths the user
|
|
95
|
+
// has actually brought into scope.
|
|
96
|
+
const mode = getHookModeFor("read");
|
|
97
|
+
if (mode === "off") {
|
|
98
|
+
process.stdout.write(JSON.stringify(buildPassThrough()));
|
|
99
|
+
await exit(0, "ok", tool);
|
|
100
|
+
}
|
|
101
|
+
if (mode === "nudge" || !isInsideCwd(payload!.file_path)) {
|
|
102
|
+
const nudge = buildNudgeContext("Read", { file_path: payload!.file_path });
|
|
103
|
+
process.stdout.write(JSON.stringify(nudge ?? buildPassThrough()));
|
|
104
|
+
await exit(0, "ok", tool);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const savedTokens = Math.max(0, Math.floor((size! - 1024) / 4));
|
|
108
|
+
const savingsPct = Math.min(90, Math.round((savedTokens / Math.max(1, size! / 4)) * 100));
|
|
109
|
+
// Track G: record block for posttooluse-correlate (best-effort, never throws).
|
|
110
|
+
recordBlock({ ts: Date.now(), toolName: "Read", filePath: payload!.file_path });
|
|
111
|
+
process.stdout.write(JSON.stringify(buildToolRedirectBlock({
|
|
112
|
+
mcpToolName: "mcp__plugin_ashlr_ashlr__ashlr__read",
|
|
113
|
+
// JSON.stringify so Windows path backslashes are escaped properly.
|
|
114
|
+
argsJson: JSON.stringify({ path: payload!.file_path }),
|
|
115
|
+
why: `native Read returns all ${size} bytes; ashlr__read returns snipCompact-truncated view (head + tail, elided middle), saving ~${savedTokens} tokens. Pass bypassSummary:true to get the full file.`,
|
|
116
|
+
savingsPct,
|
|
117
|
+
})));
|
|
118
|
+
await exit(0, "block", tool);
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* pretooluse-task.ts — PreToolUse hook for TaskList and TaskGet tools.
|
|
4
|
+
*
|
|
5
|
+
* In redirect mode (default) this hook BLOCKS the native TaskList/TaskGet
|
|
6
|
+
* calls and routes the agent to ashlr__task_list / ashlr__task_get, which
|
|
7
|
+
* filter, paginate, and truncate task payloads to save tokens.
|
|
8
|
+
*
|
|
9
|
+
* TaskCreate and TaskUpdate are NOT intercepted — they're tiny inputs.
|
|
10
|
+
*
|
|
11
|
+
* Escape valves (in order of granularity):
|
|
12
|
+
* - `ASHLR_TASK_PASSTHROUGH=1` env — pass through TaskList/TaskGet for this
|
|
13
|
+
* invocation only. Use from skills like `/deep-work` that orchestrate
|
|
14
|
+
* many subagents and need the raw, full task list.
|
|
15
|
+
* - `~/.ashlr/settings.json { "hookModes": { "task": "nudge" } }` — soft
|
|
16
|
+
* suggestion instead of block, permanently.
|
|
17
|
+
* - `ASHLR_HOOK_MODE=nudge` env — same as above but global (all hooks).
|
|
18
|
+
* - `ASHLR_HOOK_MODE=off` (or `settings.json { toolRedirect: false }`) —
|
|
19
|
+
* total pass-through, all hooks.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
buildPassThrough,
|
|
24
|
+
buildRedirectBlock,
|
|
25
|
+
flushHookTimings,
|
|
26
|
+
getHookModeFor,
|
|
27
|
+
installHookTimeout,
|
|
28
|
+
parsePayload,
|
|
29
|
+
readStdin,
|
|
30
|
+
recordHookTiming,
|
|
31
|
+
} from "./pretooluse-common";
|
|
32
|
+
|
|
33
|
+
if (import.meta.main) installHookTimeout("pretooluse-task");
|
|
34
|
+
|
|
35
|
+
const hookStartedAt = Date.now();
|
|
36
|
+
|
|
37
|
+
async function exit(code: number, outcome: "ok" | "bypass" | "block" | "error", tool?: string): Promise<never> {
|
|
38
|
+
recordHookTiming({ hook: "pretooluse-task", tool, durationMs: Date.now() - hookStartedAt, outcome });
|
|
39
|
+
await flushHookTimings();
|
|
40
|
+
process.exit(code);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const raw = await readStdin();
|
|
44
|
+
const payload = parsePayload(raw);
|
|
45
|
+
if (!payload) await exit(0, "ok");
|
|
46
|
+
|
|
47
|
+
const toolName = payload!.tool_name;
|
|
48
|
+
const tool = toolName || undefined;
|
|
49
|
+
|
|
50
|
+
if (toolName !== "TaskList" && toolName !== "TaskGet") await exit(0, "ok", tool);
|
|
51
|
+
if (payload!.bypass) await exit(0, "bypass", tool);
|
|
52
|
+
|
|
53
|
+
// Per-invocation escape valve. Skills that legitimately need the full task
|
|
54
|
+
// list (e.g. multi-agent orchestrators like /deep-work) can set this in their
|
|
55
|
+
// prelude before issuing TaskList/TaskGet.
|
|
56
|
+
if (process.env.ASHLR_TASK_PASSTHROUGH === "1") {
|
|
57
|
+
process.stdout.write(JSON.stringify(buildPassThrough()));
|
|
58
|
+
await exit(0, "bypass", tool);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const mode = getHookModeFor("task");
|
|
62
|
+
if (mode === "off") {
|
|
63
|
+
process.stdout.write(JSON.stringify(buildPassThrough()));
|
|
64
|
+
await exit(0, "ok", tool);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Parse tool input for a more helpful redirect message.
|
|
68
|
+
let taskId = "<taskId>";
|
|
69
|
+
let status = "<status>";
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(raw) as { tool_input?: { task_id?: string; taskId?: string; status?: string } };
|
|
72
|
+
const input = parsed?.tool_input ?? {};
|
|
73
|
+
if (typeof input.task_id === "string") taskId = input.task_id;
|
|
74
|
+
if (typeof input.taskId === "string") taskId = input.taskId;
|
|
75
|
+
if (typeof input.status === "string") status = input.status;
|
|
76
|
+
} catch {
|
|
77
|
+
/* ignore */
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (mode === "nudge") {
|
|
81
|
+
const ashlrTool = toolName === "TaskList" ? "ashlr__task_list" : "ashlr__task_get";
|
|
82
|
+
const exampleCall =
|
|
83
|
+
toolName === "TaskList"
|
|
84
|
+
? `{ "status": ${JSON.stringify(status !== "<status>" ? status : undefined)} }`
|
|
85
|
+
: `{ "taskId": ${JSON.stringify(taskId)} }`;
|
|
86
|
+
process.stdout.write(
|
|
87
|
+
JSON.stringify({
|
|
88
|
+
hookSpecificOutput: {
|
|
89
|
+
hookEventName: "PreToolUse",
|
|
90
|
+
additionalContext:
|
|
91
|
+
`[ashlr] Prefer the MCP tool \`${ashlrTool}\` over the built-in ${toolName}. ` +
|
|
92
|
+
(toolName === "TaskList"
|
|
93
|
+
? "It filters by status/owner, limits to 30 rows by default, and returns a compact column view (taskId, status, subject, ageMin). "
|
|
94
|
+
: "It snipCompacts descriptions > 2KB and returns a compact structured view. ") +
|
|
95
|
+
`Call it with ${exampleCall}.`,
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
await exit(0, "ok", tool);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Default: redirect mode.
|
|
103
|
+
const escapeValves =
|
|
104
|
+
`Escape valves: ASHLR_TASK_PASSTHROUGH=1 (one-shot pass-through, ` +
|
|
105
|
+
`intended for skills like /deep-work that need the full list); ` +
|
|
106
|
+
`~/.ashlr/settings.json { "hookModes": { "task": "nudge" } } (soft hint ` +
|
|
107
|
+
`instead of block); ASHLR_HOOK_MODE=nudge (global soft hint).`;
|
|
108
|
+
|
|
109
|
+
let reason: string;
|
|
110
|
+
if (toolName === "TaskList") {
|
|
111
|
+
const safeStatus = status.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
112
|
+
reason =
|
|
113
|
+
`[ashlr] Blocking the built-in TaskList. Call ` +
|
|
114
|
+
`mcp__plugin_ashlr_ashlr__ashlr__task_list instead — it filters by status/owner, ` +
|
|
115
|
+
`limits to 30 rows by default, and returns a compact column view (taskId, status, ` +
|
|
116
|
+
`subject, ageMin) saving 50-80% tokens on large task lists. ` +
|
|
117
|
+
`Equivalent call: { "status": "${safeStatus}" }. ` +
|
|
118
|
+
escapeValves;
|
|
119
|
+
} else {
|
|
120
|
+
const safeTaskId = taskId.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
121
|
+
reason =
|
|
122
|
+
`[ashlr] Blocking the built-in TaskGet. Call ` +
|
|
123
|
+
`mcp__plugin_ashlr_ashlr__ashlr__task_get instead — it snipCompacts descriptions ` +
|
|
124
|
+
`> 2KB (head + tail with elision marker) to save tokens on tasks with long bodies. ` +
|
|
125
|
+
`Equivalent call: { "taskId": "${safeTaskId}" }. ` +
|
|
126
|
+
escapeValves;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
process.stdout.write(JSON.stringify(buildRedirectBlock(reason)));
|
|
130
|
+
await exit(0, "block", tool);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* pretooluse-webfetch.ts — PreToolUse hook for the WebFetch tool.
|
|
4
|
+
*
|
|
5
|
+
* WebFetch has no ashlr__* equivalent. This hook provides audit-trail
|
|
6
|
+
* coverage so every WebFetch call appears in hook-timings.jsonl. In nudge
|
|
7
|
+
* mode it reminds the agent that ashlr__webfetch compresses large HTML
|
|
8
|
+
* responses, returning only the main content with the boilerplate stripped.
|
|
9
|
+
*
|
|
10
|
+
* Never blocks in any mode — WebFetch is a read-only network tool and the
|
|
11
|
+
* agent must always be able to fetch documentation or API responses.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
buildPassThrough,
|
|
16
|
+
flushHookTimings,
|
|
17
|
+
getHookModeFor,
|
|
18
|
+
parsePayload,
|
|
19
|
+
readStdin,
|
|
20
|
+
recordHookTiming,
|
|
21
|
+
} from "./pretooluse-common";
|
|
22
|
+
import { installHookTimeout } from "./pretooluse-common";
|
|
23
|
+
|
|
24
|
+
if (import.meta.main) installHookTimeout("pretooluse-webfetch");
|
|
25
|
+
|
|
26
|
+
const hookStartedAt = Date.now();
|
|
27
|
+
|
|
28
|
+
async function exit(code: number, outcome: "ok" | "bypass" | "block" | "error", tool?: string): Promise<never> {
|
|
29
|
+
recordHookTiming({ hook: "pretooluse-webfetch", tool, durationMs: Date.now() - hookStartedAt, outcome });
|
|
30
|
+
await flushHookTimings();
|
|
31
|
+
process.exit(code);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const raw = await readStdin();
|
|
35
|
+
const payload = parsePayload(raw);
|
|
36
|
+
if (!payload) await exit(0, "ok");
|
|
37
|
+
|
|
38
|
+
const tool = payload!.tool_name || undefined;
|
|
39
|
+
if (payload!.tool_name !== "WebFetch") await exit(0, "ok", tool);
|
|
40
|
+
|
|
41
|
+
const mode = getHookModeFor("webfetch");
|
|
42
|
+
if (mode === "nudge") {
|
|
43
|
+
let url = "<url>";
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(raw) as { tool_input?: { url?: string } };
|
|
46
|
+
if (typeof parsed?.tool_input?.url === "string") url = parsed.tool_input.url;
|
|
47
|
+
} catch {
|
|
48
|
+
/* ignore */
|
|
49
|
+
}
|
|
50
|
+
process.stdout.write(
|
|
51
|
+
JSON.stringify({
|
|
52
|
+
hookSpecificOutput: {
|
|
53
|
+
hookEventName: "PreToolUse",
|
|
54
|
+
additionalContext:
|
|
55
|
+
`[ashlr] Prefer the MCP tool \`ashlr__webfetch\` over the built-in WebFetch. ` +
|
|
56
|
+
`It strips HTML boilerplate and returns only the main content, saving tokens ` +
|
|
57
|
+
`on documentation pages and API references. ` +
|
|
58
|
+
`Call it with { "url": ${JSON.stringify(url)} }.`,
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
await exit(0, "ok", tool);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
process.stdout.write(JSON.stringify(buildPassThrough()));
|
|
66
|
+
await exit(0, "ok", tool);
|