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,587 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* genome-cloud-sync — Q2 plugin-side cloud delta sync.
|
|
4
|
+
*
|
|
5
|
+
* Polls GET /genome/cloud-deltas?genome_id=X&since_cursor=N from the Pro
|
|
6
|
+
* backend and writes each returned delta into the local genome:
|
|
7
|
+
*
|
|
8
|
+
* commit → .ashlrcode/genome/sections/commits/<sha>.json
|
|
9
|
+
* pr_merged → .ashlrcode/genome/sections/prs/<number>.json (Q3)
|
|
10
|
+
* issue_closed → .ashlrcode/genome/sections/issues/<number>.json (Q3)
|
|
11
|
+
*
|
|
12
|
+
* Q3 update: PR / issue deltas now write into dedicated retrieval-friendly
|
|
13
|
+
* subdirs (prs/ + issues/) with structured PrSection / IssueSection payloads
|
|
14
|
+
* instead of envelope-wrapped raw deltas in cloud/. The retrievers in
|
|
15
|
+
* _genome-prs.ts + _genome-issues.ts consume those files directly. Older
|
|
16
|
+
* envelope files in cloud/ are left untouched for forward-compat with users
|
|
17
|
+
* mid-upgrade — retrieval only looks at prs/ + issues/.
|
|
18
|
+
*
|
|
19
|
+
* Cursor state lives at `.ashlrcode/genome/_cloud-cursor.json` and is only
|
|
20
|
+
* advanced on a fully successful pull. Network failures leave the cursor
|
|
21
|
+
* untouched so the next session retries.
|
|
22
|
+
*
|
|
23
|
+
* Tier gate (HARDCODED — first line of syncCloudDeltas):
|
|
24
|
+
* free → returns { skipped: true, reason: "free-tier" } immediately
|
|
25
|
+
* pro / team → proceeds
|
|
26
|
+
*
|
|
27
|
+
* Best-effort budget: SessionStart calls this with a 2s hard ceiling. The
|
|
28
|
+
* caller (hooks/session-start.ts) is responsible for the timeout; this
|
|
29
|
+
* function itself just tries the network and writes whatever it can.
|
|
30
|
+
*
|
|
31
|
+
* CLI:
|
|
32
|
+
* bun scripts/genome-cloud-sync.ts # actually sync
|
|
33
|
+
* bun scripts/genome-cloud-sync.ts --dry-run # print plan, write nothing
|
|
34
|
+
* bun scripts/genome-cloud-sync.ts --limit=50 # cap per-request page size
|
|
35
|
+
*
|
|
36
|
+
* Privacy: cloud sections store only what the server returned — title,
|
|
37
|
+
* message, file paths, optional 2-3 sentence summary. No diff content.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync, unlinkSync } from "fs";
|
|
41
|
+
import { homedir } from "os";
|
|
42
|
+
import { dirname, join } from "path";
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Constants
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
const API_URL_DEFAULT = process.env["ASHLR_API_URL"] ?? "https://api.ashlr.ai";
|
|
49
|
+
|
|
50
|
+
/** Hard cap on cloud sections kept on disk (across all kinds). */
|
|
51
|
+
export const CLOUD_RETENTION_LIMIT = 200;
|
|
52
|
+
|
|
53
|
+
/** Default per-request page size when --limit isn't supplied. */
|
|
54
|
+
export const DEFAULT_LIMIT = 100;
|
|
55
|
+
|
|
56
|
+
/** Subdirectory under .ashlrcode/genome/sections/ for non-commit cloud entries. */
|
|
57
|
+
export const CLOUD_SUBDIR = "cloud";
|
|
58
|
+
|
|
59
|
+
/** Subdirectory for commit deltas — shared with the local commit watcher. */
|
|
60
|
+
export const COMMITS_SUBDIR = "commits";
|
|
61
|
+
|
|
62
|
+
/** Q3 — dedicated subdir for `pr_merged` deltas, consumed by _genome-prs.ts. */
|
|
63
|
+
export const PRS_SUBDIR = "prs";
|
|
64
|
+
|
|
65
|
+
/** Q3 — dedicated subdir for `issue_closed` deltas, consumed by _genome-issues.ts. */
|
|
66
|
+
export const ISSUES_SUBDIR = "issues";
|
|
67
|
+
|
|
68
|
+
/** Q3 — per-subdir cap (independent of CLOUD_RETENTION_LIMIT). */
|
|
69
|
+
export const PR_RETENTION_LIMIT = 100;
|
|
70
|
+
export const ISSUE_RETENTION_LIMIT = 100;
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Types
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
export type Tier = "free" | "pro" | "team";
|
|
77
|
+
|
|
78
|
+
export type DeltaKind = "commit" | "pr_merged" | "issue_closed";
|
|
79
|
+
|
|
80
|
+
export interface CloudDelta {
|
|
81
|
+
id: number;
|
|
82
|
+
cursor: number;
|
|
83
|
+
kind: DeltaKind;
|
|
84
|
+
sourceSha: string;
|
|
85
|
+
recordedAt: string;
|
|
86
|
+
payload: unknown;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface CloudDeltasResponse {
|
|
90
|
+
deltas: CloudDelta[];
|
|
91
|
+
nextCursor: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface CloudCursorFile {
|
|
95
|
+
genomeId: string | null;
|
|
96
|
+
cursor: number;
|
|
97
|
+
lastSyncedAt: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface SyncResult {
|
|
101
|
+
skipped?: true;
|
|
102
|
+
reason?: string;
|
|
103
|
+
written?: number;
|
|
104
|
+
cursorBefore?: number;
|
|
105
|
+
cursorAfter?: number;
|
|
106
|
+
pruned?: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type FetchFn = (url: string, init?: RequestInit) => Promise<Response>;
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Path helpers
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
export function genomeRoot(cwd: string): string {
|
|
116
|
+
return join(cwd, ".ashlrcode", "genome");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function sectionsRoot(cwd: string): string {
|
|
120
|
+
return join(genomeRoot(cwd), "sections");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function commitsDir(cwd: string): string {
|
|
124
|
+
return join(sectionsRoot(cwd), COMMITS_SUBDIR);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function cloudDir(cwd: string): string {
|
|
128
|
+
return join(sectionsRoot(cwd), CLOUD_SUBDIR);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function prsDir(cwd: string): string {
|
|
132
|
+
return join(sectionsRoot(cwd), PRS_SUBDIR);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function issuesDir(cwd: string): string {
|
|
136
|
+
return join(sectionsRoot(cwd), ISSUES_SUBDIR);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function cursorPath(cwd: string): string {
|
|
140
|
+
return join(genomeRoot(cwd), "_cloud-cursor.json");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// Cursor I/O
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
export function readCursor(cwd: string): CloudCursorFile | null {
|
|
148
|
+
const p = cursorPath(cwd);
|
|
149
|
+
if (!existsSync(p)) return null;
|
|
150
|
+
try {
|
|
151
|
+
return JSON.parse(readFileSync(p, "utf-8")) as CloudCursorFile;
|
|
152
|
+
} catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function writeCursor(cwd: string, c: CloudCursorFile): void {
|
|
158
|
+
const p = cursorPath(cwd);
|
|
159
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
160
|
+
writeFileSync(p, JSON.stringify(c, null, 2), "utf-8");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Pro token + genome-id discovery
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
function readProToken(home: string): string {
|
|
168
|
+
try {
|
|
169
|
+
return readFileSync(join(home, ".ashlr", "pro-token"), "utf-8").trim();
|
|
170
|
+
} catch {
|
|
171
|
+
return "";
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Discover the cloud genome id for the current repo. Reads the marker file
|
|
177
|
+
* written by genome-cloud-pull (`~/.ashlr/genomes/<hash>/.ashlr-cloud-genome`).
|
|
178
|
+
*
|
|
179
|
+
* Returns null when no marker exists — meaning the user hasn't yet completed
|
|
180
|
+
* a full cloud-pull for this repo, so there's no genome to sync deltas for.
|
|
181
|
+
*/
|
|
182
|
+
export function findGenomeIdForCwd(home: string, cwd: string): string | null {
|
|
183
|
+
try {
|
|
184
|
+
const { spawnSync } = require("child_process") as typeof import("child_process");
|
|
185
|
+
const res = spawnSync("git", ["remote", "get-url", "origin"], {
|
|
186
|
+
cwd,
|
|
187
|
+
timeout: 2000,
|
|
188
|
+
encoding: "utf-8",
|
|
189
|
+
});
|
|
190
|
+
if (res.status !== 0 || !res.stdout) return null;
|
|
191
|
+
const remote = (res.stdout as string).trim();
|
|
192
|
+
// Canonicalize: lowercase, strip .git, ssh→https. Match server-side rules.
|
|
193
|
+
let url = remote;
|
|
194
|
+
const ssh = url.match(/^git@([^:]+):(.+)$/);
|
|
195
|
+
if (ssh) url = `https://${ssh[1]}/${ssh[2]}`;
|
|
196
|
+
url = url.toLowerCase().replace(/\/+$/, "");
|
|
197
|
+
if (url.endsWith(".git")) url = url.slice(0, -4);
|
|
198
|
+
url = url.replace(/\/+$/, "");
|
|
199
|
+
|
|
200
|
+
const { createHash } = require("crypto") as typeof import("crypto");
|
|
201
|
+
const hash = createHash("sha256").update(url).digest("hex").slice(0, 8);
|
|
202
|
+
const marker = join(home, ".ashlr", "genomes", hash, ".ashlr-cloud-genome");
|
|
203
|
+
if (!existsSync(marker)) return null;
|
|
204
|
+
const parsed = JSON.parse(readFileSync(marker, "utf-8")) as { genomeId?: string };
|
|
205
|
+
return parsed.genomeId ?? null;
|
|
206
|
+
} catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// Section writers (one per delta kind)
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
function writeCommitSection(cwd: string, delta: CloudDelta): string {
|
|
216
|
+
const dir = commitsDir(cwd);
|
|
217
|
+
mkdirSync(dir, { recursive: true });
|
|
218
|
+
const p = join(dir, `${delta.sourceSha}.json`);
|
|
219
|
+
writeFileSync(p, JSON.stringify(delta.payload, null, 2), "utf-8");
|
|
220
|
+
return p;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Best-effort GitHub URL builder. The webhook payload doesn't include the
|
|
225
|
+
* canonical URL, so we reconstruct from the cwd's git remote (origin). When
|
|
226
|
+
* we can't read it we still write the section — just with an empty url string.
|
|
227
|
+
*/
|
|
228
|
+
function buildGithubUrl(cwd: string, kind: "pull" | "issues", num: number | string): string {
|
|
229
|
+
try {
|
|
230
|
+
const { spawnSync } = require("child_process") as typeof import("child_process");
|
|
231
|
+
const res = spawnSync("git", ["remote", "get-url", "origin"], {
|
|
232
|
+
cwd,
|
|
233
|
+
timeout: 2000,
|
|
234
|
+
encoding: "utf-8",
|
|
235
|
+
});
|
|
236
|
+
if (res.status !== 0 || !res.stdout) return "";
|
|
237
|
+
let url = (res.stdout as string).trim();
|
|
238
|
+
const ssh = url.match(/^git@([^:]+):(.+)$/);
|
|
239
|
+
if (ssh) url = `https://${ssh[1]}/${ssh[2]}`;
|
|
240
|
+
if (url.endsWith(".git")) url = url.slice(0, -4);
|
|
241
|
+
url = url.replace(/\/+$/, "");
|
|
242
|
+
return `${url}/${kind}/${num}`;
|
|
243
|
+
} catch {
|
|
244
|
+
return "";
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
interface PrPayloadShape {
|
|
249
|
+
kind?: string;
|
|
250
|
+
number?: number;
|
|
251
|
+
title?: string;
|
|
252
|
+
mergedSha?: string;
|
|
253
|
+
author?: string;
|
|
254
|
+
mergedAt?: string;
|
|
255
|
+
filesChanged?: string[];
|
|
256
|
+
summary?: string;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
interface IssuePayloadShape {
|
|
260
|
+
kind?: string;
|
|
261
|
+
number?: number;
|
|
262
|
+
title?: string;
|
|
263
|
+
closedAt?: string;
|
|
264
|
+
author?: string;
|
|
265
|
+
labels?: string[];
|
|
266
|
+
summary?: string;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Q3: write a structured PR section to sections/prs/<number>.json.
|
|
271
|
+
* Schema matches PrSection in _manifest-v2.ts so retrieval can read it back.
|
|
272
|
+
*/
|
|
273
|
+
function writePrSection(cwd: string, delta: CloudDelta): string {
|
|
274
|
+
const dir = prsDir(cwd);
|
|
275
|
+
mkdirSync(dir, { recursive: true });
|
|
276
|
+
const p = (delta.payload ?? {}) as PrPayloadShape;
|
|
277
|
+
const number = p.number ?? Number(delta.sourceSha) ?? 0;
|
|
278
|
+
const id = String(number);
|
|
279
|
+
const section = {
|
|
280
|
+
id,
|
|
281
|
+
title: (p.title ?? "").slice(0, 512),
|
|
282
|
+
mergedAt: p.mergedAt ?? "",
|
|
283
|
+
author: p.author ?? "",
|
|
284
|
+
filesChanged: Array.isArray(p.filesChanged) ? p.filesChanged : [],
|
|
285
|
+
summary: (p.summary ?? "").slice(0, 1024),
|
|
286
|
+
url: buildGithubUrl(cwd, "pull", number),
|
|
287
|
+
};
|
|
288
|
+
const out = join(dir, `${id}.json`);
|
|
289
|
+
writeFileSync(out, JSON.stringify(section, null, 2), "utf-8");
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Q3: write a structured Issue section to sections/issues/<number>.json.
|
|
295
|
+
* Schema matches IssueSection in _manifest-v2.ts.
|
|
296
|
+
*/
|
|
297
|
+
function writeIssueSection(cwd: string, delta: CloudDelta): string {
|
|
298
|
+
const dir = issuesDir(cwd);
|
|
299
|
+
mkdirSync(dir, { recursive: true });
|
|
300
|
+
const p = (delta.payload ?? {}) as IssuePayloadShape;
|
|
301
|
+
const number = p.number ?? Number(delta.sourceSha) ?? 0;
|
|
302
|
+
const id = String(number);
|
|
303
|
+
const section = {
|
|
304
|
+
id,
|
|
305
|
+
title: (p.title ?? "").slice(0, 512),
|
|
306
|
+
closedAt: p.closedAt ?? "",
|
|
307
|
+
author: p.author ?? "",
|
|
308
|
+
labels: Array.isArray(p.labels) ? p.labels : [],
|
|
309
|
+
summary: (p.summary ?? "").slice(0, 1024),
|
|
310
|
+
url: buildGithubUrl(cwd, "issues", number),
|
|
311
|
+
};
|
|
312
|
+
const out = join(dir, `${id}.json`);
|
|
313
|
+
writeFileSync(out, JSON.stringify(section, null, 2), "utf-8");
|
|
314
|
+
return out;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function applyDelta(cwd: string, delta: CloudDelta): string | null {
|
|
318
|
+
if (delta.kind === "commit") return writeCommitSection(cwd, delta);
|
|
319
|
+
if (delta.kind === "pr_merged") return writePrSection(cwd, delta);
|
|
320
|
+
if (delta.kind === "issue_closed") return writeIssueSection(cwd, delta);
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
// Pruner — keep last CLOUD_RETENTION_LIMIT total cloud sections by mtime
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
|
|
328
|
+
export function pruneCloudSections(cwd: string, limit = CLOUD_RETENTION_LIMIT): number {
|
|
329
|
+
const dirs = [commitsDir(cwd), cloudDir(cwd)];
|
|
330
|
+
type Entry = { path: string; mtimeMs: number };
|
|
331
|
+
const all: Entry[] = [];
|
|
332
|
+
for (const d of dirs) {
|
|
333
|
+
if (!existsSync(d)) continue;
|
|
334
|
+
for (const name of readdirSync(d)) {
|
|
335
|
+
if (!name.endsWith(".json")) continue;
|
|
336
|
+
const p = join(d, name);
|
|
337
|
+
try {
|
|
338
|
+
const st = statSync(p);
|
|
339
|
+
all.push({ path: p, mtimeMs: st.mtimeMs });
|
|
340
|
+
} catch {
|
|
341
|
+
// ignore
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (all.length <= limit) return 0;
|
|
346
|
+
all.sort((a, b) => b.mtimeMs - a.mtimeMs); // newest first
|
|
347
|
+
const toDrop = all.slice(limit);
|
|
348
|
+
let dropped = 0;
|
|
349
|
+
for (const ent of toDrop) {
|
|
350
|
+
try {
|
|
351
|
+
unlinkSync(ent.path);
|
|
352
|
+
dropped++;
|
|
353
|
+
} catch {
|
|
354
|
+
// ignore
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return dropped;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Q3: prune a single retrieval subdir (prs/ or issues/) to the last `limit`
|
|
362
|
+
* files by mtime. Independent of the aggregate CLOUD_RETENTION_LIMIT —
|
|
363
|
+
* PR + issue retrieval expects a deeper history (100 each) so users can
|
|
364
|
+
* still find "auth bug" from 3 weeks ago.
|
|
365
|
+
*
|
|
366
|
+
* Returns number of files dropped. No-op when the dir doesn't exist or
|
|
367
|
+
* holds fewer files than `limit`.
|
|
368
|
+
*/
|
|
369
|
+
export function pruneSubdir(cwd: string, subdir: string, limit: number): number {
|
|
370
|
+
const dir = join(sectionsRoot(cwd), subdir);
|
|
371
|
+
if (!existsSync(dir)) return 0;
|
|
372
|
+
type Entry = { path: string; mtimeMs: number };
|
|
373
|
+
const all: Entry[] = [];
|
|
374
|
+
for (const name of readdirSync(dir)) {
|
|
375
|
+
if (!name.endsWith(".json")) continue;
|
|
376
|
+
const p = join(dir, name);
|
|
377
|
+
try {
|
|
378
|
+
const st = statSync(p);
|
|
379
|
+
all.push({ path: p, mtimeMs: st.mtimeMs });
|
|
380
|
+
} catch {
|
|
381
|
+
// ignore
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (all.length <= limit) return 0;
|
|
385
|
+
all.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
386
|
+
const toDrop = all.slice(limit);
|
|
387
|
+
let dropped = 0;
|
|
388
|
+
for (const ent of toDrop) {
|
|
389
|
+
try {
|
|
390
|
+
unlinkSync(ent.path);
|
|
391
|
+
dropped++;
|
|
392
|
+
} catch {
|
|
393
|
+
// ignore
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return dropped;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
// Main sync function
|
|
401
|
+
// ---------------------------------------------------------------------------
|
|
402
|
+
|
|
403
|
+
export interface SyncOpts {
|
|
404
|
+
tier: Tier;
|
|
405
|
+
/** Override for tests / CLI --dry-run. */
|
|
406
|
+
dryRun?: boolean;
|
|
407
|
+
/** Per-request page size (server caps at 500). */
|
|
408
|
+
limit?: number;
|
|
409
|
+
/** Override cwd (default process.cwd()). */
|
|
410
|
+
cwd?: string;
|
|
411
|
+
/** Override $HOME (default homedir()). */
|
|
412
|
+
home?: string;
|
|
413
|
+
/** Override fetch (tests inject a mock). */
|
|
414
|
+
fetchFn?: FetchFn;
|
|
415
|
+
/** Override API base URL. */
|
|
416
|
+
apiUrl?: string;
|
|
417
|
+
/** Explicit genome id (skips marker-file discovery). */
|
|
418
|
+
genomeId?: string;
|
|
419
|
+
/** Explicit pro-token (skips file read). */
|
|
420
|
+
proToken?: string;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export async function syncCloudDeltas(opts: SyncOpts): Promise<SyncResult> {
|
|
424
|
+
// ---- Tier gate — FIRST line of real work. ----
|
|
425
|
+
if (opts.tier === "free") {
|
|
426
|
+
return { skipped: true, reason: "free-tier" };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Kill switch — same flag the rest of the cloud pipeline honors.
|
|
430
|
+
if (process.env["ASHLR_CLOUD_GENOME_DISABLE"] === "1") {
|
|
431
|
+
return { skipped: true, reason: "kill-switch" };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
435
|
+
const home = opts.home ?? homedir();
|
|
436
|
+
const doFetch: FetchFn = opts.fetchFn ?? fetch;
|
|
437
|
+
const apiUrl = opts.apiUrl ?? API_URL_DEFAULT;
|
|
438
|
+
const limit = Math.max(1, Math.min(500, opts.limit ?? DEFAULT_LIMIT));
|
|
439
|
+
|
|
440
|
+
// No genome → nothing to sync. This is the steady state for users who
|
|
441
|
+
// haven't initialised a local genome yet, so it must be a quiet no-op.
|
|
442
|
+
if (!existsSync(genomeRoot(cwd))) {
|
|
443
|
+
return { skipped: true, reason: "no-local-genome" };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const proToken = opts.proToken ?? readProToken(home);
|
|
447
|
+
if (!proToken) return { skipped: true, reason: "no-pro-token" };
|
|
448
|
+
|
|
449
|
+
const genomeId = opts.genomeId ?? findGenomeIdForCwd(home, cwd);
|
|
450
|
+
if (!genomeId) return { skipped: true, reason: "no-cloud-genome" };
|
|
451
|
+
|
|
452
|
+
const cursorFile = readCursor(cwd);
|
|
453
|
+
const cursorBefore =
|
|
454
|
+
cursorFile && cursorFile.genomeId === genomeId ? cursorFile.cursor : 0;
|
|
455
|
+
|
|
456
|
+
// ---- Network ----
|
|
457
|
+
let res: Response;
|
|
458
|
+
try {
|
|
459
|
+
res = await doFetch(
|
|
460
|
+
`${apiUrl}/genome/cloud-deltas?genome_id=${encodeURIComponent(genomeId)}&since_cursor=${cursorBefore}&limit=${limit}`,
|
|
461
|
+
{
|
|
462
|
+
headers: {
|
|
463
|
+
Authorization: `Bearer ${proToken}`,
|
|
464
|
+
Accept: "application/json",
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
);
|
|
468
|
+
} catch {
|
|
469
|
+
// Network failure: leave the cursor untouched. Next session retries.
|
|
470
|
+
return { skipped: true, reason: "network-error", cursorBefore };
|
|
471
|
+
}
|
|
472
|
+
if (!res.ok) {
|
|
473
|
+
return {
|
|
474
|
+
skipped: true,
|
|
475
|
+
reason: `http-${res.status}`,
|
|
476
|
+
cursorBefore,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
let body: CloudDeltasResponse;
|
|
481
|
+
try {
|
|
482
|
+
body = (await res.json()) as CloudDeltasResponse;
|
|
483
|
+
} catch {
|
|
484
|
+
return { skipped: true, reason: "bad-json", cursorBefore };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (!Array.isArray(body.deltas) || body.deltas.length === 0) {
|
|
488
|
+
// Empty page — still advance cursor if server bumped nextCursor.
|
|
489
|
+
const nextCursor = Number.isFinite(body.nextCursor) ? body.nextCursor : cursorBefore;
|
|
490
|
+
if (!opts.dryRun && nextCursor > cursorBefore) {
|
|
491
|
+
writeCursor(cwd, {
|
|
492
|
+
genomeId,
|
|
493
|
+
cursor: nextCursor,
|
|
494
|
+
lastSyncedAt: new Date().toISOString(),
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
return { written: 0, cursorBefore, cursorAfter: nextCursor, pruned: 0 };
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ---- Apply ----
|
|
501
|
+
let written = 0;
|
|
502
|
+
if (!opts.dryRun) {
|
|
503
|
+
for (const delta of body.deltas) {
|
|
504
|
+
try {
|
|
505
|
+
if (applyDelta(cwd, delta)) written++;
|
|
506
|
+
} catch {
|
|
507
|
+
// Individual section write failure shouldn't tank the batch.
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
} else {
|
|
511
|
+
written = body.deltas.length; // dry-run reports "would-write" count
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Cursor advance — use the server's nextCursor, falling back to the last
|
|
515
|
+
// applied delta's cursor.
|
|
516
|
+
const lastCursor = body.deltas[body.deltas.length - 1]!.cursor;
|
|
517
|
+
const cursorAfter = Number.isFinite(body.nextCursor)
|
|
518
|
+
? Math.max(body.nextCursor, lastCursor)
|
|
519
|
+
: lastCursor;
|
|
520
|
+
|
|
521
|
+
let pruned = 0;
|
|
522
|
+
if (!opts.dryRun) {
|
|
523
|
+
writeCursor(cwd, {
|
|
524
|
+
genomeId,
|
|
525
|
+
cursor: cursorAfter,
|
|
526
|
+
lastSyncedAt: new Date().toISOString(),
|
|
527
|
+
});
|
|
528
|
+
pruned = pruneCloudSections(cwd);
|
|
529
|
+
// Q3: keep the per-subdir caps independent from the aggregate so PR +
|
|
530
|
+
// issue history remains deep enough to be useful for retrieval.
|
|
531
|
+
pruned += pruneSubdir(cwd, PRS_SUBDIR, PR_RETENTION_LIMIT);
|
|
532
|
+
pruned += pruneSubdir(cwd, ISSUES_SUBDIR, ISSUE_RETENTION_LIMIT);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return { written, cursorBefore, cursorAfter, pruned };
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ---------------------------------------------------------------------------
|
|
539
|
+
// CLI
|
|
540
|
+
// ---------------------------------------------------------------------------
|
|
541
|
+
|
|
542
|
+
function parseCliArgs(argv: string[]): { dryRun: boolean; limit?: number } {
|
|
543
|
+
let dryRun = false;
|
|
544
|
+
let limit: number | undefined;
|
|
545
|
+
for (const a of argv) {
|
|
546
|
+
if (a === "--dry-run") dryRun = true;
|
|
547
|
+
else if (a.startsWith("--limit=")) {
|
|
548
|
+
const n = Number.parseInt(a.slice("--limit=".length), 10);
|
|
549
|
+
if (Number.isFinite(n) && n > 0) limit = n;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return { dryRun, limit };
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async function detectTier(): Promise<Tier> {
|
|
556
|
+
// Mirror scripts/check-tier.ts but inline so this script has zero relative
|
|
557
|
+
// imports (it can run from anywhere and be vendored into the hook tree).
|
|
558
|
+
try {
|
|
559
|
+
const cachePath = join(homedir(), ".ashlr", "pro-token-cache.json");
|
|
560
|
+
if (!existsSync(cachePath)) return "free";
|
|
561
|
+
const c = JSON.parse(readFileSync(cachePath, "utf-8")) as {
|
|
562
|
+
valid?: boolean;
|
|
563
|
+
validatedAt?: string;
|
|
564
|
+
plan?: string;
|
|
565
|
+
};
|
|
566
|
+
if (c.valid !== true || !c.validatedAt) return "free";
|
|
567
|
+
const age = Date.now() - new Date(c.validatedAt).getTime();
|
|
568
|
+
if (age >= 7 * 24 * 60 * 60 * 1000) return "free";
|
|
569
|
+
if (c.plan === "team") return "team";
|
|
570
|
+
if (c.plan === "pro") return "pro";
|
|
571
|
+
return "free";
|
|
572
|
+
} catch {
|
|
573
|
+
return "free";
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (import.meta.main) {
|
|
578
|
+
const args = parseCliArgs(process.argv.slice(2));
|
|
579
|
+
const tier = await detectTier();
|
|
580
|
+
const result = await syncCloudDeltas({
|
|
581
|
+
tier,
|
|
582
|
+
dryRun: args.dryRun,
|
|
583
|
+
...(args.limit !== undefined ? { limit: args.limit } : {}),
|
|
584
|
+
});
|
|
585
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
586
|
+
process.exit(0);
|
|
587
|
+
}
|