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,242 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* ashlr orient-nudge PostToolUse hook.
|
|
4
|
+
*
|
|
5
|
+
* Detects the "orientation" anti-pattern: 3+ Read/Grep calls inside a rolling
|
|
6
|
+
* 60s window without an intervening Edit/Write. When detected, nudges the
|
|
7
|
+
* agent to use the `ashlr__orient` tool, which folds tree scan + grep +
|
|
8
|
+
* selective reads into a single synthesized call.
|
|
9
|
+
*
|
|
10
|
+
* State lives in ~/.ashlr/orient-state.json:
|
|
11
|
+
* {
|
|
12
|
+
* "pid": 12345, // session-scoped: resets when PID changes
|
|
13
|
+
* "events": [{ "tool": "Read", "at": 171.. }, ...],
|
|
14
|
+
* "lastNudgeAt": 171.. // unix-ms of last nudge (for debounce)
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* Hook contract (PostToolUse):
|
|
18
|
+
* stdin → { tool_name, tool_input?, ... }
|
|
19
|
+
* stdout → { hookSpecificOutput: { hookEventName: "PostToolUse",
|
|
20
|
+
* additionalContext?: string } }
|
|
21
|
+
*
|
|
22
|
+
* Trigger:
|
|
23
|
+
* - On Edit/Write/ashlr__edit: clear events (user is now editing, not
|
|
24
|
+
* orienting). Never nudges on these.
|
|
25
|
+
* - On Read/Grep/ashlr__read/ashlr__grep: append event, prune to last 60s.
|
|
26
|
+
* If window has ≥3 events AND we haven't nudged in past 5 minutes → nudge.
|
|
27
|
+
* Note: we fire regardless of whether the grep returned results — the
|
|
28
|
+
* signal is "agent is scanning" not "agent found something". That's the
|
|
29
|
+
* whole point: empty greps are the most expensive kind of orienting.
|
|
30
|
+
*
|
|
31
|
+
* Debounce: after firing, set lastNudgeAt; suppress further nudges for 5 min
|
|
32
|
+
* even if the agent keeps Read/Grep'ing. The window continues filling up so
|
|
33
|
+
* once the 5min elapses, if they're still orienting, they get nudged again.
|
|
34
|
+
*
|
|
35
|
+
* Opt-out: { "ashlr": { "orientNudge": false } } in ~/.claude/settings.json.
|
|
36
|
+
*
|
|
37
|
+
* Design rules:
|
|
38
|
+
* - Never throw. Malformed input, fs errors, malformed settings → pass-through.
|
|
39
|
+
* - Only Read/Grep/ashlr__read/ashlr__grep append events. Edit/Write/
|
|
40
|
+
* ashlr__edit clear. Other tools → pass-through untouched.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
44
|
+
import { homedir } from "os";
|
|
45
|
+
import { dirname, join } from "path";
|
|
46
|
+
|
|
47
|
+
import { noteHookError } from "./_hook-errors";
|
|
48
|
+
|
|
49
|
+
export const READ_TOOL_NAMES = new Set([
|
|
50
|
+
"Read",
|
|
51
|
+
"Grep",
|
|
52
|
+
"mcp__ashlr-efficiency__ashlr__read",
|
|
53
|
+
"mcp__ashlr-efficiency__ashlr__grep",
|
|
54
|
+
"mcp__plugin_ashlr_ashlr__ashlr__read",
|
|
55
|
+
"mcp__plugin_ashlr_ashlr__ashlr__grep",
|
|
56
|
+
"ashlr__read",
|
|
57
|
+
"ashlr__grep",
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
export const EDIT_TOOL_NAMES = new Set([
|
|
61
|
+
"Edit",
|
|
62
|
+
"Write",
|
|
63
|
+
"MultiEdit",
|
|
64
|
+
"mcp__ashlr-efficiency__ashlr__edit",
|
|
65
|
+
"mcp__plugin_ashlr_ashlr__ashlr__edit",
|
|
66
|
+
"mcp__plugin_ashlr_ashlr__ashlr__multi_edit",
|
|
67
|
+
"mcp__plugin_ashlr_ashlr__ashlr__edit_structural",
|
|
68
|
+
"ashlr__edit",
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
export const WINDOW_MS = 60_000;
|
|
72
|
+
export const NUDGE_THRESHOLD = 3;
|
|
73
|
+
export const DEBOUNCE_MS = 5 * 60_000;
|
|
74
|
+
|
|
75
|
+
export const NUDGE_MESSAGE =
|
|
76
|
+
"[ashlr] You've done 3+ Read/Grep calls in the last minute without editing. " +
|
|
77
|
+
"If you're orienting, try `ashlr__orient` with a natural-language query — " +
|
|
78
|
+
"it runs a tree scan + grep + selective reads in one call with a synthesized answer.";
|
|
79
|
+
|
|
80
|
+
interface PostToolUsePayload {
|
|
81
|
+
tool_name?: string;
|
|
82
|
+
tool_input?: Record<string, unknown>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface OrientEvent {
|
|
86
|
+
tool: string;
|
|
87
|
+
at: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface OrientState {
|
|
91
|
+
pid: number;
|
|
92
|
+
events: OrientEvent[];
|
|
93
|
+
lastNudgeAt?: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface HookOutput {
|
|
97
|
+
hookSpecificOutput: {
|
|
98
|
+
hookEventName: "PostToolUse";
|
|
99
|
+
additionalContext?: string;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function passThrough(): HookOutput {
|
|
104
|
+
return { hookSpecificOutput: { hookEventName: "PostToolUse" } };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function statePath(home: string = homedir()): string {
|
|
108
|
+
return join(home, ".ashlr", "orient-state.json");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function settingsPath(home: string = homedir()): string {
|
|
112
|
+
return join(home, ".claude", "settings.json");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function isOptedOut(home: string = homedir()): boolean {
|
|
116
|
+
try {
|
|
117
|
+
const p = settingsPath(home);
|
|
118
|
+
if (!existsSync(p)) return false;
|
|
119
|
+
const raw = JSON.parse(readFileSync(p, "utf-8")) as {
|
|
120
|
+
ashlr?: { orientNudge?: unknown };
|
|
121
|
+
};
|
|
122
|
+
return raw?.ashlr?.orientNudge === false;
|
|
123
|
+
} catch (e) {
|
|
124
|
+
noteHookError("orient-nudge", "settings-parse", e);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function loadState(path: string, currentPid: number): OrientState {
|
|
130
|
+
try {
|
|
131
|
+
if (!existsSync(path)) return { pid: currentPid, events: [] };
|
|
132
|
+
const raw = JSON.parse(readFileSync(path, "utf-8")) as Partial<OrientState>;
|
|
133
|
+
if (typeof raw.pid !== "number" || raw.pid !== currentPid) {
|
|
134
|
+
return { pid: currentPid, events: [] };
|
|
135
|
+
}
|
|
136
|
+
const events = Array.isArray(raw.events)
|
|
137
|
+
? raw.events.filter(
|
|
138
|
+
(e): e is OrientEvent =>
|
|
139
|
+
!!e &&
|
|
140
|
+
typeof (e as OrientEvent).tool === "string" &&
|
|
141
|
+
typeof (e as OrientEvent).at === "number",
|
|
142
|
+
)
|
|
143
|
+
: [];
|
|
144
|
+
const lastNudgeAt =
|
|
145
|
+
typeof raw.lastNudgeAt === "number" ? raw.lastNudgeAt : undefined;
|
|
146
|
+
return { pid: currentPid, events, lastNudgeAt };
|
|
147
|
+
} catch (e) {
|
|
148
|
+
noteHookError("orient-nudge", "load-state", e);
|
|
149
|
+
return { pid: currentPid, events: [] };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function saveState(path: string, state: OrientState): void {
|
|
154
|
+
try {
|
|
155
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
156
|
+
writeFileSync(path, JSON.stringify(state));
|
|
157
|
+
} catch (e) {
|
|
158
|
+
noteHookError("orient-nudge", "save-state", e);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface DecideOpts {
|
|
163
|
+
home?: string;
|
|
164
|
+
pid?: number;
|
|
165
|
+
now?: number;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function decide(
|
|
169
|
+
payload: PostToolUsePayload,
|
|
170
|
+
opts: DecideOpts = {},
|
|
171
|
+
): HookOutput {
|
|
172
|
+
const name = payload?.tool_name;
|
|
173
|
+
if (!name) return passThrough();
|
|
174
|
+
|
|
175
|
+
const home = opts.home ?? homedir();
|
|
176
|
+
const pid = opts.pid ?? process.pid;
|
|
177
|
+
const now = opts.now ?? Date.now();
|
|
178
|
+
|
|
179
|
+
if (isOptedOut(home)) return passThrough();
|
|
180
|
+
|
|
181
|
+
const path = statePath(home);
|
|
182
|
+
const state = loadState(path, pid);
|
|
183
|
+
|
|
184
|
+
// Edit/Write tools end the orientation phase — clear events.
|
|
185
|
+
if (EDIT_TOOL_NAMES.has(name)) {
|
|
186
|
+
saveState(path, { pid, events: [], lastNudgeAt: state.lastNudgeAt });
|
|
187
|
+
return passThrough();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Not a tool we care about.
|
|
191
|
+
if (!READ_TOOL_NAMES.has(name)) return passThrough();
|
|
192
|
+
|
|
193
|
+
// Prune stale events and record this one.
|
|
194
|
+
const fresh = state.events.filter((e) => now - e.at <= WINDOW_MS);
|
|
195
|
+
fresh.push({ tool: name, at: now });
|
|
196
|
+
|
|
197
|
+
const debounced =
|
|
198
|
+
typeof state.lastNudgeAt === "number" &&
|
|
199
|
+
now - state.lastNudgeAt < DEBOUNCE_MS;
|
|
200
|
+
|
|
201
|
+
if (fresh.length >= NUDGE_THRESHOLD && !debounced) {
|
|
202
|
+
saveState(path, { pid, events: fresh, lastNudgeAt: now });
|
|
203
|
+
return {
|
|
204
|
+
hookSpecificOutput: {
|
|
205
|
+
hookEventName: "PostToolUse",
|
|
206
|
+
additionalContext: NUDGE_MESSAGE,
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
saveState(path, { pid, events: fresh, lastNudgeAt: state.lastNudgeAt });
|
|
212
|
+
return passThrough();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function readStdin(): Promise<string> {
|
|
216
|
+
const chunks: Buffer[] = [];
|
|
217
|
+
for await (const chunk of process.stdin as AsyncIterable<Buffer | string>) {
|
|
218
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
219
|
+
}
|
|
220
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function main(): Promise<void> {
|
|
224
|
+
let payload: PostToolUsePayload = {};
|
|
225
|
+
try {
|
|
226
|
+
const raw = await readStdin();
|
|
227
|
+
if (raw.trim()) payload = JSON.parse(raw) as PostToolUsePayload;
|
|
228
|
+
} catch {
|
|
229
|
+
process.stdout.write(JSON.stringify(passThrough()));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
process.stdout.write(JSON.stringify(decide(payload)));
|
|
234
|
+
} catch (e) {
|
|
235
|
+
noteHookError("orient-nudge", "decide", e);
|
|
236
|
+
process.stdout.write(JSON.stringify(passThrough()));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (import.meta.main) {
|
|
241
|
+
await main();
|
|
242
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* policy-enforce.ts — PreToolUse hook.
|
|
4
|
+
*
|
|
5
|
+
* Runs before every Edit, Write, Bash, MultiEdit call (and ashlr equivalents).
|
|
6
|
+
* Fetches /policy/current (5-min in-process cache) and applies rules:
|
|
7
|
+
*
|
|
8
|
+
* deny → exit 2, writes JSON reason to stdout (blocks the tool)
|
|
9
|
+
* requireConfirm → writes permissionDecision:"ask" JSON to stdout (Claude prompts user)
|
|
10
|
+
* allow / no match → exit 0 silently (proceeds)
|
|
11
|
+
*
|
|
12
|
+
* Precedence: deny > requireConfirm > allow.
|
|
13
|
+
*
|
|
14
|
+
* Kill switch: set ASHLR_POLICY_ENFORCE=0 to disable entirely.
|
|
15
|
+
*
|
|
16
|
+
* Registered in hooks.json:
|
|
17
|
+
* PreToolUse matcher: Edit|Write|Bash|MultiEdit|mcp__ashlr-efficiency__ashlr__edit|mcp__ashlr-multi-edit__*
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { tmpdir } from "os";
|
|
21
|
+
import { join } from "path";
|
|
22
|
+
import { minimatch } from "minimatch";
|
|
23
|
+
import { flushHookTimings, installHookTimeout, recordHookTiming } from "./pretooluse-common";
|
|
24
|
+
import { noteHookError } from "./_hook-errors";
|
|
25
|
+
|
|
26
|
+
if (import.meta.main) installHookTimeout("policy-enforce");
|
|
27
|
+
|
|
28
|
+
const hookStartedAt = Date.now();
|
|
29
|
+
let observedTool: string | undefined;
|
|
30
|
+
let outcome: "ok" | "bypass" | "block" | "error" = "ok";
|
|
31
|
+
|
|
32
|
+
async function exit(code: number): Promise<never> {
|
|
33
|
+
if (outcome === "ok" && code === 2) outcome = "block";
|
|
34
|
+
recordHookTiming({
|
|
35
|
+
hook: "policy-enforce",
|
|
36
|
+
tool: observedTool,
|
|
37
|
+
durationMs: Date.now() - hookStartedAt,
|
|
38
|
+
outcome,
|
|
39
|
+
});
|
|
40
|
+
await flushHookTimings();
|
|
41
|
+
process.exit(code);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const token = process.env["ASHLR_PRO_TOKEN"];
|
|
45
|
+
const baseUrl = process.env["ASHLR_API_URL"] ?? "https://api.ashlr.ai";
|
|
46
|
+
const enforce = process.env["ASHLR_POLICY_ENFORCE"] !== "0";
|
|
47
|
+
|
|
48
|
+
if (!token || !enforce) {
|
|
49
|
+
await exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// In-process policy cache (5-min TTL, reset on each process start — this hook
|
|
54
|
+
// is short-lived, so the "cache" is just a module-level variable reused within
|
|
55
|
+
// a single invocation; for a long-running daemon it would survive across calls)
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
interface PolicyRule {
|
|
59
|
+
match: string;
|
|
60
|
+
kind: "tool" | "path" | "shell";
|
|
61
|
+
reason?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface PolicyRules {
|
|
65
|
+
allow: PolicyRule[];
|
|
66
|
+
deny: PolicyRule[];
|
|
67
|
+
requireConfirm: PolicyRule[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface CachedPolicy {
|
|
71
|
+
rules: PolicyRules;
|
|
72
|
+
fetchedAt: number;
|
|
73
|
+
etag: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Persist cache in a temp file so multiple short-lived hook invocations share it.
|
|
77
|
+
// Use os.tmpdir() + path.join so this works on Windows (where /tmp/ does not
|
|
78
|
+
// exist and the silent failure otherwise disables policy enforcement entirely).
|
|
79
|
+
const CACHE_PATH = join(
|
|
80
|
+
tmpdir(),
|
|
81
|
+
`.ashlr-policy-cache-${Buffer.from(token!).toString("base64url").slice(0, 16)}.json`,
|
|
82
|
+
);
|
|
83
|
+
const TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
84
|
+
|
|
85
|
+
async function loadCachedPolicy(): Promise<CachedPolicy | null> {
|
|
86
|
+
try {
|
|
87
|
+
const raw = await Bun.file(CACHE_PATH).text();
|
|
88
|
+
const cached = JSON.parse(raw) as CachedPolicy;
|
|
89
|
+
if (Date.now() - cached.fetchedAt < TTL_MS) return cached;
|
|
90
|
+
} catch {
|
|
91
|
+
// Cache miss or parse error
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function fetchPolicy(): Promise<PolicyRules | null> {
|
|
97
|
+
const cached = await loadCachedPolicy();
|
|
98
|
+
const headers: Record<string, string> = {
|
|
99
|
+
Authorization: `Bearer ${token}`,
|
|
100
|
+
};
|
|
101
|
+
if (cached) headers["if-none-match"] = cached.etag;
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const controller = new AbortController();
|
|
105
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
106
|
+
const res = await fetch(`${baseUrl}/policy/current`, { headers, signal: controller.signal });
|
|
107
|
+
clearTimeout(timeout);
|
|
108
|
+
|
|
109
|
+
if (res.status === 304 && cached) return cached.rules;
|
|
110
|
+
if (res.status === 404) return null; // No policy configured — allow all
|
|
111
|
+
if (!res.ok) return cached?.rules ?? null;
|
|
112
|
+
|
|
113
|
+
const data = await res.json() as { rules: PolicyRules };
|
|
114
|
+
const etag = res.headers.get("etag") ?? "";
|
|
115
|
+
const entry: CachedPolicy = { rules: data.rules, fetchedAt: Date.now(), etag };
|
|
116
|
+
await Bun.write(CACHE_PATH, JSON.stringify(entry));
|
|
117
|
+
return entry.rules;
|
|
118
|
+
} catch (e) {
|
|
119
|
+
// Network error — fail open (allow) to avoid blocking all tools, but
|
|
120
|
+
// surface the failure: silent policy bypass is a security concern.
|
|
121
|
+
noteHookError("policy-enforce", "fetch-policy", e);
|
|
122
|
+
return cached?.rules ?? null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Read hook input from stdin
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
let payload: Record<string, unknown> = {};
|
|
131
|
+
try {
|
|
132
|
+
const raw = await Bun.stdin.text();
|
|
133
|
+
if (raw.trim()) payload = JSON.parse(raw) as Record<string, unknown>;
|
|
134
|
+
} catch {
|
|
135
|
+
await exit(0);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const toolName = (payload["tool_name"] as string | undefined) ?? "";
|
|
139
|
+
observedTool = toolName || undefined;
|
|
140
|
+
const toolInput = (payload["tool_input"] as Record<string, unknown> | undefined) ?? {};
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Build match subject strings for each rule kind
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
/** Returns true if the rule matches the current tool call. */
|
|
147
|
+
function ruleMatches(rule: PolicyRule): boolean {
|
|
148
|
+
switch (rule.kind) {
|
|
149
|
+
case "tool":
|
|
150
|
+
// Match against tool name, e.g. "mcp__ashlr-*" or "Bash"
|
|
151
|
+
return minimatch(toolName, rule.match, { nocase: true });
|
|
152
|
+
|
|
153
|
+
case "path": {
|
|
154
|
+
// Extract path arguments from tool input
|
|
155
|
+
const pathArgs = [
|
|
156
|
+
toolInput["file_path"],
|
|
157
|
+
toolInput["path"],
|
|
158
|
+
toolInput["file"],
|
|
159
|
+
].filter((v): v is string => typeof v === "string");
|
|
160
|
+
return pathArgs.some((p) => minimatch(p, rule.match));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case "shell": {
|
|
164
|
+
// Match against Bash command string
|
|
165
|
+
const cmd = (toolInput["command"] as string | undefined) ?? "";
|
|
166
|
+
// Rule like "Bash(rm *)" — extract the inner pattern if present
|
|
167
|
+
const inner = rule.match.match(/^Bash\((.+)\)$/);
|
|
168
|
+
const pattern = inner ? inner[1] : rule.match;
|
|
169
|
+
return minimatch(cmd, pattern!) || cmd.startsWith(pattern!.replace(/\s*\*$/, "").trimEnd());
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// Evaluate rules
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
const rules = await fetchPolicy();
|
|
179
|
+
|
|
180
|
+
if (!rules) {
|
|
181
|
+
// No policy or fetch failed — allow
|
|
182
|
+
await exit(0);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Deny takes highest precedence
|
|
186
|
+
for (const rule of rules!.deny) {
|
|
187
|
+
if (ruleMatches(rule)) {
|
|
188
|
+
const reason = rule.reason ?? `Blocked by policy rule: deny ${rule.kind} "${rule.match}"`;
|
|
189
|
+
process.stdout.write(
|
|
190
|
+
JSON.stringify({
|
|
191
|
+
type: "block",
|
|
192
|
+
reason,
|
|
193
|
+
}) + "\n",
|
|
194
|
+
);
|
|
195
|
+
await exit(2);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// requireConfirm
|
|
200
|
+
for (const rule of rules!.requireConfirm) {
|
|
201
|
+
if (ruleMatches(rule)) {
|
|
202
|
+
const reason = `Policy requires confirmation: ${rule.kind} "${rule.match}"`;
|
|
203
|
+
process.stdout.write(
|
|
204
|
+
JSON.stringify({
|
|
205
|
+
permissionDecision: "ask",
|
|
206
|
+
reason,
|
|
207
|
+
}) + "\n",
|
|
208
|
+
);
|
|
209
|
+
await exit(0);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Allow (explicit or no match) — proceed silently
|
|
214
|
+
await exit(0);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* post-tool-use-embedding.ts — Re-embed edited files after ashlr__edit / ashlr__multi_edit.
|
|
4
|
+
*
|
|
5
|
+
* PostToolUse hook. Reads the tool result JSON from stdin, extracts the file
|
|
6
|
+
* path(s), then queues re-embedding in a completely detached child process so
|
|
7
|
+
* the hook returns immediately (fire-and-forget, < 1 ms impact on tool chain).
|
|
8
|
+
*
|
|
9
|
+
* Honors:
|
|
10
|
+
* ASHLR_CONTEXT_DB_DISABLE=1 — exits immediately; no-op
|
|
11
|
+
* ASHLR_EMBED_URL — forwarded to child (remote embedder)
|
|
12
|
+
* ASHLR_EMBED_MODEL — forwarded to child
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { resolve, dirname } from "path";
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
17
|
+
import { flushHookTimings, recordHookTiming } from "./pretooluse-common";
|
|
18
|
+
|
|
19
|
+
const hookStartedAt = Date.now();
|
|
20
|
+
let observedTool: string | undefined;
|
|
21
|
+
let outcome: "ok" | "error" = "ok";
|
|
22
|
+
|
|
23
|
+
async function exit(code: number): Promise<never> {
|
|
24
|
+
recordHookTiming({
|
|
25
|
+
hook: "post-tool-use-embedding",
|
|
26
|
+
tool: observedTool,
|
|
27
|
+
durationMs: Date.now() - hookStartedAt,
|
|
28
|
+
outcome,
|
|
29
|
+
});
|
|
30
|
+
await flushHookTimings();
|
|
31
|
+
process.exit(code);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (process.env.ASHLR_CONTEXT_DB_DISABLE === "1") await exit(0);
|
|
35
|
+
|
|
36
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT ?? resolve(scriptDir, "..");
|
|
38
|
+
|
|
39
|
+
const chunks: Buffer[] = [];
|
|
40
|
+
process.stdin.on("data", (c: Buffer) => chunks.push(c));
|
|
41
|
+
process.stdin.on("end", async () => {
|
|
42
|
+
let payload: Record<string, unknown> = {};
|
|
43
|
+
try {
|
|
44
|
+
payload = JSON.parse(Buffer.concat(chunks).toString("utf-8")) as Record<string, unknown>;
|
|
45
|
+
} catch {
|
|
46
|
+
outcome = "error";
|
|
47
|
+
await exit(0);
|
|
48
|
+
}
|
|
49
|
+
observedTool = (payload.tool_name as string | undefined) || undefined;
|
|
50
|
+
|
|
51
|
+
// Extract file paths from tool input (works for both ashlr__edit and ashlr__multi_edit).
|
|
52
|
+
const paths: string[] = [];
|
|
53
|
+
|
|
54
|
+
const input = payload.tool_input as Record<string, unknown> | undefined;
|
|
55
|
+
if (input) {
|
|
56
|
+
// ashlr__edit: { path: string }
|
|
57
|
+
if (typeof input.path === "string") paths.push(input.path);
|
|
58
|
+
// ashlr__multi_edit: { edits: Array<{ path: string }> }
|
|
59
|
+
if (Array.isArray(input.edits)) {
|
|
60
|
+
for (const e of input.edits as Array<Record<string, unknown>>) {
|
|
61
|
+
if (typeof e.path === "string") paths.push(e.path);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (paths.length === 0) await exit(0);
|
|
67
|
+
|
|
68
|
+
// Spawn detached worker — inherits env, writes to /dev/null, exits on its own.
|
|
69
|
+
const workerScript = resolve(pluginRoot, "scripts", "embed-file-worker.ts");
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const proc = Bun.spawn(
|
|
73
|
+
["bun", "run", workerScript, ...paths],
|
|
74
|
+
{
|
|
75
|
+
stdin: "ignore",
|
|
76
|
+
stdout: "ignore",
|
|
77
|
+
stderr: "ignore",
|
|
78
|
+
// detached so it outlives this hook process
|
|
79
|
+
env: {
|
|
80
|
+
...process.env,
|
|
81
|
+
ASHLR_CONTEXT_DB_DISABLE: process.env.ASHLR_CONTEXT_DB_DISABLE ?? "0",
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
// Don't await — fire-and-forget.
|
|
86
|
+
proc.unref?.();
|
|
87
|
+
} catch {
|
|
88
|
+
outcome = "error";
|
|
89
|
+
/* best-effort */
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await exit(0);
|
|
93
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ashlr-plugin PostToolUse: auto-propose genome updates.
|
|
3
|
+
#
|
|
4
|
+
# Streams the PostToolUse JSON payload into scripts/genome-auto-propose.ts,
|
|
5
|
+
# which decides whether to append a proposal to the nearest `.ashlrcode/
|
|
6
|
+
# genome/proposals.jsonl`. This is a fire-and-forget observer: it must NEVER
|
|
7
|
+
# block the agent and must NEVER emit output on stdout (the harness reads
|
|
8
|
+
# stdout as hook JSON).
|
|
9
|
+
#
|
|
10
|
+
# Honors:
|
|
11
|
+
# ASHLR_GENOME_AUTO=0 — env-var kill switch
|
|
12
|
+
# ~/.ashlr/config.json — { "genomeAuto": false } disables
|
|
13
|
+
# ~/.claude/settings.json — user-level global settings (handled in TS)
|
|
14
|
+
|
|
15
|
+
set +e
|
|
16
|
+
|
|
17
|
+
# Short-circuit before we even spawn Bun if the user disabled auto-genome.
|
|
18
|
+
if [ "${ASHLR_GENOME_AUTO:-1}" = "0" ]; then
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$SCRIPT_DIR")}"
|
|
24
|
+
PROPOSE_TS="$PLUGIN_ROOT/scripts/genome-auto-propose.ts"
|
|
25
|
+
|
|
26
|
+
# No bun or missing script → pass through without error.
|
|
27
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
if [ ! -f "$PROPOSE_TS" ]; then
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Pipe stdin through; suppress stdout so we don't pollute the hook channel.
|
|
35
|
+
bun run "$PROPOSE_TS" >/dev/null 2>&1 || true
|
|
36
|
+
|
|
37
|
+
exit 0
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* post-tool-use-genome.ts — Cross-platform replacement for post-tool-use-genome.sh.
|
|
4
|
+
*
|
|
5
|
+
* Streams the PostToolUse JSON payload into scripts/genome-auto-propose.ts,
|
|
6
|
+
* which decides whether to append a proposal to the nearest .ashlrcode/genome/
|
|
7
|
+
* proposals.jsonl. Fire-and-forget: must never block the agent, must never
|
|
8
|
+
* emit on stdout.
|
|
9
|
+
*
|
|
10
|
+
* Honors:
|
|
11
|
+
* ASHLR_GENOME_AUTO=0 — env-var kill switch
|
|
12
|
+
* ~/.ashlr/config.json — { "genomeAuto": false } disables
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { resolve, dirname, join } from "path";
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
17
|
+
import { existsSync } from "fs";
|
|
18
|
+
import { flushHookTimings, recordHookTiming } from "./pretooluse-common";
|
|
19
|
+
|
|
20
|
+
const hookStartedAt = Date.now();
|
|
21
|
+
let observedTool: string | undefined;
|
|
22
|
+
let outcome: "ok" | "error" = "ok";
|
|
23
|
+
|
|
24
|
+
async function exit(code: number): Promise<never> {
|
|
25
|
+
recordHookTiming({
|
|
26
|
+
hook: "post-tool-use-genome",
|
|
27
|
+
tool: observedTool,
|
|
28
|
+
durationMs: Date.now() - hookStartedAt,
|
|
29
|
+
outcome,
|
|
30
|
+
});
|
|
31
|
+
await flushHookTimings();
|
|
32
|
+
process.exit(code);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (process.env.ASHLR_GENOME_AUTO === "0") await exit(0);
|
|
36
|
+
|
|
37
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
38
|
+
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT ?? resolve(scriptDir, "..");
|
|
39
|
+
const proposeTs = join(pluginRoot, "scripts", "genome-auto-propose.ts");
|
|
40
|
+
|
|
41
|
+
if (!existsSync(proposeTs)) await exit(0);
|
|
42
|
+
|
|
43
|
+
const chunks: Buffer[] = [];
|
|
44
|
+
process.stdin.on("data", (c: Buffer) => chunks.push(c));
|
|
45
|
+
process.stdin.on("end", async () => {
|
|
46
|
+
const stdin = Buffer.concat(chunks);
|
|
47
|
+
try {
|
|
48
|
+
const raw = stdin.toString("utf-8").trim();
|
|
49
|
+
if (raw) {
|
|
50
|
+
const p = JSON.parse(raw) as Record<string, unknown>;
|
|
51
|
+
observedTool = (p.tool_name as string | undefined) || undefined;
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
/* best-effort */
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
// Suppress stdout so we don't pollute the hook channel.
|
|
58
|
+
const proc = Bun.spawn(["bun", "run", proposeTs], {
|
|
59
|
+
stdin: new Response(stdin),
|
|
60
|
+
stdout: "ignore",
|
|
61
|
+
stderr: "ignore",
|
|
62
|
+
});
|
|
63
|
+
await proc.exited;
|
|
64
|
+
} catch {
|
|
65
|
+
outcome = "error";
|
|
66
|
+
/* best-effort */
|
|
67
|
+
}
|
|
68
|
+
await exit(0);
|
|
69
|
+
});
|