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,699 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ashlr-rename-file — v1.19 module-path refactor.
|
|
3
|
+
*
|
|
4
|
+
* Complements v1.18's `ashlr__edit_structural rename-cross-file` (which
|
|
5
|
+
* renames IDENTIFIERS across files). This tool renames MODULE PATHS: you
|
|
6
|
+
* move/rename a source file on disk and every import specifier in the
|
|
7
|
+
* project that resolved to it is rewritten in-place.
|
|
8
|
+
*
|
|
9
|
+
* Scope rules:
|
|
10
|
+
* - `from` / `to` are both cwd-clamped (clampToCwd). Any path that escapes
|
|
11
|
+
* cwd → refusal.
|
|
12
|
+
* - `from` must exist as a regular file; `to` must not exist.
|
|
13
|
+
* - `to`'s parent directory must already exist (we don't auto-create dirs
|
|
14
|
+
* — the caller is explicit about where the file lands).
|
|
15
|
+
* - Binary files (by extension) are rejected so we don't ever mutate
|
|
16
|
+
* non-source assets.
|
|
17
|
+
*
|
|
18
|
+
* Importer detection (ripgrep-based, not a naive whole-tree scan):
|
|
19
|
+
* - We shell out to `rg -l` to enumerate files containing the *basename*
|
|
20
|
+
* of `from` (without extension) in an import/require/export-from
|
|
21
|
+
* statement, bounded to .ts/.tsx/.js/.jsx files and subject to the
|
|
22
|
+
* `roots` filter if provided.
|
|
23
|
+
* - For each candidate, we parse every `import ... from "spec"`,
|
|
24
|
+
* `require("spec")`, and `export ... from "spec"` string literal and
|
|
25
|
+
* resolve it relative to the importer. If the resolved target equals
|
|
26
|
+
* `from`, the specifier is rewritten to the appropriate relative form
|
|
27
|
+
* of `to`.
|
|
28
|
+
* - Extension elision (`./foo` → `./foo.ts`) and index resolution
|
|
29
|
+
* (`./foo` → `./foo/index.ts`) are both honored. The rewritten
|
|
30
|
+
* specifier preserves the caller's original extension style (if the
|
|
31
|
+
* import omitted the extension, the new specifier does too).
|
|
32
|
+
* - Bare package specifiers (`react`, `@scope/pkg`) are never touched.
|
|
33
|
+
*
|
|
34
|
+
* Apply semantics:
|
|
35
|
+
* - Dry run lists every planned edit; nothing is written and the file is
|
|
36
|
+
* not moved.
|
|
37
|
+
* - On apply: every text edit is written first, then `fs.rename(from, to)`.
|
|
38
|
+
* If any edit fails to write, the remaining writes are skipped and the
|
|
39
|
+
* already-written files are best-effort rolled back from our in-memory
|
|
40
|
+
* pre-image. We only rename `from → to` if every text edit succeeded.
|
|
41
|
+
* - No force mode. No symlink following. Intentionally tight scope for v1.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import { access, readFile, rename, stat, writeFile } from "fs/promises";
|
|
45
|
+
import { accessSync, realpathSync, statSync } from "fs";
|
|
46
|
+
import { spawnSync } from "child_process";
|
|
47
|
+
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
48
|
+
import { clampToCwd } from "./_cwd-clamp";
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Types
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
export interface RenameFileArgs {
|
|
55
|
+
from: string;
|
|
56
|
+
to: string;
|
|
57
|
+
dryRun?: boolean;
|
|
58
|
+
maxFiles?: number;
|
|
59
|
+
roots?: string[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ImporterEdit {
|
|
63
|
+
/** Absolute path of the importer file. */
|
|
64
|
+
path: string;
|
|
65
|
+
/** Byte offsets into the importer's source (inside the string literal). */
|
|
66
|
+
start: number;
|
|
67
|
+
end: number;
|
|
68
|
+
/** The original specifier text (without surrounding quotes). */
|
|
69
|
+
oldSpecifier: string;
|
|
70
|
+
/** The rewritten specifier text (without surrounding quotes). */
|
|
71
|
+
newSpecifier: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface RenameFilePlan {
|
|
75
|
+
from: string;
|
|
76
|
+
to: string;
|
|
77
|
+
/** All edits grouped by importer. Each inner array is non-empty. */
|
|
78
|
+
edits: Array<{ path: string; edits: ImporterEdit[]; source: string }>;
|
|
79
|
+
/** Candidate files rg surfaced but that had no matching specifier. */
|
|
80
|
+
skipped: number;
|
|
81
|
+
/** Non-fatal warnings (e.g., maxFiles truncation). */
|
|
82
|
+
warnings: string[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type RenameFileResult =
|
|
86
|
+
| { ok: true; plan: RenameFilePlan; written: number; renamed: boolean }
|
|
87
|
+
| { ok: false; reason: string };
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Constants
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
const DEFAULT_MAX_FILES = 200;
|
|
94
|
+
|
|
95
|
+
/** Source file extensions we'll follow. */
|
|
96
|
+
const SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Binary extensions we flat-out refuse — catching user accidents like
|
|
100
|
+
* `ashlr__rename_file from=assets/hero.png to=assets/banner.png`. This is
|
|
101
|
+
* NOT a security check (the caller could still move these with `mv`); it's
|
|
102
|
+
* a contract: this tool understands import-path semantics, which binaries
|
|
103
|
+
* don't have. Keep the list conservative — if an unknown extension slips
|
|
104
|
+
* through, the downstream rg scan will simply return zero importers.
|
|
105
|
+
*/
|
|
106
|
+
const BINARY_EXTS = new Set([
|
|
107
|
+
".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif", ".ico", ".bmp",
|
|
108
|
+
".pdf", ".wasm", ".woff", ".woff2", ".ttf", ".otf", ".eot",
|
|
109
|
+
".mp3", ".mp4", ".mov", ".avi", ".webm", ".ogg", ".wav", ".flac",
|
|
110
|
+
".zip", ".tar", ".gz", ".bz2", ".7z", ".rar",
|
|
111
|
+
".so", ".dylib", ".dll", ".exe", ".class",
|
|
112
|
+
".db", ".sqlite", ".sqlite3",
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// rg resolver (duplicated from efficiency-server to avoid circular import)
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
function resolveRg(): string {
|
|
120
|
+
const candidates = [
|
|
121
|
+
"/opt/homebrew/bin/rg",
|
|
122
|
+
"/usr/local/bin/rg",
|
|
123
|
+
"/usr/bin/rg",
|
|
124
|
+
// codex vendor bundle (present on some macOS dev machines)
|
|
125
|
+
"/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/path/rg",
|
|
126
|
+
"/opt/homebrew/lib/node_modules/@openai/codex/bin/rg",
|
|
127
|
+
];
|
|
128
|
+
const bun = (globalThis as { Bun?: { which(bin: string): string | null } }).Bun;
|
|
129
|
+
const fromBun = bun ? bun.which("rg") : null;
|
|
130
|
+
if (fromBun) return fromBun;
|
|
131
|
+
for (const p of candidates) {
|
|
132
|
+
try {
|
|
133
|
+
accessSync(p);
|
|
134
|
+
return p;
|
|
135
|
+
} catch {
|
|
136
|
+
// keep trying
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return "rg";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Specifier resolution
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Resolve an import specifier against the importer's directory to an
|
|
148
|
+
* absolute filesystem path. Handles extension elision and `index.<ext>`
|
|
149
|
+
* lookup. Returns `null` for bare specifiers (packages) and unresolvable
|
|
150
|
+
* paths — callers MUST check for null before comparing.
|
|
151
|
+
*/
|
|
152
|
+
function resolveSpecifier(importerDir: string, spec: string): string | null {
|
|
153
|
+
// Bare specifier: doesn't start with `.` or `/`. Examples: `react`,
|
|
154
|
+
// `@scope/pkg`, `node:fs`. Never a candidate for rewrite.
|
|
155
|
+
if (!spec.startsWith(".") && !spec.startsWith("/") && !isAbsolute(spec)) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const base = isAbsolute(spec) ? spec : resolve(importerDir, spec);
|
|
160
|
+
|
|
161
|
+
// 1. Exact path as written — but ONLY if it's a regular file. If `base`
|
|
162
|
+
// is a directory (e.g., `./foo` where `foo/` is a dir with index.ts),
|
|
163
|
+
// Node resolves it via the index lookup below, not as the dir itself.
|
|
164
|
+
if (regularFileExistsSync(base)) return base;
|
|
165
|
+
|
|
166
|
+
// 2. Extension elision: spec lacks an extension, try each source ext.
|
|
167
|
+
if (!SOURCE_EXTS.has(extname(base))) {
|
|
168
|
+
for (const ext of SOURCE_EXTS) {
|
|
169
|
+
if (regularFileExistsSync(base + ext)) return base + ext;
|
|
170
|
+
}
|
|
171
|
+
// 3. Directory + index.<ext>
|
|
172
|
+
for (const ext of SOURCE_EXTS) {
|
|
173
|
+
const idx = join(base, "index" + ext);
|
|
174
|
+
if (regularFileExistsSync(idx)) return idx;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function regularFileExistsSync(p: string): boolean {
|
|
182
|
+
try {
|
|
183
|
+
const s = statSync(p);
|
|
184
|
+
return s.isFile();
|
|
185
|
+
} catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Given the absolute path of the importer, the absolute path of the
|
|
192
|
+
* new target (`to`), and the original specifier text (so we can preserve
|
|
193
|
+
* extension style + index style), produce the new specifier string.
|
|
194
|
+
*
|
|
195
|
+
* - Always use POSIX separators (import specifiers are URL-ish).
|
|
196
|
+
* - Prefix with `./` if the relative path is in the same dir (otherwise
|
|
197
|
+
* Node/TS treats it as a bare package).
|
|
198
|
+
* - If the original specifier omitted the extension, the new one does too.
|
|
199
|
+
* - If the original specifier resolved via `index.<ext>`, strip
|
|
200
|
+
* `/index` from the new one so it stays symmetric.
|
|
201
|
+
*/
|
|
202
|
+
function computeNewSpecifier(
|
|
203
|
+
importerDir: string,
|
|
204
|
+
toAbs: string,
|
|
205
|
+
oldSpec: string,
|
|
206
|
+
resolvedFromAbs: string,
|
|
207
|
+
): string {
|
|
208
|
+
let rel = relative(importerDir, toAbs);
|
|
209
|
+
// Normalize to POSIX separators — import strings are always forward slash.
|
|
210
|
+
if (sep !== "/") rel = rel.split(sep).join("/");
|
|
211
|
+
|
|
212
|
+
// Did the original specifier carry an extension we recognize?
|
|
213
|
+
const oldExt = posixExtname(oldSpec);
|
|
214
|
+
const hadExt = SOURCE_EXTS.has(oldExt);
|
|
215
|
+
|
|
216
|
+
// Did the original resolve via `index.<ext>` (and did the spec NOT
|
|
217
|
+
// spell out `/index` itself)?
|
|
218
|
+
const toFile = basenameNoSepStripped(resolvedFromAbs);
|
|
219
|
+
const resolvedViaIndex =
|
|
220
|
+
/^index\.(ts|tsx|js|jsx|mjs|cjs)$/.test(toFile) &&
|
|
221
|
+
!oldSpec.endsWith("/index") &&
|
|
222
|
+
!/\/index\.(ts|tsx|js|jsx|mjs|cjs)$/.test(oldSpec);
|
|
223
|
+
|
|
224
|
+
// If the new `to` is also an `index.<ext>` and the caller imported via
|
|
225
|
+
// directory-index, strip the `/index.<ext>` tail so `"./bar"` continues
|
|
226
|
+
// to resolve via index. If the caller spelled out `/index`, leave it be.
|
|
227
|
+
if (resolvedViaIndex) {
|
|
228
|
+
const tail = /\/index\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
229
|
+
if (tail.test(rel)) rel = rel.replace(tail, "");
|
|
230
|
+
} else if (!hadExt) {
|
|
231
|
+
// Extension elision: the original spec omitted the extension. Strip
|
|
232
|
+
// the extension from the new one so we stay consistent.
|
|
233
|
+
const ext = posixExtname(rel);
|
|
234
|
+
if (SOURCE_EXTS.has(ext)) {
|
|
235
|
+
rel = rel.slice(0, -ext.length);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Ensure relative prefix. `relative()` may produce `bar.ts` (same dir) —
|
|
240
|
+
// which as an import spec is a bare package, not a relative import.
|
|
241
|
+
if (!rel.startsWith(".") && !rel.startsWith("/")) {
|
|
242
|
+
rel = "./" + rel;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return rel;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function posixExtname(p: string): string {
|
|
249
|
+
const idx = p.lastIndexOf(".");
|
|
250
|
+
const slashIdx = p.lastIndexOf("/");
|
|
251
|
+
if (idx < 0 || idx < slashIdx) return "";
|
|
252
|
+
return p.slice(idx);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function basenameNoSepStripped(p: string): string {
|
|
256
|
+
const parts = p.split(/[\\/]/);
|
|
257
|
+
return parts[parts.length - 1] ?? "";
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// Import-specifier extraction
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Regex matching an import/require/export-from statement's string literal
|
|
266
|
+
* and capturing the specifier text plus quote character. Covers:
|
|
267
|
+
*
|
|
268
|
+
* import x from "foo"
|
|
269
|
+
* import { a, b } from 'foo'
|
|
270
|
+
* import "foo"
|
|
271
|
+
* import * as x from "foo"
|
|
272
|
+
* export { x } from "foo"
|
|
273
|
+
* export * from "foo"
|
|
274
|
+
* const x = require("foo")
|
|
275
|
+
* const x = require('foo')
|
|
276
|
+
* await import("foo")
|
|
277
|
+
*
|
|
278
|
+
* Group 1: specifier text. Quote character is captured so we can match the
|
|
279
|
+
* closing quote without cross-pollinating on mismatched quotes.
|
|
280
|
+
*
|
|
281
|
+
* Deliberately narrow: we don't try to handle template literals or
|
|
282
|
+
* computed dynamic imports. v1 targets the 99% case; anything exotic is
|
|
283
|
+
* left untouched and the caller sees a "no matching specifier" skip.
|
|
284
|
+
*/
|
|
285
|
+
const IMPORT_RE =
|
|
286
|
+
// eslint-disable-next-line no-useless-escape
|
|
287
|
+
/(?:^|[^\w$])(?:import|export)(?:[\s\S]*?)\s+from\s+(['"])([^'"\n]+?)\1|(?:^|[^\w$])import\s+(['"])([^'"\n]+?)\3|(?:^|[^\w$])(?:require|import)\s*\(\s*(['"])([^'"\n]+?)\5\s*\)/g;
|
|
288
|
+
|
|
289
|
+
interface RawMatch {
|
|
290
|
+
/** Byte offset of the opening quote (inclusive). */
|
|
291
|
+
quoteStart: number;
|
|
292
|
+
/** Specifier text (without quotes). */
|
|
293
|
+
specifier: string;
|
|
294
|
+
/** Length of specifier text. */
|
|
295
|
+
specLen: number;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function extractImportMatches(source: string): RawMatch[] {
|
|
299
|
+
const out: RawMatch[] = [];
|
|
300
|
+
IMPORT_RE.lastIndex = 0;
|
|
301
|
+
let m: RegExpExecArray | null;
|
|
302
|
+
while ((m = IMPORT_RE.exec(source))) {
|
|
303
|
+
// Which alternation fired? Check groups 2 / 4 / 6.
|
|
304
|
+
let quote: string;
|
|
305
|
+
let spec: string;
|
|
306
|
+
if (m[2] !== undefined) {
|
|
307
|
+
quote = m[1]!;
|
|
308
|
+
spec = m[2]!;
|
|
309
|
+
} else if (m[4] !== undefined) {
|
|
310
|
+
quote = m[3]!;
|
|
311
|
+
spec = m[4]!;
|
|
312
|
+
} else if (m[6] !== undefined) {
|
|
313
|
+
quote = m[5]!;
|
|
314
|
+
spec = m[6]!;
|
|
315
|
+
} else {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
// Find the opening quote position inside the full match. m.index points
|
|
319
|
+
// at the leading separator/newline — scan forward for the quote.
|
|
320
|
+
const quoteIdx = source.indexOf(quote + spec + quote, m.index);
|
|
321
|
+
if (quoteIdx < 0) continue;
|
|
322
|
+
out.push({
|
|
323
|
+
quoteStart: quoteIdx,
|
|
324
|
+
specifier: spec,
|
|
325
|
+
specLen: spec.length,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
return out;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
// Candidate discovery via ripgrep
|
|
333
|
+
// ---------------------------------------------------------------------------
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Return the list of absolute file paths that could plausibly import
|
|
337
|
+
* `fromAbs` — files that mention the basename (with or without extension)
|
|
338
|
+
* inside an import/require/export-from string literal. Falls back to a
|
|
339
|
+
* broad list of any TS/JS files containing the basename if rg is unavailable.
|
|
340
|
+
*/
|
|
341
|
+
function discoverCandidates(
|
|
342
|
+
fromAbs: string,
|
|
343
|
+
searchRoots: string[],
|
|
344
|
+
cap: number,
|
|
345
|
+
): { files: string[]; truncated: boolean } {
|
|
346
|
+
const base = basenameNoSepStripped(fromAbs);
|
|
347
|
+
const baseNoExt = base.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
|
|
348
|
+
// For directory-index files (`foo/index.ts`), the importing specifier is
|
|
349
|
+
// typically the PARENT dir (`./foo`), not `index`. We add the parent dir
|
|
350
|
+
// name as a second prefilter needle so those importers surface.
|
|
351
|
+
const parentName =
|
|
352
|
+
/^index\.(ts|tsx|js|jsx|mjs|cjs)$/.test(base)
|
|
353
|
+
? basenameNoSepStripped(dirname(fromAbs))
|
|
354
|
+
: null;
|
|
355
|
+
|
|
356
|
+
const rg = resolveRg();
|
|
357
|
+
const files = new Set<string>();
|
|
358
|
+
let truncated = false;
|
|
359
|
+
|
|
360
|
+
const runRg = (needle: string): void => {
|
|
361
|
+
if (!needle) return;
|
|
362
|
+
const args = [
|
|
363
|
+
"--files-with-matches",
|
|
364
|
+
"--fixed-strings",
|
|
365
|
+
"--glob", "*.ts",
|
|
366
|
+
"--glob", "*.tsx",
|
|
367
|
+
"--glob", "*.js",
|
|
368
|
+
"--glob", "*.jsx",
|
|
369
|
+
"--glob", "*.mjs",
|
|
370
|
+
"--glob", "*.cjs",
|
|
371
|
+
needle,
|
|
372
|
+
...searchRoots,
|
|
373
|
+
];
|
|
374
|
+
const res = spawnSync(rg, args, { encoding: "utf-8", timeout: 15_000 });
|
|
375
|
+
if (res.error || (res.status !== 0 && res.status !== 1)) return;
|
|
376
|
+
for (const line of (res.stdout ?? "").split("\n")) {
|
|
377
|
+
const p = line.trim();
|
|
378
|
+
if (!p) continue;
|
|
379
|
+
if (files.size >= cap) {
|
|
380
|
+
truncated = true;
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
// Canonicalize so symlinked tmpdirs on macOS (/var → /private/var)
|
|
384
|
+
// produce paths comparable to the clamp's canonicalized `from`.
|
|
385
|
+
let canonical: string;
|
|
386
|
+
try {
|
|
387
|
+
canonical = realpathSync(p);
|
|
388
|
+
} catch {
|
|
389
|
+
canonical = p;
|
|
390
|
+
}
|
|
391
|
+
files.add(canonical);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
runRg(baseNoExt);
|
|
396
|
+
if (base !== baseNoExt) runRg(base);
|
|
397
|
+
if (parentName && parentName !== baseNoExt) runRg(parentName);
|
|
398
|
+
|
|
399
|
+
return { files: Array.from(files), truncated };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ---------------------------------------------------------------------------
|
|
403
|
+
// Core planner
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
|
|
406
|
+
export async function planRenameFile(args: RenameFileArgs): Promise<RenameFileResult> {
|
|
407
|
+
const { from, to, maxFiles = DEFAULT_MAX_FILES, roots } = args;
|
|
408
|
+
|
|
409
|
+
if (!from || typeof from !== "string") {
|
|
410
|
+
return { ok: false, reason: "'from' is required" };
|
|
411
|
+
}
|
|
412
|
+
if (!to || typeof to !== "string") {
|
|
413
|
+
return { ok: false, reason: "'to' is required" };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const fromClamp = clampToCwd(from, "ashlr__rename_file");
|
|
417
|
+
if (!fromClamp.ok) return { ok: false, reason: fromClamp.message };
|
|
418
|
+
const toClamp = clampToCwd(to, "ashlr__rename_file");
|
|
419
|
+
if (!toClamp.ok) return { ok: false, reason: toClamp.message };
|
|
420
|
+
|
|
421
|
+
const fromAbs = fromClamp.abs;
|
|
422
|
+
const toAbs = toClamp.abs;
|
|
423
|
+
|
|
424
|
+
// Same-path is a no-op refusal so the caller can't accidentally truncate
|
|
425
|
+
// the file via self-rename.
|
|
426
|
+
if (fromAbs === toAbs) {
|
|
427
|
+
return { ok: false, reason: `'from' and 'to' resolve to the same path: ${fromAbs}` };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// `from` must be a regular file.
|
|
431
|
+
let fromStat;
|
|
432
|
+
try {
|
|
433
|
+
fromStat = await stat(fromAbs);
|
|
434
|
+
} catch {
|
|
435
|
+
return { ok: false, reason: `'from' does not exist: ${from}` };
|
|
436
|
+
}
|
|
437
|
+
if (!fromStat.isFile()) {
|
|
438
|
+
return { ok: false, reason: `'from' is not a regular file: ${from}` };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Binary-file refusal.
|
|
442
|
+
const fromExt = extname(fromAbs).toLowerCase();
|
|
443
|
+
if (BINARY_EXTS.has(fromExt)) {
|
|
444
|
+
return { ok: false, reason: `binary files are not supported (extension ${fromExt}): ${from}` };
|
|
445
|
+
}
|
|
446
|
+
const toExt = extname(toAbs).toLowerCase();
|
|
447
|
+
if (BINARY_EXTS.has(toExt)) {
|
|
448
|
+
return { ok: false, reason: `binary files are not supported (extension ${toExt}): ${to}` };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// `to` must NOT exist.
|
|
452
|
+
try {
|
|
453
|
+
await access(toAbs);
|
|
454
|
+
return { ok: false, reason: `'to' already exists: ${to}` };
|
|
455
|
+
} catch {
|
|
456
|
+
// desired: does not exist
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// `to`'s parent directory MUST exist.
|
|
460
|
+
const toDir = dirname(toAbs);
|
|
461
|
+
try {
|
|
462
|
+
const s = await stat(toDir);
|
|
463
|
+
if (!s.isDirectory()) {
|
|
464
|
+
return { ok: false, reason: `destination parent is not a directory: ${toDir}` };
|
|
465
|
+
}
|
|
466
|
+
} catch {
|
|
467
|
+
return { ok: false, reason: `destination directory does not exist (create it first): ${toDir}` };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Determine the search roots. If the caller passed `roots`, clamp each.
|
|
471
|
+
// Otherwise default to cwd.
|
|
472
|
+
const searchRoots: string[] = [];
|
|
473
|
+
if (Array.isArray(roots) && roots.length > 0) {
|
|
474
|
+
for (const r of roots) {
|
|
475
|
+
const c = clampToCwd(r, "ashlr__rename_file");
|
|
476
|
+
if (!c.ok) return { ok: false, reason: c.message };
|
|
477
|
+
searchRoots.push(c.abs);
|
|
478
|
+
}
|
|
479
|
+
} else {
|
|
480
|
+
searchRoots.push(process.cwd());
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Discover candidate importers.
|
|
484
|
+
const { files: candidates, truncated } = discoverCandidates(fromAbs, searchRoots, maxFiles);
|
|
485
|
+
|
|
486
|
+
const warnings: string[] = [];
|
|
487
|
+
if (truncated) {
|
|
488
|
+
warnings.push(
|
|
489
|
+
`ripgrep returned more than ${maxFiles} candidates — only the first ${maxFiles} were scanned. ` +
|
|
490
|
+
`Rerun with a larger maxFiles or narrower roots.`,
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// For each candidate, parse import specifiers and resolve to an abs path.
|
|
495
|
+
const perFileEdits: Array<{ path: string; edits: ImporterEdit[]; source: string }> = [];
|
|
496
|
+
let skipped = 0;
|
|
497
|
+
|
|
498
|
+
for (const importer of candidates) {
|
|
499
|
+
// Skip the renamed file itself — relevant edits don't apply to it
|
|
500
|
+
// (its own path is what we're changing, not a reference to another file).
|
|
501
|
+
if (importer === fromAbs) {
|
|
502
|
+
skipped++;
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
let source: string;
|
|
506
|
+
try {
|
|
507
|
+
source = await readFile(importer, "utf-8");
|
|
508
|
+
} catch {
|
|
509
|
+
skipped++;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const matches = extractImportMatches(source);
|
|
514
|
+
if (matches.length === 0) {
|
|
515
|
+
skipped++;
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const importerDir = dirname(importer);
|
|
520
|
+
const edits: ImporterEdit[] = [];
|
|
521
|
+
|
|
522
|
+
for (const m of matches) {
|
|
523
|
+
const resolved = resolveSpecifier(importerDir, m.specifier);
|
|
524
|
+
if (!resolved || resolved !== fromAbs) continue;
|
|
525
|
+
|
|
526
|
+
const newSpec = computeNewSpecifier(importerDir, toAbs, m.specifier, resolved);
|
|
527
|
+
if (newSpec === m.specifier) continue; // already correct, no-op
|
|
528
|
+
|
|
529
|
+
edits.push({
|
|
530
|
+
path: importer,
|
|
531
|
+
start: m.quoteStart + 1, // inside opening quote
|
|
532
|
+
end: m.quoteStart + 1 + m.specLen, // inside closing quote
|
|
533
|
+
oldSpecifier: m.specifier,
|
|
534
|
+
newSpecifier: newSpec,
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (edits.length === 0) {
|
|
539
|
+
skipped++;
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
perFileEdits.push({ path: importer, edits, source });
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
ok: true,
|
|
547
|
+
plan: {
|
|
548
|
+
from: fromAbs,
|
|
549
|
+
to: toAbs,
|
|
550
|
+
edits: perFileEdits,
|
|
551
|
+
skipped,
|
|
552
|
+
warnings,
|
|
553
|
+
},
|
|
554
|
+
written: 0,
|
|
555
|
+
renamed: false,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// ---------------------------------------------------------------------------
|
|
560
|
+
// Apply (write edits, then rename)
|
|
561
|
+
// ---------------------------------------------------------------------------
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Apply a plan from `planRenameFile`. Writes each importer's updated text,
|
|
565
|
+
* then renames `from → to`. On any write failure, best-effort rolls back
|
|
566
|
+
* every already-written importer using the cached pre-image. The file
|
|
567
|
+
* itself is only renamed if every text edit succeeded.
|
|
568
|
+
*
|
|
569
|
+
* Concurrency note: we sequence writes (not parallel) because a failure
|
|
570
|
+
* rollback is simpler when we know exactly which files made it to disk.
|
|
571
|
+
*/
|
|
572
|
+
export async function applyRenameFilePlan(
|
|
573
|
+
plan: RenameFilePlan,
|
|
574
|
+
): Promise<{ written: number; renamed: boolean; rollbackWarnings: string[] }> {
|
|
575
|
+
const writtenPaths: Array<{ path: string; original: string }> = [];
|
|
576
|
+
const rollbackWarnings: string[] = [];
|
|
577
|
+
|
|
578
|
+
for (const fe of plan.edits) {
|
|
579
|
+
const updated = applyEditsToSource(fe.source, fe.edits);
|
|
580
|
+
try {
|
|
581
|
+
await writeFile(fe.path, updated, "utf-8");
|
|
582
|
+
writtenPaths.push({ path: fe.path, original: fe.source });
|
|
583
|
+
} catch (err) {
|
|
584
|
+
// Rollback everything we wrote.
|
|
585
|
+
for (const prev of writtenPaths) {
|
|
586
|
+
try {
|
|
587
|
+
await writeFile(prev.path, prev.original, "utf-8");
|
|
588
|
+
} catch (rbErr) {
|
|
589
|
+
rollbackWarnings.push(
|
|
590
|
+
`rollback failed for ${prev.path}: ${(rbErr as Error).message}`,
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
595
|
+
throw new Error(`failed to write ${fe.path}: ${msg}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// All text edits written — now rename the file itself.
|
|
600
|
+
try {
|
|
601
|
+
await rename(plan.from, plan.to);
|
|
602
|
+
} catch (err) {
|
|
603
|
+
// File rename failed — roll back importer edits so the codebase stays
|
|
604
|
+
// consistent (references still point at the un-moved file).
|
|
605
|
+
for (const prev of writtenPaths) {
|
|
606
|
+
try {
|
|
607
|
+
await writeFile(prev.path, prev.original, "utf-8");
|
|
608
|
+
} catch (rbErr) {
|
|
609
|
+
rollbackWarnings.push(
|
|
610
|
+
`rollback failed for ${prev.path}: ${(rbErr as Error).message}`,
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
615
|
+
throw new Error(`failed to rename ${plan.from} → ${plan.to}: ${msg}`);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return { written: writtenPaths.length, renamed: true, rollbackWarnings };
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Apply a set of ImporterEdits (all against the same source) and return
|
|
623
|
+
* the updated text. Edits are applied right-to-left so earlier offsets
|
|
624
|
+
* remain valid throughout the rewrite. Overlap detection is a sanity net —
|
|
625
|
+
* our extractor never emits overlapping ranges, but a future extension
|
|
626
|
+
* might, and silently miscounting bytes would corrupt the file.
|
|
627
|
+
*/
|
|
628
|
+
function applyEditsToSource(source: string, edits: ImporterEdit[]): string {
|
|
629
|
+
const sorted = [...edits].sort((a, b) => b.start - a.start);
|
|
630
|
+
for (let i = 0; i < sorted.length - 1; i++) {
|
|
631
|
+
if (sorted[i]!.start < sorted[i + 1]!.end) {
|
|
632
|
+
throw new Error("rename-file: overlapping edits detected");
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
let out = source;
|
|
636
|
+
for (const e of sorted) {
|
|
637
|
+
out = out.slice(0, e.start) + e.newSpecifier + out.slice(e.end);
|
|
638
|
+
}
|
|
639
|
+
return out;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// ---------------------------------------------------------------------------
|
|
643
|
+
// Top-level callable (used by the MCP handler)
|
|
644
|
+
// ---------------------------------------------------------------------------
|
|
645
|
+
|
|
646
|
+
export async function ashlrRenameFile(args: RenameFileArgs): Promise<string> {
|
|
647
|
+
const planRes = await planRenameFile(args);
|
|
648
|
+
if (!planRes.ok) {
|
|
649
|
+
throw new Error(planRes.reason);
|
|
650
|
+
}
|
|
651
|
+
const { plan } = planRes;
|
|
652
|
+
const dryRun = args.dryRun === true;
|
|
653
|
+
|
|
654
|
+
const importerCount = plan.edits.length;
|
|
655
|
+
const totalEdits = plan.edits.reduce((s, f) => s + f.edits.length, 0);
|
|
656
|
+
|
|
657
|
+
const lines: string[] = [];
|
|
658
|
+
lines.push(
|
|
659
|
+
`[ashlr__rename_file] ${dryRun ? "(dry run) " : ""}${plan.from} → ${plan.to}`,
|
|
660
|
+
);
|
|
661
|
+
lines.push(
|
|
662
|
+
` ${importerCount} importer${importerCount === 1 ? "" : "s"}, ${totalEdits} specifier${totalEdits === 1 ? "" : "s"} updated, ${plan.skipped} candidate${plan.skipped === 1 ? "" : "s"} skipped`,
|
|
663
|
+
);
|
|
664
|
+
if (plan.warnings.length > 0) {
|
|
665
|
+
lines.push(" warnings:");
|
|
666
|
+
for (const w of plan.warnings) lines.push(` ${w}`);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// List every planned edit when the set is small enough to read at a glance.
|
|
670
|
+
if (importerCount <= 20) {
|
|
671
|
+
for (const fe of plan.edits) {
|
|
672
|
+
lines.push(` ${fe.path}`);
|
|
673
|
+
for (const e of fe.edits) {
|
|
674
|
+
lines.push(` "${e.oldSpecifier}" → "${e.newSpecifier}"`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (dryRun) {
|
|
680
|
+
lines.push(" (dry run — no files written, file not renamed)");
|
|
681
|
+
return lines.join("\n");
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (importerCount === 0) {
|
|
685
|
+
// Still rename the file itself — the caller asked us to move it; the
|
|
686
|
+
// absence of importers just means nothing else needs updating.
|
|
687
|
+
await rename(plan.from, plan.to);
|
|
688
|
+
lines.push(" renamed");
|
|
689
|
+
return lines.join("\n");
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const applied = await applyRenameFilePlan(plan);
|
|
693
|
+
lines.push(` ${applied.written} file${applied.written === 1 ? "" : "s"} written, renamed`);
|
|
694
|
+
if (applied.rollbackWarnings.length > 0) {
|
|
695
|
+
lines.push(" rollback warnings:");
|
|
696
|
+
for (const w of applied.rollbackWarnings) lines.push(` ${w}`);
|
|
697
|
+
}
|
|
698
|
+
return lines.join("\n");
|
|
699
|
+
}
|