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,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _genome-sync.ts — Plugin-side team genome sync client.
|
|
3
|
+
*
|
|
4
|
+
* Opt-in: set ASHLR_TEAM_GENOME_ID (plus existing ASHLR_PRO_TOKEN).
|
|
5
|
+
*
|
|
6
|
+
* Used by:
|
|
7
|
+
* - hooks/session-start.ts: pull remote sections on session start
|
|
8
|
+
* - servers/_genome-live.ts: push updated sections after ashlr__edit
|
|
9
|
+
*
|
|
10
|
+
* Design constraints:
|
|
11
|
+
* - Never throws — all functions catch internally and log to stderr.
|
|
12
|
+
* - Non-blocking: callers fire-and-forget (no await required).
|
|
13
|
+
* - Tracks a local sequence number in ASHLR_GENOME_LOCAL_SEQ_PATH so
|
|
14
|
+
* incremental pulls only fetch sections modified since last pull.
|
|
15
|
+
*
|
|
16
|
+
* Encryption (v1):
|
|
17
|
+
* When a team key exists at ~/.ashlr/team-keys/<genomeId>.key, every
|
|
18
|
+
* section's content is encrypted with AES-256-GCM before upload and
|
|
19
|
+
* decrypted after pull. The server stores only ciphertext; it cannot read
|
|
20
|
+
* section content. Vclock metadata is transmitted in plaintext so the
|
|
21
|
+
* server can perform conflict detection without decrypting.
|
|
22
|
+
*
|
|
23
|
+
* If no key file exists, sync falls back to plaintext (backward compat).
|
|
24
|
+
* Existing plaintext sections are returned as-is on pull; if a section
|
|
25
|
+
* fails to decrypt (wrong key, tamper), it is skipped with a warning.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
29
|
+
import { existsSync } from "fs";
|
|
30
|
+
import { join, dirname } from "path";
|
|
31
|
+
import { homedir } from "os";
|
|
32
|
+
import {
|
|
33
|
+
loadKey,
|
|
34
|
+
encryptSection,
|
|
35
|
+
decryptSection,
|
|
36
|
+
serializeBlob,
|
|
37
|
+
parseBlob,
|
|
38
|
+
} from "./_genome-crypto.ts";
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Config — read at call time so tests can set env vars after module load
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
function cfg() {
|
|
45
|
+
return {
|
|
46
|
+
apiUrl: process.env["ASHLR_API_URL"] ?? "https://api.ashlr.ai",
|
|
47
|
+
genomeId: process.env["ASHLR_TEAM_GENOME_ID"] ?? "",
|
|
48
|
+
proToken: process.env["ASHLR_PRO_TOKEN"] ?? "",
|
|
49
|
+
clientId: process.env["ASHLR_CLIENT_ID"] ?? `client_${process.pid}`,
|
|
50
|
+
localSeqPath: process.env["ASHLR_GENOME_LOCAL_SEQ_PATH"] ?? join(homedir(), ".ashlr", "genome-seq.json"),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Types
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
export interface RemoteSection {
|
|
59
|
+
path: string;
|
|
60
|
+
content: string;
|
|
61
|
+
content_encrypted: boolean;
|
|
62
|
+
vclock: Record<string, number>;
|
|
63
|
+
conflictFlag: boolean;
|
|
64
|
+
serverSeq: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface PullResult {
|
|
68
|
+
sections: RemoteSection[];
|
|
69
|
+
serverSeqNum: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface PushResult {
|
|
73
|
+
applied: string[];
|
|
74
|
+
conflicts: string[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Guards
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
/** Returns true when team genome sync is configured. */
|
|
82
|
+
export function isTeamGenomeEnabled(): boolean {
|
|
83
|
+
const { genomeId, proToken } = cfg();
|
|
84
|
+
return Boolean(genomeId && proToken);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Local seq persistence
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
async function readLocalSeq(): Promise<number> {
|
|
92
|
+
try {
|
|
93
|
+
const raw = await readFile(cfg().localSeqPath, "utf-8");
|
|
94
|
+
const obj = JSON.parse(raw) as { seq?: number };
|
|
95
|
+
return typeof obj.seq === "number" ? obj.seq : 0;
|
|
96
|
+
} catch {
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function writeLocalSeq(seq: number): Promise<void> {
|
|
102
|
+
try {
|
|
103
|
+
const p = cfg().localSeqPath;
|
|
104
|
+
await mkdir(dirname(p), { recursive: true });
|
|
105
|
+
await writeFile(p, JSON.stringify({ seq }), "utf-8");
|
|
106
|
+
} catch {
|
|
107
|
+
// Non-fatal
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// HTTP helpers
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
async function apiFetch(
|
|
116
|
+
method: string,
|
|
117
|
+
path: string,
|
|
118
|
+
body?: unknown,
|
|
119
|
+
): Promise<Response> {
|
|
120
|
+
const { apiUrl, proToken } = cfg();
|
|
121
|
+
const url = `${apiUrl}${path}`;
|
|
122
|
+
const init: RequestInit = {
|
|
123
|
+
method,
|
|
124
|
+
headers: {
|
|
125
|
+
Authorization: `Bearer ${proToken}`,
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
if (body !== undefined) {
|
|
130
|
+
init.body = JSON.stringify(body);
|
|
131
|
+
}
|
|
132
|
+
return fetch(url, init);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Pull
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Pull remote sections since the last known sequence number, write them to
|
|
141
|
+
* .ashlrcode/genome/sections/, update local seq.
|
|
142
|
+
*
|
|
143
|
+
* @param genomeSectionsDir Absolute path to .ashlrcode/genome/sections/ in the repo.
|
|
144
|
+
*/
|
|
145
|
+
export async function pullTeamGenome(genomeSectionsDir: string): Promise<PullResult | null> {
|
|
146
|
+
if (!isTeamGenomeEnabled()) return null;
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const since = await readLocalSeq();
|
|
150
|
+
const res = await apiFetch("GET", `/genome/${cfg().genomeId}/pull?since=${since}`);
|
|
151
|
+
|
|
152
|
+
if (!res.ok) {
|
|
153
|
+
process.stderr.write(`[ashlr-genome-sync] pull failed: ${res.status}\n`);
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const data = await res.json() as PullResult;
|
|
158
|
+
|
|
159
|
+
if (data.sections.length > 0) {
|
|
160
|
+
await mkdir(genomeSectionsDir, { recursive: true });
|
|
161
|
+
|
|
162
|
+
// Load team key once for the whole pull (null = no key file = plaintext mode)
|
|
163
|
+
const teamKey = await loadKey(cfg().genomeId).catch(() => null);
|
|
164
|
+
|
|
165
|
+
for (const section of data.sections) {
|
|
166
|
+
const safeName = section.path.replace(/^sections\//, "");
|
|
167
|
+
// Additional client-side path safety check
|
|
168
|
+
if (safeName.includes("..") || safeName.startsWith("/") || /^[A-Za-z]:[/\\]/.test(safeName)) {
|
|
169
|
+
process.stderr.write(`[ashlr-genome-sync] skipping unsafe path: ${section.path}\n`);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let plaintext = section.content;
|
|
174
|
+
|
|
175
|
+
// Decrypt if the server flagged this section as encrypted and we have a key
|
|
176
|
+
if (section.content_encrypted) {
|
|
177
|
+
if (!teamKey) {
|
|
178
|
+
process.stderr.write(
|
|
179
|
+
`[ashlr-genome] WARNING: section ${section.path} is encrypted but no local key found — skipping\n`,
|
|
180
|
+
);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const blob = parseBlob(section.content);
|
|
185
|
+
plaintext = decryptSection(blob, teamKey);
|
|
186
|
+
} catch {
|
|
187
|
+
process.stderr.write(
|
|
188
|
+
`[ashlr-genome] WARNING: section ${section.path} failed to decrypt — wrong key or corrupt data\n`,
|
|
189
|
+
);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const dest = join(genomeSectionsDir, safeName);
|
|
195
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
196
|
+
await writeFile(dest, plaintext, "utf-8");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
await writeLocalSeq(data.serverSeqNum);
|
|
200
|
+
process.stderr.write(`[ashlr-genome-sync] pulled ${data.sections.length} section(s), seq=${data.serverSeqNum}\n`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return data;
|
|
204
|
+
} catch (err) {
|
|
205
|
+
process.stderr.write(`[ashlr-genome-sync] pull error: ${String(err)}\n`);
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
// Push
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Push a single updated section to the remote genome.
|
|
216
|
+
*
|
|
217
|
+
* @param sectionPath Relative path within genome sections (e.g. "sections/auth.md")
|
|
218
|
+
* @param content Full section content after the edit
|
|
219
|
+
* @param vclock Current vector clock for this client (caller manages increment)
|
|
220
|
+
*/
|
|
221
|
+
export async function pushTeamGenomeSection(
|
|
222
|
+
sectionPath: string,
|
|
223
|
+
content: string,
|
|
224
|
+
vclock: Record<string, number>,
|
|
225
|
+
): Promise<PushResult | null> {
|
|
226
|
+
if (!isTeamGenomeEnabled()) return null;
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const { genomeId, clientId } = cfg();
|
|
230
|
+
|
|
231
|
+
// Encrypt the section content if a team key is present
|
|
232
|
+
const teamKey = await loadKey(genomeId).catch(() => null);
|
|
233
|
+
let uploadContent = content;
|
|
234
|
+
if (teamKey) {
|
|
235
|
+
const blob = encryptSection(content, teamKey);
|
|
236
|
+
uploadContent = serializeBlob(blob);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const res = await apiFetch("POST", `/genome/${genomeId}/push`, {
|
|
240
|
+
clientId,
|
|
241
|
+
sections: [{ path: sectionPath, content: uploadContent, vclock }],
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
if (!res.ok) {
|
|
245
|
+
process.stderr.write(`[ashlr-genome-sync] push failed: ${res.status}\n`);
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const data = await res.json() as PushResult;
|
|
250
|
+
|
|
251
|
+
if (data.conflicts.length > 0) {
|
|
252
|
+
process.stderr.write(
|
|
253
|
+
`[ashlr-genome-sync] conflict on ${data.conflicts.join(", ")} — run /ashlr-genome-conflicts to resolve\n`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return data;
|
|
258
|
+
} catch (err) {
|
|
259
|
+
process.stderr.write(`[ashlr-genome-sync] push error: ${String(err)}\n`);
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Vclock helpers (client-side)
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
/** Load the local vclock state from disk. Returns {} if not found. */
|
|
269
|
+
export async function loadLocalVClock(genomeSectionsDir: string, sectionPath: string): Promise<Record<string, number>> {
|
|
270
|
+
const metaPath = join(genomeSectionsDir, ".vclock", sectionPath.replace(/^sections\//, "") + ".json");
|
|
271
|
+
try {
|
|
272
|
+
const raw = await readFile(metaPath, "utf-8");
|
|
273
|
+
return JSON.parse(raw) as Record<string, number>;
|
|
274
|
+
} catch {
|
|
275
|
+
return {};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Persist a vclock after a successful push. */
|
|
280
|
+
export async function saveLocalVClock(
|
|
281
|
+
genomeSectionsDir: string,
|
|
282
|
+
sectionPath: string,
|
|
283
|
+
vclock: Record<string, number>,
|
|
284
|
+
): Promise<void> {
|
|
285
|
+
const metaPath = join(genomeSectionsDir, ".vclock", sectionPath.replace(/^sections\//, "") + ".json");
|
|
286
|
+
try {
|
|
287
|
+
await mkdir(dirname(metaPath), { recursive: true });
|
|
288
|
+
await writeFile(metaPath, JSON.stringify(vclock), "utf-8");
|
|
289
|
+
} catch {
|
|
290
|
+
// Non-fatal
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Increment this client's counter in a vclock and return the updated clock. */
|
|
295
|
+
export function tickVClock(vclock: Record<string, number>, clientId = cfg().clientId): Record<string, number> {
|
|
296
|
+
return { ...vclock, [clientId]: (vclock[clientId] ?? 0) + 1 };
|
|
297
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _history-tracker.ts — Per-session tool result history for multi-turn staleness tracking.
|
|
3
|
+
*
|
|
4
|
+
* Records every ashlr tool result emitted this session with size + freshness metadata.
|
|
5
|
+
* Stored in ~/.ashlr/session-history/<sessionId>.jsonl (one JSONL line per result).
|
|
6
|
+
*
|
|
7
|
+
* Freshness decay (by subsequent tool-call count since emission):
|
|
8
|
+
* turns 0–4 → 1.0 (fresh)
|
|
9
|
+
* turns 5–14 → 0.5 (stale)
|
|
10
|
+
* turns 15+ → 0.2 (very stale)
|
|
11
|
+
*
|
|
12
|
+
* v1.25 starting coefficients — telemetry from multi_turn_stale_estimate events will
|
|
13
|
+
* tune these in v1.26.
|
|
14
|
+
*
|
|
15
|
+
* Design:
|
|
16
|
+
* - Best-effort only — never throws, never blocks a tool call.
|
|
17
|
+
* - Append-only JSONL for crash-safety and cross-process reads.
|
|
18
|
+
* - Session-scoped: each sessionId gets its own file, GC'd by session-end hook.
|
|
19
|
+
* - Kill switch: ASHLR_SESSION_LOG=0 disables all writes.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs";
|
|
23
|
+
import { homedir } from "os";
|
|
24
|
+
import { dirname, join } from "path";
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Constants
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
/** Freshness is 1.0 for the first N turns after emission. */
|
|
31
|
+
export const FRESH_TURNS = 5;
|
|
32
|
+
|
|
33
|
+
/** Turns 5–14 after emission decay to this freshness. */
|
|
34
|
+
export const STALE_FRESHNESS = 0.5;
|
|
35
|
+
|
|
36
|
+
/** Turns 15+ after emission decay to this freshness. */
|
|
37
|
+
export const VERY_STALE_FRESHNESS = 0.2;
|
|
38
|
+
|
|
39
|
+
/** Results older than this many turns are considered "stale" for compaction purposes. */
|
|
40
|
+
export const STALE_TURN_THRESHOLD = 5;
|
|
41
|
+
|
|
42
|
+
/** Nudge threshold: total stale bytes before the adaptive nudge fires. */
|
|
43
|
+
export const STALE_BYTES_NUDGE_THRESHOLD = 50 * 1024; // 50 KB
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Types
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
/** A single history entry appended per tool call. */
|
|
50
|
+
export interface HistoryEntry {
|
|
51
|
+
/** Unix ms when the result was emitted. */
|
|
52
|
+
ts: number;
|
|
53
|
+
/** Tool name (e.g. "ashlr__read", "Read"). */
|
|
54
|
+
tool: string;
|
|
55
|
+
/** Byte size of the tool result content. */
|
|
56
|
+
sizeBytes: number;
|
|
57
|
+
/** First 8 chars of SHA-256 of the result content (for dedup detection). */
|
|
58
|
+
contentSha8: string;
|
|
59
|
+
/**
|
|
60
|
+
* Global turn counter at time of emission. Starts at 0 and increments with
|
|
61
|
+
* every PostToolUse event recorded in this session (across all tools).
|
|
62
|
+
*/
|
|
63
|
+
turn: number;
|
|
64
|
+
/** Session ID — redundant with the filename but useful for sanity checks. */
|
|
65
|
+
sessionId: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Freshness-annotated history entry (computed, not stored). */
|
|
69
|
+
export interface AnnotatedEntry extends HistoryEntry {
|
|
70
|
+
/** Turns elapsed since emission (currentTurn - entry.turn). */
|
|
71
|
+
turnDelta: number;
|
|
72
|
+
/** Freshness score [0.2, 1.0]. */
|
|
73
|
+
freshness: number;
|
|
74
|
+
/** True when turnDelta >= STALE_TURN_THRESHOLD. */
|
|
75
|
+
isStale: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Path helpers
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
function home(): string {
|
|
83
|
+
return process.env.ASHLR_HOME_OVERRIDE?.trim() || process.env.HOME || homedir();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function historyDir(homeDir: string = home()): string {
|
|
87
|
+
return join(homeDir, ".ashlr", "session-history");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function historyPath(sessionId: string, homeDir: string = home()): string {
|
|
91
|
+
return join(historyDir(homeDir), `${sessionId}.jsonl`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Session ID resolution
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Derive a session ID using the same strategy as session-start.ts:
|
|
100
|
+
* CLAUDE_SESSION_ID → ASHLR_SESSION_ID → hash of ppid + cwd.
|
|
101
|
+
* Stable within a single Claude Code session because ppid + cwd are constant.
|
|
102
|
+
*/
|
|
103
|
+
export function resolveSessionId(): string {
|
|
104
|
+
const explicit =
|
|
105
|
+
process.env.CLAUDE_SESSION_ID?.trim() ||
|
|
106
|
+
process.env.ASHLR_SESSION_ID?.trim();
|
|
107
|
+
if (explicit) return explicit;
|
|
108
|
+
const seed = `${process.ppid ?? process.pid}:${process.cwd()}`;
|
|
109
|
+
let h = 0;
|
|
110
|
+
for (let i = 0; i < seed.length; i++) h = ((h << 5) - h + seed.charCodeAt(i)) | 0;
|
|
111
|
+
return `h${(h >>> 0).toString(16)}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// SHA-8 helper
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
/** Compute first 8 hex chars of a simple djb2 hash (no native crypto needed). */
|
|
119
|
+
export function sha8(content: string): string {
|
|
120
|
+
let h = 5381;
|
|
121
|
+
for (let i = 0; i < content.length; i++) {
|
|
122
|
+
h = ((h << 5) + h + content.charCodeAt(i)) | 0;
|
|
123
|
+
}
|
|
124
|
+
return (h >>> 0).toString(16).padStart(8, "0").slice(0, 8);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// Current turn counter
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Read the current session turn count from the history file.
|
|
133
|
+
* Turn = number of entries already written (0-indexed).
|
|
134
|
+
*/
|
|
135
|
+
export function readCurrentTurn(sessionId: string, homeDir: string = home()): number {
|
|
136
|
+
try {
|
|
137
|
+
const p = historyPath(sessionId, homeDir);
|
|
138
|
+
if (!existsSync(p)) return 0;
|
|
139
|
+
const raw = readFileSync(p, "utf-8");
|
|
140
|
+
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
141
|
+
return lines.length;
|
|
142
|
+
} catch {
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Record a result
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Append one history entry for a completed tool call.
|
|
153
|
+
* Best-effort — never throws.
|
|
154
|
+
*
|
|
155
|
+
* @param tool Tool name from PostToolUse payload.
|
|
156
|
+
* @param content String representation of the result (for size + sha8).
|
|
157
|
+
* @param sessionId Override for testing.
|
|
158
|
+
* @param homeDir Override for testing.
|
|
159
|
+
* @param now Override for testing.
|
|
160
|
+
*/
|
|
161
|
+
export function recordResult(
|
|
162
|
+
tool: string,
|
|
163
|
+
content: string,
|
|
164
|
+
sessionId: string = resolveSessionId(),
|
|
165
|
+
homeDir: string = home(),
|
|
166
|
+
now: number = Date.now(),
|
|
167
|
+
): void {
|
|
168
|
+
if (process.env.ASHLR_SESSION_LOG === "0") return;
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const dir = historyDir(homeDir);
|
|
172
|
+
mkdirSync(dir, { recursive: true });
|
|
173
|
+
|
|
174
|
+
const turn = readCurrentTurn(sessionId, homeDir);
|
|
175
|
+
const entry: HistoryEntry = {
|
|
176
|
+
ts: now,
|
|
177
|
+
tool,
|
|
178
|
+
sizeBytes: Buffer.byteLength(content, "utf-8"),
|
|
179
|
+
contentSha8: sha8(content),
|
|
180
|
+
turn,
|
|
181
|
+
sessionId,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
appendFileSync(historyPath(sessionId, homeDir), JSON.stringify(entry) + "\n", "utf-8");
|
|
185
|
+
} catch {
|
|
186
|
+
// Best-effort — never block the tool call.
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// Read and annotate history
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Read all history entries for a session.
|
|
196
|
+
* Returns [] on any error.
|
|
197
|
+
*/
|
|
198
|
+
export function readHistory(
|
|
199
|
+
sessionId: string = resolveSessionId(),
|
|
200
|
+
homeDir: string = home(),
|
|
201
|
+
): HistoryEntry[] {
|
|
202
|
+
try {
|
|
203
|
+
const p = historyPath(sessionId, homeDir);
|
|
204
|
+
if (!existsSync(p)) return [];
|
|
205
|
+
const raw = readFileSync(p, "utf-8");
|
|
206
|
+
return raw
|
|
207
|
+
.split("\n")
|
|
208
|
+
.filter((l) => l.trim().length > 0)
|
|
209
|
+
.map((l) => {
|
|
210
|
+
try { return JSON.parse(l) as HistoryEntry; } catch { return null; }
|
|
211
|
+
})
|
|
212
|
+
.filter((e): e is HistoryEntry => e !== null);
|
|
213
|
+
} catch {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Freshness computation
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Compute freshness score given the number of turns since emission.
|
|
224
|
+
*
|
|
225
|
+
* 0–4 turns → 1.0
|
|
226
|
+
* 5–14 turns → 0.5
|
|
227
|
+
* 15+ turns → 0.2
|
|
228
|
+
*/
|
|
229
|
+
export function freshnessScore(turnDelta: number): number {
|
|
230
|
+
if (turnDelta < FRESH_TURNS) return 1.0;
|
|
231
|
+
if (turnDelta < 15) return STALE_FRESHNESS;
|
|
232
|
+
return VERY_STALE_FRESHNESS;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Annotate all history entries with freshness metadata relative to currentTurn.
|
|
237
|
+
*/
|
|
238
|
+
export function annotateHistory(
|
|
239
|
+
entries: HistoryEntry[],
|
|
240
|
+
currentTurn: number,
|
|
241
|
+
): AnnotatedEntry[] {
|
|
242
|
+
return entries.map((e) => {
|
|
243
|
+
const turnDelta = Math.max(0, currentTurn - e.turn);
|
|
244
|
+
const freshness = freshnessScore(turnDelta);
|
|
245
|
+
return {
|
|
246
|
+
...e,
|
|
247
|
+
turnDelta,
|
|
248
|
+
freshness,
|
|
249
|
+
isStale: turnDelta >= STALE_TURN_THRESHOLD,
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
// Stale byte total
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Compute total byte size of stale results in the current session.
|
|
260
|
+
* Used by the adaptive nudge and /ashlr-compact.
|
|
261
|
+
*/
|
|
262
|
+
export function staleByteTotal(
|
|
263
|
+
sessionId: string = resolveSessionId(),
|
|
264
|
+
homeDir: string = home(),
|
|
265
|
+
): { staleBytes: number; staleResults: number; sessionTurnCount: number } {
|
|
266
|
+
try {
|
|
267
|
+
const entries = readHistory(sessionId, homeDir);
|
|
268
|
+
const currentTurn = entries.length;
|
|
269
|
+
const annotated = annotateHistory(entries, currentTurn);
|
|
270
|
+
const stale = annotated.filter((e) => e.isStale);
|
|
271
|
+
return {
|
|
272
|
+
staleBytes: stale.reduce((acc, e) => acc + e.sizeBytes, 0),
|
|
273
|
+
staleResults: stale.length,
|
|
274
|
+
sessionTurnCount: currentTurn,
|
|
275
|
+
};
|
|
276
|
+
} catch {
|
|
277
|
+
return { staleBytes: 0, staleResults: 0, sessionTurnCount: 0 };
|
|
278
|
+
}
|
|
279
|
+
}
|