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,451 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* sessionend-hook-health-nudge.ts — Two SessionEnd nudges that surface
|
|
4
|
+
* hook health to users who would otherwise never look at it:
|
|
5
|
+
*
|
|
6
|
+
* 1. Hook-error visibility (nudge #2) — if any hook wrote to
|
|
7
|
+
* ~/.ashlr/hook-errors.jsonl during this session, fire one nudge
|
|
8
|
+
* with a count + first 2 error summaries + link to
|
|
9
|
+
* `/ashlr-doctor --errors`. Silent failures hide real bugs;
|
|
10
|
+
* this makes them visible without spamming.
|
|
11
|
+
*
|
|
12
|
+
* 2. Hook regression detector (nudge #7) — if any hook in
|
|
13
|
+
* ~/.ashlr/hook-timings.jsonl breached a budget this session
|
|
14
|
+
* (p95 > 200ms OR max > 1s), fire one nudge with the offending
|
|
15
|
+
* hook names + link to `/ashlr-hook-timings --flags-only`.
|
|
16
|
+
*
|
|
17
|
+
* Both nudges:
|
|
18
|
+
* - Scope to "current session" via stats.json `sessions[*].startedAt`
|
|
19
|
+
* (no session-id in the jsonl rows; we approximate by `ts >= sessionStart`).
|
|
20
|
+
* - Throttle via ~/.ashlr/hook-health-nudge-state.json so the same
|
|
21
|
+
* category cannot re-fire within 24h across session boundaries.
|
|
22
|
+
* - Tail-read each jsonl (last 500 lines) so a very large file never
|
|
23
|
+
* blows past the SessionEnd 2s safety net (v1.29).
|
|
24
|
+
*
|
|
25
|
+
* Best-effort: any I/O error → null (no nudge). Never throws. Never blocks.
|
|
26
|
+
* Output: prints nudge text(s) to stdout, separated by blank lines. Exits 0.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
closeSync,
|
|
31
|
+
existsSync,
|
|
32
|
+
fstatSync,
|
|
33
|
+
mkdirSync,
|
|
34
|
+
openSync,
|
|
35
|
+
readFileSync,
|
|
36
|
+
readSync,
|
|
37
|
+
renameSync,
|
|
38
|
+
writeFileSync,
|
|
39
|
+
} from "fs";
|
|
40
|
+
import { homedir } from "os";
|
|
41
|
+
import { join } from "path";
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Thresholds + windows
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
/** p95 budget — anything above this triggers a regression nudge. */
|
|
48
|
+
export const REGRESSION_P95_MS = 200;
|
|
49
|
+
/** Max budget — a single sample over this triggers a regression nudge. */
|
|
50
|
+
export const REGRESSION_MAX_MS = 1000;
|
|
51
|
+
/** Minimum sample count for p95 to be meaningful. */
|
|
52
|
+
export const MIN_SAMPLES_FOR_P95 = 5;
|
|
53
|
+
/** Per-category throttle window (24h). */
|
|
54
|
+
export const THROTTLE_WINDOW_MS = 24 * 60 * 60_000;
|
|
55
|
+
/** Tail-read line cap — both jsonls. Keeps total scan budget well under 50ms. */
|
|
56
|
+
export const TAIL_LINES_CAP = 500;
|
|
57
|
+
/** Hard byte ceiling per tail read, independent of file size. */
|
|
58
|
+
export const TAIL_BYTES_CAP = 256 * 1024;
|
|
59
|
+
const TAIL_CHUNK_BYTES = 16 * 1024;
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// State
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
export interface HealthNudgeState {
|
|
66
|
+
/** Epoch ms of the last hook-error nudge (any). */
|
|
67
|
+
last_error_nudge_at: number;
|
|
68
|
+
/** Epoch ms of the last hook-regression nudge (any). */
|
|
69
|
+
last_regression_nudge_at: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const defaultState = (): HealthNudgeState => ({
|
|
73
|
+
last_error_nudge_at: 0,
|
|
74
|
+
last_regression_nudge_at: 0,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export function healthNudgeStatePath(homeDir: string = homedir()): string {
|
|
78
|
+
return join(homeDir, ".ashlr", "hook-health-nudge-state.json");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function readHealthState(homeDir: string = homedir()): HealthNudgeState {
|
|
82
|
+
try {
|
|
83
|
+
const p = healthNudgeStatePath(homeDir);
|
|
84
|
+
if (!existsSync(p)) return defaultState();
|
|
85
|
+
const raw = JSON.parse(readFileSync(p, "utf-8")) as Partial<HealthNudgeState>;
|
|
86
|
+
return {
|
|
87
|
+
last_error_nudge_at:
|
|
88
|
+
typeof raw.last_error_nudge_at === "number" ? raw.last_error_nudge_at : 0,
|
|
89
|
+
last_regression_nudge_at:
|
|
90
|
+
typeof raw.last_regression_nudge_at === "number" ? raw.last_regression_nudge_at : 0,
|
|
91
|
+
};
|
|
92
|
+
} catch {
|
|
93
|
+
return defaultState();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function writeHealthState(
|
|
98
|
+
state: HealthNudgeState,
|
|
99
|
+
homeDir: string = homedir(),
|
|
100
|
+
): void {
|
|
101
|
+
try {
|
|
102
|
+
const dir = join(homeDir, ".ashlr");
|
|
103
|
+
mkdirSync(dir, { recursive: true });
|
|
104
|
+
const finalPath = healthNudgeStatePath(homeDir);
|
|
105
|
+
const tmpPath = `${finalPath}.${process.pid}.tmp`;
|
|
106
|
+
writeFileSync(tmpPath, JSON.stringify(state, null, 2));
|
|
107
|
+
renameSync(tmpPath, finalPath);
|
|
108
|
+
} catch {
|
|
109
|
+
/* best-effort */
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Tail reader — bounded scan to respect 2s safety net
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Read the last `lineCap` lines of a JSONL file, parse each as JSON,
|
|
119
|
+
* drop malformed lines. Returns [] if the file does not exist or fails to
|
|
120
|
+
* read. Bounded to `lineCap` to protect against pathological files.
|
|
121
|
+
*/
|
|
122
|
+
export function tailJsonl<T = Record<string, unknown>>(
|
|
123
|
+
path: string,
|
|
124
|
+
lineCap: number = TAIL_LINES_CAP,
|
|
125
|
+
byteCap: number = TAIL_BYTES_CAP,
|
|
126
|
+
): T[] {
|
|
127
|
+
let fd: number | undefined;
|
|
128
|
+
try {
|
|
129
|
+
if (!existsSync(path)) return [];
|
|
130
|
+
fd = openSync(path, "r");
|
|
131
|
+
const size = fstatSync(fd).size;
|
|
132
|
+
if (size === 0 || lineCap <= 0 || byteCap <= 0) return [];
|
|
133
|
+
let position = size;
|
|
134
|
+
let bytes = 0;
|
|
135
|
+
let newlineCount = 0;
|
|
136
|
+
const chunks: Buffer[] = [];
|
|
137
|
+
while (position > 0 && bytes < byteCap && newlineCount <= lineCap) {
|
|
138
|
+
const length = Math.min(TAIL_CHUNK_BYTES, position, byteCap - bytes);
|
|
139
|
+
position -= length;
|
|
140
|
+
const chunk = Buffer.allocUnsafe(length);
|
|
141
|
+
const read = readSync(fd, chunk, 0, length, position);
|
|
142
|
+
if (read !== length) return [];
|
|
143
|
+
chunks.unshift(chunk);
|
|
144
|
+
bytes += read;
|
|
145
|
+
for (const byte of chunk) if (byte === 10) newlineCount++;
|
|
146
|
+
}
|
|
147
|
+
let raw = Buffer.concat(chunks).toString("utf8");
|
|
148
|
+
// Starting mid-file means the first bytes are a partial row.
|
|
149
|
+
if (position > 0) {
|
|
150
|
+
const firstNewline = raw.indexOf("\n");
|
|
151
|
+
raw = firstNewline === -1 ? "" : raw.slice(firstNewline + 1);
|
|
152
|
+
}
|
|
153
|
+
const allLines = raw.split("\n");
|
|
154
|
+
const slice = allLines.filter((line) => line.trim()).slice(-lineCap);
|
|
155
|
+
const out: T[] = [];
|
|
156
|
+
for (const line of slice) {
|
|
157
|
+
const t = line.trim();
|
|
158
|
+
if (!t) continue;
|
|
159
|
+
try {
|
|
160
|
+
const parsed: unknown = JSON.parse(t);
|
|
161
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
162
|
+
out.push(parsed as T);
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
/* skip malformed */
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
} catch {
|
|
170
|
+
return [];
|
|
171
|
+
} finally {
|
|
172
|
+
if (fd !== undefined) {
|
|
173
|
+
try { closeSync(fd); } catch { /* best effort */ }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
// Session-start resolver — reuses stats.json (same as sessionend-savings-nudge)
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
interface StatsBucket {
|
|
183
|
+
startedAt?: string;
|
|
184
|
+
tokensSaved?: number;
|
|
185
|
+
}
|
|
186
|
+
interface StatsFile {
|
|
187
|
+
sessions?: Record<string, StatsBucket>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Pick the most active session's startedAt as "current session". Falls back
|
|
192
|
+
* to `now - 1h` when nothing is available — that bound keeps nudges
|
|
193
|
+
* locally scoped while still finding records on a fresh install.
|
|
194
|
+
*/
|
|
195
|
+
export function resolveSessionStartMs(
|
|
196
|
+
homeDir: string = homedir(),
|
|
197
|
+
nowMs: number = Date.now(),
|
|
198
|
+
): number {
|
|
199
|
+
try {
|
|
200
|
+
const p = join(homeDir, ".ashlr", "stats.json");
|
|
201
|
+
if (!existsSync(p)) return nowMs - 60 * 60_000;
|
|
202
|
+
const stats = JSON.parse(readFileSync(p, "utf-8")) as StatsFile;
|
|
203
|
+
let bestStart = "";
|
|
204
|
+
let bestTokens = -1;
|
|
205
|
+
for (const bucket of Object.values(stats.sessions ?? {})) {
|
|
206
|
+
const tok = bucket.tokensSaved ?? 0;
|
|
207
|
+
if (tok > bestTokens && typeof bucket.startedAt === "string") {
|
|
208
|
+
bestTokens = tok;
|
|
209
|
+
bestStart = bucket.startedAt;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (!bestStart) return nowMs - 60 * 60_000;
|
|
213
|
+
const parsed = Date.parse(bestStart);
|
|
214
|
+
return Number.isFinite(parsed) ? parsed : nowMs - 60 * 60_000;
|
|
215
|
+
} catch {
|
|
216
|
+
return nowMs - 60 * 60_000;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// Nudge #2 — hook-error visibility
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
export interface HookErrorRecord {
|
|
225
|
+
ts: string;
|
|
226
|
+
hook: string;
|
|
227
|
+
context: string;
|
|
228
|
+
error: string;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export interface ErrorNudgeResult {
|
|
232
|
+
nudgeText: string | null;
|
|
233
|
+
count: number;
|
|
234
|
+
errors: HookErrorRecord[];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Pure: build the nudge text. Exported for tests.
|
|
239
|
+
*/
|
|
240
|
+
export function buildErrorNudge(count: number, errs: HookErrorRecord[]): string {
|
|
241
|
+
const head =
|
|
242
|
+
count === 1
|
|
243
|
+
? `1 hook error this session.`
|
|
244
|
+
: `${count} hook errors this session.`;
|
|
245
|
+
const lines: string[] = [head];
|
|
246
|
+
for (const e of errs.slice(0, 2)) {
|
|
247
|
+
// Trim very long error bodies so we don't dump a stack trace into the
|
|
248
|
+
// SessionEnd channel. 120 chars is the same cap used by /ashlr-doctor.
|
|
249
|
+
const trimmed = e.error.length > 120 ? e.error.slice(0, 117) + "..." : e.error;
|
|
250
|
+
lines.push(` • ${e.hook}: ${trimmed}`);
|
|
251
|
+
}
|
|
252
|
+
if (count > 2) lines.push(` + ${count - 2} more`);
|
|
253
|
+
lines.push("Run /ashlr-doctor --errors to inspect them.");
|
|
254
|
+
return lines.join("\n");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Compute the error nudge given current-session error records.
|
|
259
|
+
*/
|
|
260
|
+
export function computeErrorNudge(
|
|
261
|
+
records: HookErrorRecord[],
|
|
262
|
+
state: HealthNudgeState,
|
|
263
|
+
nowMs: number,
|
|
264
|
+
): ErrorNudgeResult {
|
|
265
|
+
if (records.length === 0) return { nudgeText: null, count: 0, errors: [] };
|
|
266
|
+
// Throttle: same nudge category cannot re-fire within 24h.
|
|
267
|
+
if (nowMs - state.last_error_nudge_at < THROTTLE_WINDOW_MS) {
|
|
268
|
+
return { nudgeText: null, count: records.length, errors: records };
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
nudgeText: buildErrorNudge(records.length, records),
|
|
272
|
+
count: records.length,
|
|
273
|
+
errors: records,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
// Nudge #7 — hook regression detector
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
|
|
281
|
+
export interface HookTimingRow {
|
|
282
|
+
ts: string;
|
|
283
|
+
hook: string;
|
|
284
|
+
tool?: string | null;
|
|
285
|
+
durationMs: number;
|
|
286
|
+
outcome: string;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export interface RegressionFinding {
|
|
290
|
+
hook: string;
|
|
291
|
+
p95: number;
|
|
292
|
+
max: number;
|
|
293
|
+
samples: number;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export interface RegressionNudgeResult {
|
|
297
|
+
nudgeText: string | null;
|
|
298
|
+
findings: RegressionFinding[];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function percentile(sorted: number[], p: number): number {
|
|
302
|
+
if (sorted.length === 0) return 0;
|
|
303
|
+
if (sorted.length === 1) return sorted[0]!;
|
|
304
|
+
const idx = (p / 100) * (sorted.length - 1);
|
|
305
|
+
const lo = Math.floor(idx);
|
|
306
|
+
const hi = Math.ceil(idx);
|
|
307
|
+
if (lo === hi) return sorted[lo]!;
|
|
308
|
+
const frac = idx - lo;
|
|
309
|
+
return Math.round(sorted[lo]! + frac * (sorted[hi]! - sorted[lo]!));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function buildRegressionNudge(findings: RegressionFinding[]): string {
|
|
313
|
+
const names = findings.map((f) => f.hook).slice(0, 3).join(", ");
|
|
314
|
+
const extra = findings.length > 3 ? ` (+${findings.length - 3} more)` : "";
|
|
315
|
+
const detail = findings
|
|
316
|
+
.slice(0, 3)
|
|
317
|
+
.map((f) => {
|
|
318
|
+
const parts: string[] = [];
|
|
319
|
+
if (f.p95 > REGRESSION_P95_MS) parts.push(`p95 ${f.p95}ms`);
|
|
320
|
+
if (f.max >= REGRESSION_MAX_MS) {
|
|
321
|
+
const maxStr = f.max >= 1000 ? `${(f.max / 1000).toFixed(1)}s` : `${f.max}ms`;
|
|
322
|
+
parts.push(`max ${maxStr}`);
|
|
323
|
+
}
|
|
324
|
+
return ` • ${f.hook} — ${parts.join(" · ")}`;
|
|
325
|
+
})
|
|
326
|
+
.join("\n");
|
|
327
|
+
return [
|
|
328
|
+
`Hook performance regressions this session: ${names}${extra}.`,
|
|
329
|
+
detail,
|
|
330
|
+
"Run /ashlr-hook-timings --flags-only for details.",
|
|
331
|
+
].join("\n");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Compute regression findings + nudge. A hook is flagged when its
|
|
336
|
+
* session-window p95 > 200ms (with ≥5 samples) OR its max ≥ 1000ms.
|
|
337
|
+
*/
|
|
338
|
+
export function computeRegressionNudge(
|
|
339
|
+
records: HookTimingRow[],
|
|
340
|
+
state: HealthNudgeState,
|
|
341
|
+
nowMs: number,
|
|
342
|
+
): RegressionNudgeResult {
|
|
343
|
+
if (records.length === 0) return { nudgeText: null, findings: [] };
|
|
344
|
+
|
|
345
|
+
// Group by hook.
|
|
346
|
+
const byHook = new Map<string, number[]>();
|
|
347
|
+
for (const r of records) {
|
|
348
|
+
const arr = byHook.get(r.hook) ?? [];
|
|
349
|
+
arr.push(r.durationMs);
|
|
350
|
+
byHook.set(r.hook, arr);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const findings: RegressionFinding[] = [];
|
|
354
|
+
for (const [hook, durations] of byHook) {
|
|
355
|
+
const sorted = [...durations].sort((a, b) => a - b);
|
|
356
|
+
const max = sorted[sorted.length - 1] ?? 0;
|
|
357
|
+
const p95 = sorted.length >= MIN_SAMPLES_FOR_P95 ? percentile(sorted, 95) : 0;
|
|
358
|
+
const p95Breach = p95 > REGRESSION_P95_MS;
|
|
359
|
+
const maxBreach = max >= REGRESSION_MAX_MS;
|
|
360
|
+
if (p95Breach || maxBreach) {
|
|
361
|
+
findings.push({ hook, p95, max, samples: sorted.length });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (findings.length === 0) return { nudgeText: null, findings: [] };
|
|
366
|
+
|
|
367
|
+
// Sort worst-first by max then p95.
|
|
368
|
+
findings.sort((a, b) => b.max - a.max || b.p95 - a.p95);
|
|
369
|
+
|
|
370
|
+
if (nowMs - state.last_regression_nudge_at < THROTTLE_WINDOW_MS) {
|
|
371
|
+
return { nudgeText: null, findings };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return { nudgeText: buildRegressionNudge(findings), findings };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ---------------------------------------------------------------------------
|
|
378
|
+
// Orchestrator — read state, scan files, emit, persist
|
|
379
|
+
// ---------------------------------------------------------------------------
|
|
380
|
+
|
|
381
|
+
export interface FireResult {
|
|
382
|
+
errorNudge: string | null;
|
|
383
|
+
regressionNudge: string | null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function checkAndFireHealthNudges(
|
|
387
|
+
homeDir: string = homedir(),
|
|
388
|
+
nowMs: number = Date.now(),
|
|
389
|
+
): FireResult {
|
|
390
|
+
try {
|
|
391
|
+
const sessionStartMs = resolveSessionStartMs(homeDir, nowMs);
|
|
392
|
+
const state = readHealthState(homeDir);
|
|
393
|
+
|
|
394
|
+
// ----- Nudge #2: hook-errors -----
|
|
395
|
+
const errPath = join(homeDir, ".ashlr", "hook-errors.jsonl");
|
|
396
|
+
const allErrs = tailJsonl<HookErrorRecord>(errPath);
|
|
397
|
+
const sessionErrs = allErrs.filter((e) => {
|
|
398
|
+
const t = Date.parse(e.ts);
|
|
399
|
+
return Number.isFinite(t) && t >= sessionStartMs;
|
|
400
|
+
});
|
|
401
|
+
const errorResult = computeErrorNudge(sessionErrs, state, nowMs);
|
|
402
|
+
|
|
403
|
+
// ----- Nudge #7: hook regression -----
|
|
404
|
+
let regressionResult: RegressionNudgeResult = { nudgeText: null, findings: [] };
|
|
405
|
+
try {
|
|
406
|
+
const timingsPath = join(homeDir, ".ashlr", "hook-timings.jsonl");
|
|
407
|
+
const activeTimings = tailJsonl<HookTimingRow>(timingsPath, TAIL_LINES_CAP);
|
|
408
|
+
const retainedTimings = activeTimings.length < TAIL_LINES_CAP
|
|
409
|
+
? tailJsonl<HookTimingRow>(`${timingsPath}.1`, TAIL_LINES_CAP - activeTimings.length)
|
|
410
|
+
: [];
|
|
411
|
+
const allTimings = [...retainedTimings, ...activeTimings];
|
|
412
|
+
const sessionTimings = allTimings.filter((t) => {
|
|
413
|
+
const ts = Date.parse(t.ts);
|
|
414
|
+
return Number.isFinite(ts) && ts >= sessionStartMs;
|
|
415
|
+
});
|
|
416
|
+
regressionResult = computeRegressionNudge(sessionTimings, state, nowMs);
|
|
417
|
+
} catch {
|
|
418
|
+
// Timing corruption must not suppress an independently computed error nudge.
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Persist throttle state only when we actually emitted.
|
|
422
|
+
const next: HealthNudgeState = { ...state };
|
|
423
|
+
if (errorResult.nudgeText) next.last_error_nudge_at = nowMs;
|
|
424
|
+
if (regressionResult.nudgeText) next.last_regression_nudge_at = nowMs;
|
|
425
|
+
if (errorResult.nudgeText || regressionResult.nudgeText) {
|
|
426
|
+
writeHealthState(next, homeDir);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
errorNudge: errorResult.nudgeText,
|
|
431
|
+
regressionNudge: regressionResult.nudgeText,
|
|
432
|
+
};
|
|
433
|
+
} catch {
|
|
434
|
+
return { errorNudge: null, regressionNudge: null };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ---------------------------------------------------------------------------
|
|
439
|
+
// CLI entry — invoked by session-end-consolidate.ts
|
|
440
|
+
// ---------------------------------------------------------------------------
|
|
441
|
+
|
|
442
|
+
if (import.meta.main) {
|
|
443
|
+
const { errorNudge, regressionNudge } = checkAndFireHealthNudges();
|
|
444
|
+
const parts: string[] = [];
|
|
445
|
+
if (errorNudge) parts.push(errorNudge);
|
|
446
|
+
if (regressionNudge) parts.push(regressionNudge);
|
|
447
|
+
if (parts.length > 0) {
|
|
448
|
+
process.stdout.write("\n" + parts.join("\n\n") + "\n");
|
|
449
|
+
}
|
|
450
|
+
process.exit(0);
|
|
451
|
+
}
|