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,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* notebook-edit-server — ashlr__notebook_edit tool implementation.
|
|
3
|
+
*
|
|
4
|
+
* Edits a single cell in a Jupyter notebook (.ipynb) and returns a
|
|
5
|
+
* compressed response that includes only the edited cell and its immediate
|
|
6
|
+
* neighbors. The full notebook is never echoed back — only a surroundingCells
|
|
7
|
+
* window of 3 entries (above, edited, below).
|
|
8
|
+
*
|
|
9
|
+
* Savings baseline: raw = full notebook JSON bytes; compact = response JSON bytes.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFile, writeFile } from "fs/promises";
|
|
13
|
+
import { createHash } from "crypto";
|
|
14
|
+
import { recordSaving } from "./_stats";
|
|
15
|
+
import { clampToCwd } from "./_cwd-clamp";
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Types
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
export interface NotebookEditArgs {
|
|
22
|
+
notebookPath: string;
|
|
23
|
+
cellIndex: number;
|
|
24
|
+
newSource: string;
|
|
25
|
+
cellType?: "code" | "markdown";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface NotebookCell {
|
|
29
|
+
cell_type: string;
|
|
30
|
+
source: string | string[];
|
|
31
|
+
metadata?: Record<string, unknown>;
|
|
32
|
+
outputs?: unknown[];
|
|
33
|
+
execution_count?: number | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface NotebookJson {
|
|
37
|
+
cells: NotebookCell[];
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
|
+
nbformat?: number;
|
|
40
|
+
nbformat_minor?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface SurroundingCell {
|
|
44
|
+
index: number;
|
|
45
|
+
type: string;
|
|
46
|
+
sourcePreview: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface NotebookEditResult {
|
|
50
|
+
notebookPath: string;
|
|
51
|
+
editedCellIndex: number;
|
|
52
|
+
totalCells: number;
|
|
53
|
+
unchangedCellCount: number;
|
|
54
|
+
surroundingCells: SurroundingCell[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Helpers
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
const PREVIEW_MAX = 120;
|
|
62
|
+
|
|
63
|
+
/** Truncate a cell source string for preview output. */
|
|
64
|
+
function previewSource(source: string | string[]): string {
|
|
65
|
+
const flat = Array.isArray(source) ? source.join("") : source;
|
|
66
|
+
if (flat.length <= PREVIEW_MAX) return flat;
|
|
67
|
+
return flat.slice(0, PREVIEW_MAX) + `… (${flat.length - PREVIEW_MAX} more chars)`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Normalize cell source to string. Jupyter stores source as string or string[]. */
|
|
71
|
+
function flattenSource(source: string | string[]): string {
|
|
72
|
+
return Array.isArray(source) ? source.join("") : source;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Store new source back in the notebook cell, preserving the original format
|
|
77
|
+
* (array or string). Most tooling writes arrays; we match the original style.
|
|
78
|
+
*/
|
|
79
|
+
function encodeSource(newSource: string, originalCell: NotebookCell): string | string[] {
|
|
80
|
+
if (Array.isArray(originalCell.source)) {
|
|
81
|
+
// Split on newlines, keep trailing newline chars on each line except last.
|
|
82
|
+
const lines = newSource.split("\n");
|
|
83
|
+
return lines.map((line, i) => (i < lines.length - 1 ? line + "\n" : line));
|
|
84
|
+
}
|
|
85
|
+
return newSource;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Core implementation
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
export async function ashlrNotebookEdit(input: NotebookEditArgs): Promise<NotebookEditResult> {
|
|
93
|
+
const { notebookPath, cellIndex, newSource, cellType } = input;
|
|
94
|
+
|
|
95
|
+
const clamp = clampToCwd(notebookPath, "ashlr__notebook_edit");
|
|
96
|
+
if (!clamp.ok) throw new Error(clamp.message);
|
|
97
|
+
const abs = clamp.abs;
|
|
98
|
+
|
|
99
|
+
const raw = await readFile(abs, "utf-8");
|
|
100
|
+
const rawBytes = raw.length;
|
|
101
|
+
|
|
102
|
+
let nb: NotebookJson;
|
|
103
|
+
try {
|
|
104
|
+
nb = JSON.parse(raw) as NotebookJson;
|
|
105
|
+
} catch {
|
|
106
|
+
throw new Error(`ashlr__notebook_edit: failed to parse notebook JSON at ${notebookPath}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!Array.isArray(nb.cells)) {
|
|
110
|
+
throw new Error(`ashlr__notebook_edit: notebook has no 'cells' array: ${notebookPath}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const totalCells = nb.cells.length;
|
|
114
|
+
|
|
115
|
+
if (cellIndex < 0 || cellIndex >= totalCells) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`ashlr__notebook_edit: cellIndex ${cellIndex} out of range (notebook has ${totalCells} cells)`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const cell = nb.cells[cellIndex]!;
|
|
122
|
+
|
|
123
|
+
// Apply the edit — direct array assignment (no applyRangeEdits abstraction
|
|
124
|
+
// needed; cell-content edits are a full-cell replacement, not a char-level
|
|
125
|
+
// range operation).
|
|
126
|
+
cell.source = encodeSource(newSource, cell);
|
|
127
|
+
if (cellType) {
|
|
128
|
+
cell.cell_type = cellType;
|
|
129
|
+
// Markdown cells don't carry outputs or execution_count.
|
|
130
|
+
if (cellType === "markdown") {
|
|
131
|
+
delete cell.outputs;
|
|
132
|
+
delete cell.execution_count;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const updated = JSON.stringify(nb, null, 1);
|
|
137
|
+
await writeFile(abs, updated, "utf-8");
|
|
138
|
+
|
|
139
|
+
// Build surroundingCells: edited cell + 1 above + 1 below.
|
|
140
|
+
const surroundingCells: SurroundingCell[] = [];
|
|
141
|
+
const indices = [cellIndex - 1, cellIndex, cellIndex + 1].filter(
|
|
142
|
+
(i) => i >= 0 && i < totalCells,
|
|
143
|
+
);
|
|
144
|
+
for (const idx of indices) {
|
|
145
|
+
const c = nb.cells[idx]!;
|
|
146
|
+
surroundingCells.push({
|
|
147
|
+
index: idx,
|
|
148
|
+
type: c.cell_type,
|
|
149
|
+
sourcePreview: previewSource(c.source),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result: NotebookEditResult = {
|
|
154
|
+
notebookPath: abs,
|
|
155
|
+
editedCellIndex: cellIndex,
|
|
156
|
+
totalCells,
|
|
157
|
+
unchangedCellCount: totalCells - 1,
|
|
158
|
+
surroundingCells,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const compactBytes = JSON.stringify(result).length;
|
|
162
|
+
await recordSaving(rawBytes, compactBytes, "ashlr__notebook_edit");
|
|
163
|
+
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Format the result as a human-readable text response for the MCP tool.
|
|
169
|
+
*/
|
|
170
|
+
export function formatNotebookEditResult(result: NotebookEditResult): string {
|
|
171
|
+
const lines: string[] = [
|
|
172
|
+
`[ashlr__notebook_edit] ${result.notebookPath}`,
|
|
173
|
+
` edited cell ${result.editedCellIndex} of ${result.totalCells} (${result.unchangedCellCount} unchanged cells elided)`,
|
|
174
|
+
` surrounding cells:`,
|
|
175
|
+
];
|
|
176
|
+
for (const cell of result.surroundingCells) {
|
|
177
|
+
const marker = cell.index === result.editedCellIndex ? " ← edited" : "";
|
|
178
|
+
lines.push(` [${cell.index}] ${cell.type}${marker}: ${cell.sourcePreview.replace(/\n/g, "↵")}`);
|
|
179
|
+
}
|
|
180
|
+
return lines.join("\n");
|
|
181
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* orient-server-handlers — side-effect module.
|
|
3
|
+
*
|
|
4
|
+
* Importing this file registers the ashlr__orient tool into the shared
|
|
5
|
+
* registry (_tool-base.ts). Logic stays in orient-server.ts during the
|
|
6
|
+
* v1.12 → v1.13 migration window; this module is a thin dispatch adapter
|
|
7
|
+
* so the tool can be served by the in-process router alongside the rest.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { registerTool, type ToolCallContext, type ToolResult } from "./_tool-base";
|
|
11
|
+
import { orient, type OrientArgs } from "./orient-server";
|
|
12
|
+
|
|
13
|
+
registerTool({
|
|
14
|
+
name: "ashlr__orient",
|
|
15
|
+
description:
|
|
16
|
+
"Meta-orientation tool. Answers 'how does X work here?' in a single call: " +
|
|
17
|
+
"runs a project tree scan, derives keywords from your query, discovers relevant " +
|
|
18
|
+
"files (genome retriever if .ashlrcode/genome/ exists, else ripgrep), snipCompacts " +
|
|
19
|
+
"them, and asks a local LLM for a ≤600-char synthesis plus a suggested next tool call. " +
|
|
20
|
+
"Replaces 3-5 round-trips (tree + grep + multiple reads). Read-only.",
|
|
21
|
+
inputSchema: {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
query: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description:
|
|
27
|
+
"Question about the codebase: 'how does auth work', 'where is X defined', 'what's the deploy flow'",
|
|
28
|
+
},
|
|
29
|
+
dir: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Project root (default: process.cwd())",
|
|
32
|
+
},
|
|
33
|
+
depth: {
|
|
34
|
+
type: "string",
|
|
35
|
+
enum: ["quick", "thorough"],
|
|
36
|
+
description:
|
|
37
|
+
"'quick' (1 tool call + 3 reads, ~2s) | 'thorough' (tree + grep + 6 reads, ~4s, default)",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
required: ["query"],
|
|
41
|
+
},
|
|
42
|
+
handler: async (args: Record<string, unknown>, _ctx: ToolCallContext): Promise<ToolResult> => {
|
|
43
|
+
const a = args as Partial<OrientArgs> & { endpointOverride?: string };
|
|
44
|
+
const out = await orient({
|
|
45
|
+
query: typeof a.query === "string" ? a.query : "",
|
|
46
|
+
dir: typeof a.dir === "string" ? a.dir : undefined,
|
|
47
|
+
depth: a.depth === "quick" || a.depth === "thorough" ? a.depth : undefined,
|
|
48
|
+
endpointOverride: typeof a.endpointOverride === "string" ? a.endpointOverride : undefined,
|
|
49
|
+
});
|
|
50
|
+
return { content: [{ type: "text", text: out.text }] };
|
|
51
|
+
},
|
|
52
|
+
});
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* ashlr-orient MCP server.
|
|
4
|
+
*
|
|
5
|
+
* Exposes a single tool:
|
|
6
|
+
* - ashlr__orient — meta-orientation. Answers "how does X work here?" with a
|
|
7
|
+
* single round-trip: baseline tree scan + keyword-derived file discovery
|
|
8
|
+
* (genome retriever if present, else ripgrep) + snipCompact'd file reads
|
|
9
|
+
* + local-LLM synthesis.
|
|
10
|
+
*
|
|
11
|
+
* Replaces 3-5 orientation calls (tree + grep + multiple reads + one synth) the
|
|
12
|
+
* agent would otherwise make in sequence. Read-only; never writes project files.
|
|
13
|
+
*
|
|
14
|
+
* Savings accounting is per-tool via ~/.ashlr/stats.json, shape-compatible with
|
|
15
|
+
* http-server.ts / efficiency-server.ts (byTool[ashlr__orient]).
|
|
16
|
+
*/
|
|
17
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
18
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
|
+
import {
|
|
20
|
+
CallToolRequestSchema,
|
|
21
|
+
ListToolsRequestSchema,
|
|
22
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
23
|
+
import { statSync } from "fs";
|
|
24
|
+
import { readFile } from "fs/promises";
|
|
25
|
+
import { join, relative, resolve } from "path";
|
|
26
|
+
import { spawnSync } from "child_process";
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
formatGenomeForPrompt,
|
|
30
|
+
genomeExists,
|
|
31
|
+
type Message,
|
|
32
|
+
retrieveSectionsV2,
|
|
33
|
+
snipCompact,
|
|
34
|
+
} from "@ashlr/core-efficiency";
|
|
35
|
+
|
|
36
|
+
import { scan, formatBaseline, listFiles } from "../scripts/baseline-scan";
|
|
37
|
+
import { readFileSync } from "fs";
|
|
38
|
+
import { recordSaving as recordSavingCore } from "./_stats";
|
|
39
|
+
import { STOPWORDS, extractKeywords } from "./_text-helpers";
|
|
40
|
+
|
|
41
|
+
// Re-export for callers that historically imported `extractKeywords` from orient-server.
|
|
42
|
+
export { extractKeywords, STOPWORDS };
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Config
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
const MAX_FILES = 8;
|
|
49
|
+
const MAX_PROMPT_BYTES = 30 * 1024;
|
|
50
|
+
const LLM_TIMEOUT_MS = 5_000;
|
|
51
|
+
const PER_FILE_CAP = 4_000; // char cap per snipped file before assembly
|
|
52
|
+
|
|
53
|
+
export const ORIENT_SYSTEM_PROMPT =
|
|
54
|
+
"You are orienting an agent who will code in this repo. Given the query " +
|
|
55
|
+
"and these pre-gathered files, produce a ≤600-char synthesis: what the " +
|
|
56
|
+
"answer is, which files are most relevant (file:line refs), and a " +
|
|
57
|
+
"suggested next tool call for the agent (e.g. `ashlr__read X` or " +
|
|
58
|
+
"`ashlr__grep Y`). Be specific. Don't hallucinate context that isn't shown.";
|
|
59
|
+
|
|
60
|
+
// Records a saving event against the shared stats store.
|
|
61
|
+
async function recordSaving(rawBytes: number, compactBytes: number, tool: string): Promise<void> {
|
|
62
|
+
await recordSavingCore(rawBytes, compactBytes, tool);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// File discovery
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
interface DiscoveredFile {
|
|
70
|
+
path: string; // absolute
|
|
71
|
+
rel: string; // relative to dir
|
|
72
|
+
snippet: string; // snipCompact'd content, capped
|
|
73
|
+
rawBytes: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function rgBinary(): string {
|
|
77
|
+
const viaBun = (globalThis as { Bun?: { which(b: string): string | null } }).Bun;
|
|
78
|
+
const found = viaBun?.which("rg") ?? null;
|
|
79
|
+
if (found) return found;
|
|
80
|
+
for (const p of ["/opt/homebrew/bin/rg", "/usr/local/bin/rg", "/usr/bin/rg"]) {
|
|
81
|
+
try { statSync(p); return p; } catch { /* ignore */ }
|
|
82
|
+
}
|
|
83
|
+
return "rg";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function grepCandidates(dir: string, keywords: string[], cap: number): string[] {
|
|
87
|
+
if (keywords.length === 0) return [];
|
|
88
|
+
// Build a single alternation pattern; case-insensitive.
|
|
89
|
+
const pattern = keywords.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
90
|
+
const res = spawnSync(rgBinary(), ["-l", "-i", "--max-count=1", pattern, dir], {
|
|
91
|
+
encoding: "utf-8",
|
|
92
|
+
timeout: 5_000,
|
|
93
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
94
|
+
});
|
|
95
|
+
let lines: string[] = [];
|
|
96
|
+
if (res.status === 0 || res.status === 1) {
|
|
97
|
+
lines = (res.stdout ?? "")
|
|
98
|
+
.split("\n")
|
|
99
|
+
.map((l) => l.trim())
|
|
100
|
+
.filter((l) => l.length > 0);
|
|
101
|
+
} else {
|
|
102
|
+
// rg unavailable — fall back to a small in-process scan that matches
|
|
103
|
+
// filename OR file contents. Best-effort; caps itself.
|
|
104
|
+
lines = fallbackFileSearch(dir, keywords, cap * 4);
|
|
105
|
+
}
|
|
106
|
+
// Prefer source-ish files: rank by keyword hits / de-prioritize tests + lockfiles.
|
|
107
|
+
const ranked = lines
|
|
108
|
+
.map((p) => {
|
|
109
|
+
let score = 0;
|
|
110
|
+
for (const k of keywords) if (p.toLowerCase().includes(k)) score += 3;
|
|
111
|
+
if (/\.(test|spec)\./.test(p)) score -= 1;
|
|
112
|
+
if (/node_modules|dist|build|\.lock|\.log$/.test(p)) score -= 10;
|
|
113
|
+
return { p, score };
|
|
114
|
+
})
|
|
115
|
+
.filter((r) => r.score > -5)
|
|
116
|
+
.sort((a, b) => b.score - a.score);
|
|
117
|
+
return ranked.slice(0, cap).map((r) => r.p);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function fallbackFileSearch(dir: string, keywords: string[], cap: number): string[] {
|
|
121
|
+
const { files } = listFiles(dir, 2000);
|
|
122
|
+
const kw = keywords.map((k) => k.toLowerCase());
|
|
123
|
+
const hits: string[] = [];
|
|
124
|
+
for (const rel of files) {
|
|
125
|
+
if (hits.length >= cap) break;
|
|
126
|
+
const relLower = rel.toLowerCase();
|
|
127
|
+
let matched = kw.some((k) => relLower.includes(k));
|
|
128
|
+
if (!matched) {
|
|
129
|
+
try {
|
|
130
|
+
const buf = readFileSync(join(dir, rel));
|
|
131
|
+
// Cheap: skip files > 256 KB and obvious binaries.
|
|
132
|
+
if (buf.length > 256 * 1024) continue;
|
|
133
|
+
const head = buf.subarray(0, Math.min(buf.length, 32 * 1024)).toString("utf-8").toLowerCase();
|
|
134
|
+
matched = kw.some((k) => head.includes(k));
|
|
135
|
+
} catch { continue; }
|
|
136
|
+
}
|
|
137
|
+
if (matched) hits.push(join(dir, rel));
|
|
138
|
+
}
|
|
139
|
+
return hits;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function snipFile(content: string): string {
|
|
143
|
+
const msgs: Message[] = [
|
|
144
|
+
{
|
|
145
|
+
role: "user",
|
|
146
|
+
content: [{ type: "tool_result", tool_use_id: "ashlr-orient", content }],
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
const compact = snipCompact(msgs);
|
|
150
|
+
const block = (compact[0]!.content as Array<{ type: string; content: string }>)[0]!;
|
|
151
|
+
const text = (block as { content: string }).content ?? content;
|
|
152
|
+
return text.length > PER_FILE_CAP ? text.slice(0, PER_FILE_CAP) + "\n[... snipped ...]" : text;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function gatherViaGrep(
|
|
156
|
+
dir: string,
|
|
157
|
+
keywords: string[],
|
|
158
|
+
cap: number,
|
|
159
|
+
): Promise<DiscoveredFile[]> {
|
|
160
|
+
const paths = grepCandidates(dir, keywords, cap);
|
|
161
|
+
const out: DiscoveredFile[] = [];
|
|
162
|
+
for (const p of paths) {
|
|
163
|
+
try {
|
|
164
|
+
const raw = await readFile(p, "utf-8");
|
|
165
|
+
out.push({
|
|
166
|
+
path: p,
|
|
167
|
+
rel: relative(dir, p),
|
|
168
|
+
snippet: snipFile(raw),
|
|
169
|
+
rawBytes: Buffer.byteLength(raw, "utf-8"),
|
|
170
|
+
});
|
|
171
|
+
} catch {
|
|
172
|
+
/* skip unreadable */
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function gatherViaGenome(
|
|
179
|
+
dir: string,
|
|
180
|
+
query: string,
|
|
181
|
+
): Promise<{ files: DiscoveredFile[]; genomeText: string }> {
|
|
182
|
+
const sections = await retrieveSectionsV2(dir, query, 4000);
|
|
183
|
+
if (sections.length === 0) return { files: [], genomeText: "" };
|
|
184
|
+
const genomeText = formatGenomeForPrompt(sections);
|
|
185
|
+
const files: DiscoveredFile[] = sections.slice(0, MAX_FILES).map((s) => {
|
|
186
|
+
const content = typeof s.content === "string" ? s.content : "";
|
|
187
|
+
const snippet = content.length > PER_FILE_CAP
|
|
188
|
+
? content.slice(0, PER_FILE_CAP) + "\n[... snipped ...]"
|
|
189
|
+
: content;
|
|
190
|
+
const rel = (s as { path?: string }).path ?? (s as { title?: string }).title ?? "(genome)";
|
|
191
|
+
return {
|
|
192
|
+
path: join(dir, rel),
|
|
193
|
+
rel,
|
|
194
|
+
snippet,
|
|
195
|
+
rawBytes: Buffer.byteLength(content, "utf-8"),
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
return { files, genomeText };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// LLM call (OpenAI-compatible; ASHLR_LLM_URL → fallback LM Studio)
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
interface CallLLMOpts {
|
|
206
|
+
systemPrompt: string;
|
|
207
|
+
userContent: string;
|
|
208
|
+
timeoutMs?: number;
|
|
209
|
+
endpointOverride?: string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function callLocalLLM(opts: CallLLMOpts): Promise<string | null> {
|
|
213
|
+
const base = opts.endpointOverride ?? process.env.ASHLR_LLM_URL ?? "http://localhost:1234/v1";
|
|
214
|
+
const apiKey = process.env.ASHLR_LLM_KEY ?? "local-llm";
|
|
215
|
+
const model = process.env.ASHLR_LLM_MODEL ?? "qwen/qwen3-coder-30b@8bit";
|
|
216
|
+
const timeoutMs = opts.timeoutMs ?? LLM_TIMEOUT_MS;
|
|
217
|
+
|
|
218
|
+
const ctl = new AbortController();
|
|
219
|
+
const t = setTimeout(() => ctl.abort(), timeoutMs);
|
|
220
|
+
try {
|
|
221
|
+
const res = await fetch(`${base}/chat/completions`, {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
224
|
+
signal: ctl.signal,
|
|
225
|
+
body: JSON.stringify({
|
|
226
|
+
model,
|
|
227
|
+
stream: false,
|
|
228
|
+
max_tokens: 500,
|
|
229
|
+
temperature: 0.1,
|
|
230
|
+
messages: [
|
|
231
|
+
{ role: "system", content: opts.systemPrompt },
|
|
232
|
+
{ role: "user", content: opts.userContent },
|
|
233
|
+
],
|
|
234
|
+
}),
|
|
235
|
+
});
|
|
236
|
+
clearTimeout(t);
|
|
237
|
+
if (!res.ok) return null;
|
|
238
|
+
const j = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> };
|
|
239
|
+
const text = j.choices?.[0]?.message?.content?.trim();
|
|
240
|
+
return text && text.length > 0 ? text : null;
|
|
241
|
+
} catch {
|
|
242
|
+
clearTimeout(t);
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// Main orient()
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
export interface OrientArgs {
|
|
252
|
+
query: string;
|
|
253
|
+
dir?: string;
|
|
254
|
+
depth?: "quick" | "thorough";
|
|
255
|
+
/** Test hook: override LLM endpoint. */
|
|
256
|
+
endpointOverride?: string;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export interface OrientResult {
|
|
260
|
+
text: string;
|
|
261
|
+
files: string[];
|
|
262
|
+
rawBytes: number;
|
|
263
|
+
outputBytes: number;
|
|
264
|
+
fellBack: boolean;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export async function orient(args: OrientArgs): Promise<OrientResult> {
|
|
268
|
+
const dir = resolve(args.dir ?? process.cwd());
|
|
269
|
+
const depth = args.depth ?? "thorough";
|
|
270
|
+
const query = (args.query ?? "").trim();
|
|
271
|
+
const fileCap = depth === "quick" ? 3 : MAX_FILES;
|
|
272
|
+
|
|
273
|
+
// 1. Tree scan (reuse baseline-scan).
|
|
274
|
+
let baselineText = "";
|
|
275
|
+
try {
|
|
276
|
+
baselineText = formatBaseline(scan({ dir }));
|
|
277
|
+
} catch {
|
|
278
|
+
baselineText = `[baseline scan failed for ${dir}]`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 2. Keywords.
|
|
282
|
+
const keywords = extractKeywords(query);
|
|
283
|
+
|
|
284
|
+
// 3. Discovery: genome first (if present AND we have a non-empty query), else grep.
|
|
285
|
+
let files: DiscoveredFile[] = [];
|
|
286
|
+
let genomeText = "";
|
|
287
|
+
let route: "genome" | "grep" | "none" = "none";
|
|
288
|
+
if (query.length > 0) {
|
|
289
|
+
if (genomeExists(dir)) {
|
|
290
|
+
try {
|
|
291
|
+
const g = await gatherViaGenome(dir, query);
|
|
292
|
+
if (g.files.length > 0) {
|
|
293
|
+
files = g.files.slice(0, fileCap);
|
|
294
|
+
genomeText = g.genomeText;
|
|
295
|
+
route = "genome";
|
|
296
|
+
}
|
|
297
|
+
} catch { /* fall through to grep */ }
|
|
298
|
+
}
|
|
299
|
+
if (files.length === 0 && keywords.length > 0) {
|
|
300
|
+
files = await gatherViaGrep(dir, keywords, fileCap);
|
|
301
|
+
if (files.length > 0) route = "grep";
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// 4. Assemble LLM user content under the 30 KB cap.
|
|
306
|
+
const header =
|
|
307
|
+
`QUERY: ${query || "(empty)"}\n\n` +
|
|
308
|
+
`PROJECT BASELINE:\n${baselineText}\n\n` +
|
|
309
|
+
(route === "genome" ? `GENOME:\n${genomeText}\n\n` : "") +
|
|
310
|
+
`KEYWORDS: ${keywords.join(", ") || "(none)"}\n` +
|
|
311
|
+
`ROUTE: ${route}\n\nFILES:\n`;
|
|
312
|
+
const parts: string[] = [header];
|
|
313
|
+
let bytesSoFar = Buffer.byteLength(header, "utf-8");
|
|
314
|
+
const consulted: string[] = [];
|
|
315
|
+
let totalRawBytes = Buffer.byteLength(baselineText, "utf-8");
|
|
316
|
+
for (const f of files) {
|
|
317
|
+
totalRawBytes += f.rawBytes;
|
|
318
|
+
const block = `--- ${f.rel} ---\n${f.snippet}\n\n`;
|
|
319
|
+
const blockBytes = Buffer.byteLength(block, "utf-8");
|
|
320
|
+
if (bytesSoFar + blockBytes > MAX_PROMPT_BYTES) break;
|
|
321
|
+
parts.push(block);
|
|
322
|
+
bytesSoFar += blockBytes;
|
|
323
|
+
consulted.push(f.rel);
|
|
324
|
+
}
|
|
325
|
+
const userContent = parts.join("");
|
|
326
|
+
|
|
327
|
+
// 5. LLM synthesis (with fallback).
|
|
328
|
+
const llmOut = await callLocalLLM({
|
|
329
|
+
systemPrompt: ORIENT_SYSTEM_PROMPT,
|
|
330
|
+
userContent,
|
|
331
|
+
timeoutMs: LLM_TIMEOUT_MS,
|
|
332
|
+
endpointOverride: args.endpointOverride,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
let synthesis: string;
|
|
336
|
+
let fellBack = false;
|
|
337
|
+
if (llmOut) {
|
|
338
|
+
synthesis = llmOut;
|
|
339
|
+
} else {
|
|
340
|
+
fellBack = true;
|
|
341
|
+
const top = consulted.slice(0, 3).join(", ") || "(none found)";
|
|
342
|
+
const nextHint = consulted[0]
|
|
343
|
+
? `You probably want to read ${consulted[0]} next (e.g. ashlr__read ${consulted[0]}).`
|
|
344
|
+
: keywords[0]
|
|
345
|
+
? `Try ashlr__grep "${keywords[0]}" to locate relevant code.`
|
|
346
|
+
: "Refine your query with more specific terms.";
|
|
347
|
+
synthesis =
|
|
348
|
+
`[ashlr__orient · LLM unreachable, fallback summary]\n` +
|
|
349
|
+
`Top files: ${top}. Based on keywords: ${keywords.join(", ") || "(none)"}. ${nextHint}`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const body =
|
|
353
|
+
synthesis +
|
|
354
|
+
`\n\n[files consulted: ${consulted.length ? consulted.join(", ") : "(none)"} · route: ${route} · depth: ${depth}]`;
|
|
355
|
+
|
|
356
|
+
await recordSaving(totalRawBytes, Buffer.byteLength(body, "utf-8"), "ashlr__orient");
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
text: body,
|
|
360
|
+
files: consulted,
|
|
361
|
+
rawBytes: totalRawBytes,
|
|
362
|
+
outputBytes: Buffer.byteLength(body, "utf-8"),
|
|
363
|
+
fellBack,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
// MCP wiring
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
|
|
371
|
+
const server = new Server(
|
|
372
|
+
{ name: "ashlr-orient", version: "0.1.0" },
|
|
373
|
+
{ capabilities: { tools: {} } },
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
377
|
+
tools: [
|
|
378
|
+
{
|
|
379
|
+
name: "ashlr__orient",
|
|
380
|
+
description:
|
|
381
|
+
"Meta-orientation tool. Answers 'how does X work here?' in a single call: " +
|
|
382
|
+
"runs a project tree scan, derives keywords from your query, discovers relevant " +
|
|
383
|
+
"files (genome retriever if .ashlrcode/genome/ exists, else ripgrep), snipCompacts " +
|
|
384
|
+
"them, and asks a local LLM for a ≤600-char synthesis plus a suggested next tool call. " +
|
|
385
|
+
"Replaces 3-5 round-trips (tree + grep + multiple reads). Read-only.",
|
|
386
|
+
inputSchema: {
|
|
387
|
+
type: "object",
|
|
388
|
+
properties: {
|
|
389
|
+
query: {
|
|
390
|
+
type: "string",
|
|
391
|
+
description:
|
|
392
|
+
"Question about the codebase: 'how does auth work', 'where is X defined', 'what's the deploy flow'",
|
|
393
|
+
},
|
|
394
|
+
dir: {
|
|
395
|
+
type: "string",
|
|
396
|
+
description: "Project root (default: process.cwd())",
|
|
397
|
+
},
|
|
398
|
+
depth: {
|
|
399
|
+
type: "string",
|
|
400
|
+
enum: ["quick", "thorough"],
|
|
401
|
+
description:
|
|
402
|
+
"'quick' (1 tool call + 3 reads, ~2s) | 'thorough' (tree + grep + 6 reads, ~4s, default)",
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
required: ["query"],
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
],
|
|
409
|
+
}));
|
|
410
|
+
|
|
411
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
412
|
+
const { name, arguments: args } = req.params;
|
|
413
|
+
try {
|
|
414
|
+
if (name !== "ashlr__orient") {
|
|
415
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
416
|
+
}
|
|
417
|
+
const a = (args ?? {}) as Partial<OrientArgs>;
|
|
418
|
+
const out = await orient({
|
|
419
|
+
query: typeof a.query === "string" ? a.query : "",
|
|
420
|
+
dir: typeof a.dir === "string" ? a.dir : undefined,
|
|
421
|
+
depth: a.depth === "quick" || a.depth === "thorough" ? a.depth : undefined,
|
|
422
|
+
endpointOverride: typeof (a as { endpointOverride?: string }).endpointOverride === "string"
|
|
423
|
+
? (a as { endpointOverride?: string }).endpointOverride
|
|
424
|
+
: undefined,
|
|
425
|
+
});
|
|
426
|
+
return { content: [{ type: "text", text: out.text }] };
|
|
427
|
+
} catch (err) {
|
|
428
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
429
|
+
return { content: [{ type: "text", text: `ashlr__orient error: ${message}` }], isError: true };
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
if (import.meta.main) {
|
|
434
|
+
const transport = new StdioServerTransport();
|
|
435
|
+
await server.connect(transport);
|
|
436
|
+
}
|