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,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _embedding-model.ts — Pluggable embedder for the ashlr embedding cache.
|
|
3
|
+
*
|
|
4
|
+
* Day-1 strategy: BM25-style sparse pseudo-embedding via hash projection.
|
|
5
|
+
* - Tokenize input text (whitespace + punctuation split, lowercase).
|
|
6
|
+
* - Compute per-token IDF weights from a per-project corpus stored at
|
|
7
|
+
* ~/.ashlr/embed-corpus.json.
|
|
8
|
+
* - Project token TF-IDF scores into a fixed 256-dim vector via a
|
|
9
|
+
* deterministic hash (FNV-1a → index mod 256, sign from bit 0).
|
|
10
|
+
* - Normalize to unit length before returning.
|
|
11
|
+
*
|
|
12
|
+
* This gives useful cosine similarity WITHOUT any ML runtime:
|
|
13
|
+
* - Same input → same vector (deterministic).
|
|
14
|
+
* - Similar inputs (shared tokens) → high cosine.
|
|
15
|
+
* - Dissimilar inputs → low cosine.
|
|
16
|
+
*
|
|
17
|
+
* Remote embedding (optional):
|
|
18
|
+
* Set ASHLR_EMBED_URL=http://localhost:11434/api/embeddings to call an
|
|
19
|
+
* Ollama/LM Studio endpoint. Falls back to BM25 on any failure.
|
|
20
|
+
*
|
|
21
|
+
* Env vars:
|
|
22
|
+
* ASHLR_EMBED_URL — HTTP POST endpoint that returns { embedding: number[] }
|
|
23
|
+
* ASHLR_EMBED_MODEL — model name to pass in the request body (default: nomic-embed-text)
|
|
24
|
+
* ASHLR_CONTEXT_DB_DISABLE — if "1", embed() still works (used outside the cache path)
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
28
|
+
import { homedir } from "os";
|
|
29
|
+
import { join } from "path";
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Constants
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
/** Fixed output dimension for the BM25 hash-projection embedder. */
|
|
36
|
+
export const EMBED_DIM = 256;
|
|
37
|
+
|
|
38
|
+
/** Max tokens considered per document (cap to keep hashing O(1) on huge files). */
|
|
39
|
+
const MAX_TOKENS = 2048;
|
|
40
|
+
|
|
41
|
+
/** IDF corpus path. */
|
|
42
|
+
const corpusPath = (): string =>
|
|
43
|
+
join(homedir(), ".ashlr", "embed-corpus.json");
|
|
44
|
+
|
|
45
|
+
/** Minimum document frequency count before IDF kicks in (avoids div-by-0 on tiny corpus). */
|
|
46
|
+
const MIN_DF = 1;
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Corpus (IDF weights)
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
interface Corpus {
|
|
53
|
+
/** document count (each upsertCorpus call = 1 doc) */
|
|
54
|
+
docCount: number;
|
|
55
|
+
/** token → document frequency */
|
|
56
|
+
df: Record<string, number>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let _corpus: Corpus | null = null;
|
|
60
|
+
let _pendingDocs = 0;
|
|
61
|
+
const _pendingDf: Map<string, number> = new Map();
|
|
62
|
+
|
|
63
|
+
function loadCorpus(): Corpus {
|
|
64
|
+
if (_corpus) return _corpus;
|
|
65
|
+
_corpus = readCorpusFromDisk();
|
|
66
|
+
return _corpus;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function readCorpusFromDisk(): Corpus {
|
|
70
|
+
const path = corpusPath();
|
|
71
|
+
if (existsSync(path)) {
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(readFileSync(path, "utf-8")) as Corpus;
|
|
74
|
+
} catch {
|
|
75
|
+
// corrupt; reinitialize
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { docCount: 0, df: {} };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function atomicWriteCorpus(c: Corpus): void {
|
|
82
|
+
const path = corpusPath();
|
|
83
|
+
const dir = join(homedir(), ".ashlr");
|
|
84
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
85
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
86
|
+
writeFileSync(tmp, JSON.stringify(c), "utf-8");
|
|
87
|
+
renameSync(tmp, path);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Acquire a cross-process advisory lock on `~/.ashlr/embed-corpus.json.lock`.
|
|
92
|
+
* Returns true if acquired, false if contended after ~1s of retries.
|
|
93
|
+
* Stale locks (>5s old) are reclaimed automatically.
|
|
94
|
+
*
|
|
95
|
+
* Async-with-sleep is load-bearing: short-lived worker processes (e.g. the
|
|
96
|
+
* embed-file-worker fired by the post-tool-use hook) exit immediately after
|
|
97
|
+
* their main task, so a purely synchronous no-sleep retry loop would burn
|
|
98
|
+
* attempts too fast to let a contending writer finish and release.
|
|
99
|
+
*/
|
|
100
|
+
async function tryAcquireCorpusLock(): Promise<boolean> {
|
|
101
|
+
const lockPath = `${corpusPath()}.lock`;
|
|
102
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
103
|
+
try {
|
|
104
|
+
const fd = openSync(lockPath, "wx");
|
|
105
|
+
closeSync(fd);
|
|
106
|
+
return true;
|
|
107
|
+
} catch {
|
|
108
|
+
// EEXIST — check for stale lock
|
|
109
|
+
try {
|
|
110
|
+
const st = statSync(lockPath);
|
|
111
|
+
if (Date.now() - st.mtimeMs > 5_000) {
|
|
112
|
+
try { unlinkSync(lockPath); } catch { /* ignore */ }
|
|
113
|
+
}
|
|
114
|
+
} catch { /* stat race — lock may have been released, loop */ }
|
|
115
|
+
}
|
|
116
|
+
if (attempt < 9) await new Promise((r) => setTimeout(r, 100));
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function releaseCorpusLock(): void {
|
|
122
|
+
try { unlinkSync(`${corpusPath()}.lock`); } catch { /* best-effort */ }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Flush pending in-process deltas to disk with a read-modify-write that
|
|
127
|
+
* merges against the current on-disk version. Cross-process races are
|
|
128
|
+
* bounded to single-delta loss worst-case (not full history), because each
|
|
129
|
+
* writer reads fresh and applies only its own pending delta.
|
|
130
|
+
*/
|
|
131
|
+
async function flushPendingDeltasToDisk(): Promise<void> {
|
|
132
|
+
if (_pendingDocs === 0 && _pendingDf.size === 0) return;
|
|
133
|
+
if (!(await tryAcquireCorpusLock())) return;
|
|
134
|
+
try {
|
|
135
|
+
const onDisk = readCorpusFromDisk();
|
|
136
|
+
onDisk.docCount += _pendingDocs;
|
|
137
|
+
for (const [t, d] of _pendingDf) onDisk.df[t] = (onDisk.df[t] ?? 0) + d;
|
|
138
|
+
atomicWriteCorpus(onDisk);
|
|
139
|
+
// In-memory copy reflects our best-known truth
|
|
140
|
+
_corpus = onDisk;
|
|
141
|
+
_pendingDocs = 0;
|
|
142
|
+
_pendingDf.clear();
|
|
143
|
+
} catch {
|
|
144
|
+
// best-effort — don't crash the caller
|
|
145
|
+
} finally {
|
|
146
|
+
releaseCorpusLock();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Explicit, awaitable flush. Short-lived processes (e.g. embed-file-worker)
|
|
152
|
+
* should await this before exit to avoid silently dropping their pending
|
|
153
|
+
* IDF deltas when the setImmediate callback gets pre-empted by process.exit.
|
|
154
|
+
*/
|
|
155
|
+
export async function flushCorpusNow(): Promise<void> {
|
|
156
|
+
await flushPendingDeltasToDisk();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Update the IDF corpus with tokens from a new document.
|
|
161
|
+
* Call this when indexing new content so IDF weights improve over time.
|
|
162
|
+
* Fire-and-forget safe (synchronous, but cheap).
|
|
163
|
+
*
|
|
164
|
+
* Concurrency: in-memory updates are immediate; disk persistence is deferred
|
|
165
|
+
* via setImmediate and uses a delta pattern + advisory lock so concurrent
|
|
166
|
+
* processes don't silently drop each other's history.
|
|
167
|
+
*/
|
|
168
|
+
export function upsertCorpus(text: string): void {
|
|
169
|
+
const c = loadCorpus();
|
|
170
|
+
const tokens = tokenize(text);
|
|
171
|
+
const seen = new Set(tokens);
|
|
172
|
+
c.docCount += 1;
|
|
173
|
+
_pendingDocs += 1;
|
|
174
|
+
for (const t of seen) {
|
|
175
|
+
c.df[t] = (c.df[t] ?? 0) + 1;
|
|
176
|
+
_pendingDf.set(t, (_pendingDf.get(t) ?? 0) + 1);
|
|
177
|
+
}
|
|
178
|
+
_corpus = c;
|
|
179
|
+
setImmediate(() => {
|
|
180
|
+
flushPendingDeltasToDisk().catch(() => { /* best-effort */ });
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// Tokenization
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
const TOKEN_RE = /[a-z0-9_$]+/g;
|
|
189
|
+
|
|
190
|
+
/** Tokenize text: lowercase, extract alphanumeric+underscore runs. */
|
|
191
|
+
export function tokenize(text: string): string[] {
|
|
192
|
+
return (text.toLowerCase().match(TOKEN_RE) ?? []).slice(0, MAX_TOKENS);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// FNV-1a hash projection
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Deterministic hash of a string → integer in [0, EMBED_DIM).
|
|
201
|
+
* Uses FNV-1a 32-bit: fast, no deps, uniform-ish distribution.
|
|
202
|
+
*/
|
|
203
|
+
function fnv1a(s: string): number {
|
|
204
|
+
let h = 0x811c9dc5;
|
|
205
|
+
for (let i = 0; i < s.length; i++) {
|
|
206
|
+
h ^= s.charCodeAt(i);
|
|
207
|
+
h = Math.imul(h, 0x01000193);
|
|
208
|
+
}
|
|
209
|
+
return h >>> 0; // unsigned
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Map a token to a bucket index [0, EMBED_DIM) and a sign (+1 or -1). */
|
|
213
|
+
function tokenToProjection(token: string): { idx: number; sign: number } {
|
|
214
|
+
const h = fnv1a(token);
|
|
215
|
+
const idx = h % EMBED_DIM;
|
|
216
|
+
const sign = (h & 1) === 0 ? 1 : -1;
|
|
217
|
+
return { idx, sign };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// BM25-style pseudo-embedding (local, no ML runtime)
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Build a 256-dim BM25 pseudo-embedding for the given text.
|
|
226
|
+
* Uses TF × IDF weights projected via FNV-1a hash.
|
|
227
|
+
* Returns a normalized Float32Array.
|
|
228
|
+
*/
|
|
229
|
+
export function bm25Embed(text: string): Float32Array {
|
|
230
|
+
const corpus = loadCorpus();
|
|
231
|
+
const tokens = tokenize(text);
|
|
232
|
+
const N = Math.max(corpus.docCount, 1);
|
|
233
|
+
|
|
234
|
+
// TF: raw count
|
|
235
|
+
const tf: Record<string, number> = {};
|
|
236
|
+
for (const t of tokens) {
|
|
237
|
+
tf[t] = (tf[t] ?? 0) + 1;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const vec = new Float32Array(EMBED_DIM);
|
|
241
|
+
|
|
242
|
+
for (const [token, count] of Object.entries(tf)) {
|
|
243
|
+
const df = Math.max(corpus.df[token] ?? 0, MIN_DF);
|
|
244
|
+
// IDF: log((N + 1) / (df + 1)) — smoothed BM25-style
|
|
245
|
+
const idf = Math.log((N + 1) / (df + 1)) + 1;
|
|
246
|
+
const weight = count * idf;
|
|
247
|
+
const { idx, sign } = tokenToProjection(token);
|
|
248
|
+
vec[idx] += sign * weight;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return normalizeInPlace(vec);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
// Remote embedder (Ollama / LM Studio)
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Call a remote embedding endpoint.
|
|
260
|
+
* Returns null on any failure so the caller can fall back to BM25.
|
|
261
|
+
*/
|
|
262
|
+
async function remoteEmbed(text: string): Promise<Float32Array | null> {
|
|
263
|
+
const url = process.env.ASHLR_EMBED_URL;
|
|
264
|
+
if (!url) return null;
|
|
265
|
+
|
|
266
|
+
const model = process.env.ASHLR_EMBED_MODEL ?? "nomic-embed-text";
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const res = await fetch(url, {
|
|
270
|
+
method: "POST",
|
|
271
|
+
headers: { "Content-Type": "application/json" },
|
|
272
|
+
body: JSON.stringify({ model, prompt: text }),
|
|
273
|
+
// 5-second timeout — don't block the grep path
|
|
274
|
+
signal: AbortSignal.timeout(5_000),
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
if (!res.ok) return null;
|
|
278
|
+
|
|
279
|
+
const json = (await res.json()) as { embedding?: number[] };
|
|
280
|
+
if (!Array.isArray(json.embedding)) return null;
|
|
281
|
+
|
|
282
|
+
const vec = new Float32Array(json.embedding);
|
|
283
|
+
return normalizeInPlace(vec);
|
|
284
|
+
} catch {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
// Normalization helper
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
|
|
293
|
+
export function normalizeInPlace(v: Float32Array): Float32Array {
|
|
294
|
+
let sum = 0;
|
|
295
|
+
for (let i = 0; i < v.length; i++) sum += v[i]! * v[i]!;
|
|
296
|
+
const mag = Math.sqrt(sum);
|
|
297
|
+
if (mag === 0) return v;
|
|
298
|
+
for (let i = 0; i < v.length; i++) v[i]! /= mag;
|
|
299
|
+
return v;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// Public API
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Compute an embedding for the given text.
|
|
308
|
+
*
|
|
309
|
+
* Strategy:
|
|
310
|
+
* 1. If ASHLR_EMBED_URL is set, try the remote endpoint.
|
|
311
|
+
* 2. On failure (or no URL), fall back to BM25 pseudo-embedding.
|
|
312
|
+
*
|
|
313
|
+
* The returned Float32Array is already normalized to unit length.
|
|
314
|
+
*/
|
|
315
|
+
export async function embed(text: string): Promise<Float32Array> {
|
|
316
|
+
if (process.env.ASHLR_EMBED_URL) {
|
|
317
|
+
const remote = await remoteEmbed(text);
|
|
318
|
+
if (remote) return remote;
|
|
319
|
+
// Fall through to BM25 on failure
|
|
320
|
+
}
|
|
321
|
+
return bm25Embed(text);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* The dimension of the vector returned by `embed()`.
|
|
326
|
+
* When using a remote model this may differ — callers should use
|
|
327
|
+
* the actual vector length; this constant applies only to BM25.
|
|
328
|
+
*/
|
|
329
|
+
export { EMBED_DIM as BM25_DIM };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability event emitter for ashlr MCP tools.
|
|
3
|
+
*
|
|
4
|
+
* Appends JSONL records to ~/.ashlr/session-log.jsonl (the same file the
|
|
5
|
+
* PostToolUse hook writes to). Purely additive — does NOT change any tool's
|
|
6
|
+
* return shape and never throws.
|
|
7
|
+
*
|
|
8
|
+
* Kill switch: set ASHLR_SESSION_LOG=0 to disable all emission.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { appendFile, mkdir } from "fs/promises";
|
|
12
|
+
import { homedir } from "os";
|
|
13
|
+
import { dirname, join } from "path";
|
|
14
|
+
import { currentSessionId } from "./_stats";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Types
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
export type EventKind =
|
|
21
|
+
| "tool_fallback"
|
|
22
|
+
| "tool_escalate"
|
|
23
|
+
| "tool_error"
|
|
24
|
+
| "tool_noop"
|
|
25
|
+
| "tool_call"
|
|
26
|
+
| "accounting_cache_hit"
|
|
27
|
+
| "tool_crashed"
|
|
28
|
+
// v1.22 Track A — savings-math trust pass
|
|
29
|
+
| "tool_low_confidence_shipped"
|
|
30
|
+
| "tool_skip_micro_edit"
|
|
31
|
+
// v1.22 Track D — LLM provider telemetry
|
|
32
|
+
| "llm_summarize_provider_used"
|
|
33
|
+
// v1.22 Track G — adoption + visibility telemetry
|
|
34
|
+
| "tool_called_after_block"
|
|
35
|
+
| "genome_route_taken"
|
|
36
|
+
| "embed_cache_hit"
|
|
37
|
+
| "embed_cache_miss"
|
|
38
|
+
// v1.23 Track HH — genome stale detection
|
|
39
|
+
| "genome_stale_detected"
|
|
40
|
+
// v1.24 Track E — warm-start corpus tier telemetry
|
|
41
|
+
| "embed_warm_index"
|
|
42
|
+
// v1.27 Track 5 — genome-search inverted index
|
|
43
|
+
| "genome_search_hit"
|
|
44
|
+
| "genome_search_miss"
|
|
45
|
+
// v1.30 Track 3 — wizard completion telemetry
|
|
46
|
+
| "wizard_step";
|
|
47
|
+
|
|
48
|
+
export interface EventPayload {
|
|
49
|
+
tool: string;
|
|
50
|
+
reason?: string;
|
|
51
|
+
extra?: Record<string, unknown>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Path helpers (resolve at call-time so tests overriding $HOME work)
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
function home(): string {
|
|
59
|
+
return process.env.HOME ?? homedir();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function logPath(): string {
|
|
63
|
+
return join(home(), ".ashlr", "session-log.jsonl");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Public API
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Append a single observability event to the session log.
|
|
72
|
+
*
|
|
73
|
+
* Schema mirrors the PostToolUse hook:
|
|
74
|
+
* ts, agent, event, tool, cwd, session, + any extras from payload.extra
|
|
75
|
+
*
|
|
76
|
+
* Best-effort: never throws.
|
|
77
|
+
*/
|
|
78
|
+
export async function logEvent(
|
|
79
|
+
event: EventKind,
|
|
80
|
+
payload: EventPayload,
|
|
81
|
+
): Promise<void> {
|
|
82
|
+
if (process.env.ASHLR_SESSION_LOG === "0") return;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const record: Record<string, unknown> = {
|
|
86
|
+
ts: new Date().toISOString(),
|
|
87
|
+
agent: "ashlr-mcp",
|
|
88
|
+
event,
|
|
89
|
+
tool: payload.tool,
|
|
90
|
+
cwd: process.cwd(),
|
|
91
|
+
session: currentSessionId(),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (payload.reason !== undefined) {
|
|
95
|
+
record.reason = payload.reason;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (payload.extra) {
|
|
99
|
+
for (const [k, v] of Object.entries(payload.extra)) {
|
|
100
|
+
record[k] = v;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const line = JSON.stringify(record) + "\n";
|
|
105
|
+
const path = logPath();
|
|
106
|
+
|
|
107
|
+
// Ensure the directory exists (best-effort; may already exist).
|
|
108
|
+
await mkdir(dirname(path), { recursive: true }).catch(() => undefined);
|
|
109
|
+
await appendFile(path, line, "utf-8");
|
|
110
|
+
} catch {
|
|
111
|
+
// Never propagate — this is observability, not a critical path.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory LRU cache for genome retrieval results.
|
|
3
|
+
*
|
|
4
|
+
* Wraps retrieveSectionsV2 from @ashlr/core-efficiency with a 64-entry LRU
|
|
5
|
+
* keyed by (genomeRoot, pattern). Entries are invalidated when the genome
|
|
6
|
+
* manifest mtime changes — detected cheaply on each cache hit.
|
|
7
|
+
*
|
|
8
|
+
* Never throws: on any miss or error, falls through to direct retrieval.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync, statSync } from "fs";
|
|
12
|
+
import { homedir } from "os";
|
|
13
|
+
import { join } from "path";
|
|
14
|
+
import { runWithTimeout } from "./_run-with-timeout";
|
|
15
|
+
import { createHash } from "crypto";
|
|
16
|
+
import { retrieveSectionsV2 as _retrieveSectionsV2 } from "@ashlr/core-efficiency";
|
|
17
|
+
import { canonicalizeRepoUrl } from "../scripts/genome-cloud-pull";
|
|
18
|
+
|
|
19
|
+
const CAPACITY = 64;
|
|
20
|
+
|
|
21
|
+
type Retriever = typeof _retrieveSectionsV2;
|
|
22
|
+
|
|
23
|
+
interface CacheEntry {
|
|
24
|
+
result: Awaited<ReturnType<Retriever>>;
|
|
25
|
+
manifestMtime: number;
|
|
26
|
+
/** LRU recency counter — higher = more recent. */
|
|
27
|
+
lru: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const cache = new Map<string, CacheEntry>();
|
|
31
|
+
let lruClock = 0;
|
|
32
|
+
|
|
33
|
+
function manifestMtime(genomeRoot: string): number {
|
|
34
|
+
try {
|
|
35
|
+
return statSync(join(genomeRoot, ".ashlrcode", "genome", "manifest.json")).mtimeMs;
|
|
36
|
+
} catch {
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function cacheKey(genomeRoot: string, pattern: string): string {
|
|
42
|
+
return `${genomeRoot}\x00${pattern}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function evictLRU(): void {
|
|
46
|
+
if (cache.size < CAPACITY) return;
|
|
47
|
+
let oldestKey = "";
|
|
48
|
+
let oldestLru = Infinity;
|
|
49
|
+
for (const [k, v] of cache) {
|
|
50
|
+
if (v.lru < oldestLru) { oldestLru = v.lru; oldestKey = k; }
|
|
51
|
+
}
|
|
52
|
+
if (oldestKey) cache.delete(oldestKey);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Retrieve genome sections, returning a cached result when the manifest mtime
|
|
57
|
+
* has not changed since the last retrieval for this (genomeRoot, pattern).
|
|
58
|
+
*
|
|
59
|
+
* The optional `retriever` parameter exists solely for testing: callers can
|
|
60
|
+
* inject a stub without needing `mock.module`, which would leak module state
|
|
61
|
+
* across the full test suite. Production callers omit it and get the real
|
|
62
|
+
* `retrieveSectionsV2` from @ashlr/core-efficiency.
|
|
63
|
+
*/
|
|
64
|
+
export async function retrieveCached(
|
|
65
|
+
genomeRoot: string,
|
|
66
|
+
pattern: string,
|
|
67
|
+
limit: number,
|
|
68
|
+
retriever: Retriever = _retrieveSectionsV2,
|
|
69
|
+
): Promise<Awaited<ReturnType<Retriever>>> {
|
|
70
|
+
const key = cacheKey(genomeRoot, pattern);
|
|
71
|
+
const mtime = manifestMtime(genomeRoot);
|
|
72
|
+
const entry = cache.get(key);
|
|
73
|
+
|
|
74
|
+
if (entry && entry.manifestMtime === mtime) {
|
|
75
|
+
entry.lru = ++lruClock;
|
|
76
|
+
return entry.result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Miss or stale — retrieve directly. Never throw.
|
|
80
|
+
try {
|
|
81
|
+
const result = await retriever(genomeRoot, pattern, limit);
|
|
82
|
+
evictLRU();
|
|
83
|
+
cache.set(key, { result, manifestMtime: mtime, lru: ++lruClock });
|
|
84
|
+
return result;
|
|
85
|
+
} catch {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Exposed for tests — clears all cache state. */
|
|
91
|
+
export function _clearCache(): void {
|
|
92
|
+
cache.clear();
|
|
93
|
+
lruClock = 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Exposed for tests — current cache size. */
|
|
97
|
+
export function _cacheSize(): number {
|
|
98
|
+
return cache.size;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Cloud genome fallback
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Locate the cloud-pulled genome directory for the given working directory.
|
|
107
|
+
*
|
|
108
|
+
* Semantics:
|
|
109
|
+
* - Local `.ashlrcode/genome/` wins if present (callers should check that
|
|
110
|
+
* first). This function is only called when no local genome was found.
|
|
111
|
+
* - Reads the git remote of `cwd`, canonicalizes it, computes the project
|
|
112
|
+
* hash, and checks whether `~/.ashlr/genomes/<hash>/` exists with a valid
|
|
113
|
+
* `.ashlr-cloud-genome` marker file.
|
|
114
|
+
* - Returns the cloud genome directory path, or `null` if unavailable.
|
|
115
|
+
* - Never throws.
|
|
116
|
+
*
|
|
117
|
+
* @param cwd - Working directory to resolve the git remote from.
|
|
118
|
+
* @param home - Override for testing (default: os.homedir()).
|
|
119
|
+
*/
|
|
120
|
+
export async function findCloudGenome(cwd: string, home?: string): Promise<string | null> {
|
|
121
|
+
try {
|
|
122
|
+
const h = home ?? homedir();
|
|
123
|
+
|
|
124
|
+
// Resolve git remote for cwd
|
|
125
|
+
const res = await runWithTimeout({
|
|
126
|
+
command: "git",
|
|
127
|
+
args: ["remote", "get-url", "origin"],
|
|
128
|
+
cwd,
|
|
129
|
+
timeoutMs: 5_000,
|
|
130
|
+
});
|
|
131
|
+
if (res.exitCode !== 0 || !res.stdout) return null;
|
|
132
|
+
const rawRemote = res.stdout.trim();
|
|
133
|
+
if (!rawRemote) return null;
|
|
134
|
+
|
|
135
|
+
const canonUrl = canonicalizeRepoUrl(rawRemote);
|
|
136
|
+
const hash = createHash("sha256").update(canonUrl).digest("hex").slice(0, 8);
|
|
137
|
+
const genomeDir = join(h, ".ashlr", "genomes", hash);
|
|
138
|
+
const markerPath = join(genomeDir, ".ashlr-cloud-genome");
|
|
139
|
+
|
|
140
|
+
if (!existsSync(markerPath)) return null;
|
|
141
|
+
|
|
142
|
+
// Validate the marker is parseable JSON with required fields
|
|
143
|
+
try {
|
|
144
|
+
const marker = JSON.parse(readFileSync(markerPath, "utf-8")) as Record<string, unknown>;
|
|
145
|
+
if (!marker["genomeId"]) return null;
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return genomeDir;
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|