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,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _genome-discoveries.ts — surface AI-synthesized discovery sections in
|
|
3
|
+
* ashlr__grep retrieval.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors `_genome-commits.ts` but for `kind: "discovery"` sections produced
|
|
6
|
+
* by `scripts/genome-synthesizer.ts`. Reuses the keyword scorer exported by
|
|
7
|
+
* the commit module so we have a single source of truth for tag/title/summary
|
|
8
|
+
* scoring across all v2 section kinds.
|
|
9
|
+
*
|
|
10
|
+
* Discoveries are ADDITIVE — they extend the corpus, never replace it. The
|
|
11
|
+
* grep handler formats them with a distinct `[discovery <id> - <date>]`
|
|
12
|
+
* prefix and a `## Discoveries` header so users can tell synthesis insights
|
|
13
|
+
* from static code and from raw commit history.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFile } from "fs/promises";
|
|
17
|
+
import { existsSync } from "fs";
|
|
18
|
+
import {
|
|
19
|
+
type DiscoverySection,
|
|
20
|
+
type SectionMetaV2,
|
|
21
|
+
discoverySectionAbsPath,
|
|
22
|
+
loadManifestV2,
|
|
23
|
+
} from "./_manifest-v2";
|
|
24
|
+
import { scoreSectionMeta, tokenize } from "./_genome-commits";
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Public API
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
export interface RetrievedDiscovery {
|
|
31
|
+
id: string;
|
|
32
|
+
shortId: string;
|
|
33
|
+
synthesizedAt: string;
|
|
34
|
+
summary: string;
|
|
35
|
+
evidence: DiscoverySection["evidence"];
|
|
36
|
+
sourceCommits: string[];
|
|
37
|
+
confidence: number;
|
|
38
|
+
score: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Retrieve up to `limit` discovery sections matching `query`.
|
|
43
|
+
*
|
|
44
|
+
* Returns an empty array when:
|
|
45
|
+
* - no genome exists,
|
|
46
|
+
* - the manifest has no discovery sections,
|
|
47
|
+
* - no discovery section's keywords overlap the query.
|
|
48
|
+
*
|
|
49
|
+
* Never throws — best-effort retrieval, callers should fall through to
|
|
50
|
+
* static + commit results when this returns [].
|
|
51
|
+
*/
|
|
52
|
+
export async function retrieveDiscoverySections(
|
|
53
|
+
cwd: string,
|
|
54
|
+
query: string,
|
|
55
|
+
limit = 3,
|
|
56
|
+
): Promise<RetrievedDiscovery[]> {
|
|
57
|
+
try {
|
|
58
|
+
const manifest = await loadManifestV2(cwd);
|
|
59
|
+
if (!manifest) return [];
|
|
60
|
+
|
|
61
|
+
const discoveryMetas = manifest.sections.filter((s) => s.kind === "discovery");
|
|
62
|
+
if (discoveryMetas.length === 0) return [];
|
|
63
|
+
|
|
64
|
+
const queryTerms = new Set(tokenize(query));
|
|
65
|
+
let scored: Array<{ meta: SectionMetaV2; score: number }>;
|
|
66
|
+
|
|
67
|
+
if (queryTerms.size === 0) {
|
|
68
|
+
// No meaningful query — surface the most recent discoveries.
|
|
69
|
+
scored = discoveryMetas.map((m) => ({ meta: m, score: 1 }));
|
|
70
|
+
} else {
|
|
71
|
+
scored = discoveryMetas
|
|
72
|
+
.map((m) => ({ meta: m, score: scoreSectionMeta(m, queryTerms) }))
|
|
73
|
+
.filter((s) => s.score > 0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (scored.length === 0) return [];
|
|
77
|
+
|
|
78
|
+
scored.sort((a, b) => {
|
|
79
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
80
|
+
const ad = a.meta.lastUpdatedAt ?? a.meta.updatedAt;
|
|
81
|
+
const bd = b.meta.lastUpdatedAt ?? b.meta.updatedAt;
|
|
82
|
+
return bd > ad ? 1 : bd < ad ? -1 : 0;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const out: RetrievedDiscovery[] = [];
|
|
86
|
+
for (const { meta, score } of scored.slice(0, limit)) {
|
|
87
|
+
const id = meta.path.replace(/^discoveries\//, "").replace(/\.json$/, "");
|
|
88
|
+
const abs = discoverySectionAbsPath(cwd, id);
|
|
89
|
+
if (!existsSync(abs)) continue;
|
|
90
|
+
let payload: DiscoverySection;
|
|
91
|
+
try {
|
|
92
|
+
payload = JSON.parse(await readFile(abs, "utf-8")) as DiscoverySection;
|
|
93
|
+
} catch {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
out.push({
|
|
97
|
+
id: payload.id,
|
|
98
|
+
shortId: payload.id.slice(0, 7),
|
|
99
|
+
synthesizedAt: payload.synthesizedAt,
|
|
100
|
+
summary: payload.summary,
|
|
101
|
+
evidence: payload.evidence,
|
|
102
|
+
sourceCommits: payload.sourceCommits,
|
|
103
|
+
confidence: payload.confidence,
|
|
104
|
+
score,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
} catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Format a list of retrieved discovery sections as a markdown block suitable
|
|
115
|
+
* for prepending to grep output. Each discovery is clearly labeled with
|
|
116
|
+
* `[discovery <id> - <date>]` so users see it's AI-synthesized, not static
|
|
117
|
+
* documentation or raw commit history.
|
|
118
|
+
*/
|
|
119
|
+
export function formatDiscoveriesForPrompt(discoveries: RetrievedDiscovery[]): string {
|
|
120
|
+
if (discoveries.length === 0) return "";
|
|
121
|
+
const parts = discoveries.map((d) => {
|
|
122
|
+
const dateShort = d.synthesizedAt ? d.synthesizedAt.slice(0, 10) : "unknown";
|
|
123
|
+
const evidence =
|
|
124
|
+
d.evidence.length === 0
|
|
125
|
+
? ""
|
|
126
|
+
: `\n_evidence: ${d.evidence
|
|
127
|
+
.slice(0, 6)
|
|
128
|
+
.map((e) => (e.lineRange ? `${e.path}:${e.lineRange[0]}-${e.lineRange[1]}` : e.path))
|
|
129
|
+
.join(", ")}${d.evidence.length > 6 ? ` (+${d.evidence.length - 6})` : ""}_\n`;
|
|
130
|
+
const commits =
|
|
131
|
+
d.sourceCommits.length === 0
|
|
132
|
+
? ""
|
|
133
|
+
: `_from commits: ${d.sourceCommits.map((s) => s.slice(0, 7)).join(", ")}_\n`;
|
|
134
|
+
const conf = `_confidence: ${d.confidence.toFixed(2)}_\n`;
|
|
135
|
+
return `### [discovery ${d.shortId} - ${dateShort}]
|
|
136
|
+
${d.summary}
|
|
137
|
+
${evidence}${commits}${conf}`;
|
|
138
|
+
});
|
|
139
|
+
return `## Discoveries\n\n${parts.join("\n\n---\n\n")}`;
|
|
140
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _genome-embed-populator.ts — Populate the embedding cache from genome sections.
|
|
3
|
+
*
|
|
4
|
+
* Walks the genome manifest on first grep per session and upserts one
|
|
5
|
+
* embedding per section into the shared ContextDb. The cache is then
|
|
6
|
+
* consulted on every `ashlr__grep` call (see grep-server.ts:236–293) to
|
|
7
|
+
* either prepend a high-similarity match or fall through to the genome
|
|
8
|
+
* retrieval / ripgrep path.
|
|
9
|
+
*
|
|
10
|
+
* Historical note: a prior version of this module shipped empty — the cache
|
|
11
|
+
* was a placebo with no rows. The populator now seeds it on every grep and
|
|
12
|
+
* grep-server consumes the rows; the marketing claim ("genome-aware
|
|
13
|
+
* retrieval saves 50–90% on relevant queries") is now load-bearing. Kill
|
|
14
|
+
* the entire pipeline with `ASHLR_GENOME_RETRIEVAL=off` to fall back to
|
|
15
|
+
* bare ripgrep for diagnostics.
|
|
16
|
+
*
|
|
17
|
+
* Cost model:
|
|
18
|
+
* - First-call-after-manifest-change: O(sections) reads + embeds. For a
|
|
19
|
+
* typical genome (~15 sections, each < 4KB) this is a few ms in BM25 mode.
|
|
20
|
+
* - Steady state: O(1) watermark check, no disk reads.
|
|
21
|
+
*
|
|
22
|
+
* Watermark:
|
|
23
|
+
* Per-project-hash manifest-mtime stored at ~/.ashlr/embed-watermark.json.
|
|
24
|
+
* Re-population is triggered ONLY when the manifest mtime advances.
|
|
25
|
+
* Section-level deltas re-embed any section whose section-text hash changed
|
|
26
|
+
* since the last run.
|
|
27
|
+
*
|
|
28
|
+
* Non-goals:
|
|
29
|
+
* - Cross-session invalidation via hashes of source files (handled by the
|
|
30
|
+
* post-tool-use-embedding hook + embed-file-worker for code files).
|
|
31
|
+
* - AST chunking of markdown genome sections — chunks are a code concern.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { existsSync, readFileSync, mkdirSync, renameSync, writeFileSync, statSync } from "fs";
|
|
35
|
+
import { createHash } from "crypto";
|
|
36
|
+
import { homedir } from "os";
|
|
37
|
+
import { dirname, join } from "path";
|
|
38
|
+
|
|
39
|
+
import { loadManifest, sectionPath } from "@ashlr/core-efficiency";
|
|
40
|
+
import type { ContextDb } from "./_embedding-cache";
|
|
41
|
+
import { embed, upsertCorpus } from "./_embedding-model";
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Watermark I/O
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
interface ProjectWatermark {
|
|
48
|
+
manifestMtime: number;
|
|
49
|
+
sections: Record<string, string>; // section path → content hash
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface WatermarkFile {
|
|
53
|
+
version: 1;
|
|
54
|
+
projects: Record<string, ProjectWatermark>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function watermarkPath(home?: string): string {
|
|
58
|
+
return join(home ?? homedir(), ".ashlr", "embed-watermark.json");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readWatermark(home?: string): WatermarkFile {
|
|
62
|
+
const path = watermarkPath(home);
|
|
63
|
+
if (!existsSync(path)) return { version: 1, projects: {} };
|
|
64
|
+
try {
|
|
65
|
+
const raw = readFileSync(path, "utf-8");
|
|
66
|
+
const parsed = JSON.parse(raw) as WatermarkFile;
|
|
67
|
+
if (parsed.version !== 1 || !parsed.projects) {
|
|
68
|
+
return { version: 1, projects: {} };
|
|
69
|
+
}
|
|
70
|
+
return parsed;
|
|
71
|
+
} catch {
|
|
72
|
+
return { version: 1, projects: {} };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function writeWatermark(wm: WatermarkFile, home?: string): void {
|
|
77
|
+
const path = watermarkPath(home);
|
|
78
|
+
const dir = dirname(path);
|
|
79
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
80
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
81
|
+
writeFileSync(tmp, JSON.stringify(wm), "utf-8");
|
|
82
|
+
renameSync(tmp, path);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function manifestMtime(genomeRoot: string): number {
|
|
86
|
+
try {
|
|
87
|
+
return statSync(join(genomeRoot, ".ashlrcode", "genome", "manifest.json")).mtimeMs;
|
|
88
|
+
} catch {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function sha256(s: string): string {
|
|
94
|
+
return createHash("sha256").update(s).digest("hex").slice(0, 16);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// In-process guard: avoid concurrent populate runs for the same project.
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
const inflight = new Map<string, Promise<PopulateStats>>();
|
|
102
|
+
|
|
103
|
+
export interface PopulateStats {
|
|
104
|
+
/** Sections embedded (inserted or refreshed). */
|
|
105
|
+
embedded: number;
|
|
106
|
+
/** Sections that were up-to-date and skipped. */
|
|
107
|
+
skipped: number;
|
|
108
|
+
/** True when the manifest mtime was unchanged — the entire run is a no-op. */
|
|
109
|
+
unchanged: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface PopulateOptions {
|
|
113
|
+
/** ContextDb instance (injected so tests can use a fresh tmp home). */
|
|
114
|
+
ctxDb: ContextDb;
|
|
115
|
+
/** Override `~` so tests never touch the real watermark file. */
|
|
116
|
+
home?: string;
|
|
117
|
+
/** Already-computed stable 8-char project hash. */
|
|
118
|
+
projectHash: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Populate the embedding cache with every section in the genome manifest.
|
|
123
|
+
* Cheap + idempotent: the watermark ensures a no-op when nothing changed.
|
|
124
|
+
*/
|
|
125
|
+
export async function populateGenomeEmbeddings(
|
|
126
|
+
genomeRoot: string,
|
|
127
|
+
opts: PopulateOptions,
|
|
128
|
+
): Promise<PopulateStats> {
|
|
129
|
+
const key = `${opts.projectHash}\x00${genomeRoot}`;
|
|
130
|
+
const running = inflight.get(key);
|
|
131
|
+
if (running) return running;
|
|
132
|
+
|
|
133
|
+
const run = populateInner(genomeRoot, opts).finally(() => {
|
|
134
|
+
if (inflight.get(key)) inflight.delete(key);
|
|
135
|
+
});
|
|
136
|
+
inflight.set(key, run);
|
|
137
|
+
return run;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function populateInner(
|
|
141
|
+
genomeRoot: string,
|
|
142
|
+
{ ctxDb, home, projectHash }: PopulateOptions,
|
|
143
|
+
): Promise<PopulateStats> {
|
|
144
|
+
const mtime = manifestMtime(genomeRoot);
|
|
145
|
+
if (mtime === 0) return { embedded: 0, skipped: 0, unchanged: true };
|
|
146
|
+
|
|
147
|
+
const wm = readWatermark(home);
|
|
148
|
+
const prev: ProjectWatermark = wm.projects[projectHash] ?? {
|
|
149
|
+
manifestMtime: 0,
|
|
150
|
+
sections: {},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
if (prev.manifestMtime === mtime) {
|
|
154
|
+
return { embedded: 0, skipped: 0, unchanged: true };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const manifest = await loadManifest(genomeRoot);
|
|
158
|
+
if (!manifest || manifest.sections.length === 0) {
|
|
159
|
+
// Manifest is unreadable or empty — advance the watermark so we don't
|
|
160
|
+
// re-read every call, but don't record a bogus section map.
|
|
161
|
+
wm.projects[projectHash] = { manifestMtime: mtime, sections: {} };
|
|
162
|
+
try { writeWatermark(wm, home); } catch { /* best-effort */ }
|
|
163
|
+
return { embedded: 0, skipped: 0, unchanged: false };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let embedded = 0;
|
|
167
|
+
let skipped = 0;
|
|
168
|
+
const nextSections: Record<string, string> = {};
|
|
169
|
+
|
|
170
|
+
for (const section of manifest.sections) {
|
|
171
|
+
let content: string;
|
|
172
|
+
try {
|
|
173
|
+
const full = sectionPath(genomeRoot, section.path);
|
|
174
|
+
content = readFileSync(full, "utf-8");
|
|
175
|
+
} catch {
|
|
176
|
+
continue; // missing or invalid path — skip this section
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const hash = sha256(content);
|
|
180
|
+
nextSections[section.path] = hash;
|
|
181
|
+
|
|
182
|
+
if (prev.sections[section.path] === hash) {
|
|
183
|
+
skipped++;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Sections are typically < 4KB of markdown; embed the title + summary + body
|
|
188
|
+
// so terms like the filename and tags also contribute to retrieval.
|
|
189
|
+
const text = `${section.title}\n${section.summary}\n${content}`;
|
|
190
|
+
try {
|
|
191
|
+
upsertCorpus(text);
|
|
192
|
+
const vec = await embed(text);
|
|
193
|
+
ctxDb.upsertEmbedding({
|
|
194
|
+
projectHash,
|
|
195
|
+
sectionPath: `genome:${section.path}`,
|
|
196
|
+
sectionText: content.slice(0, 2000),
|
|
197
|
+
embedding: vec,
|
|
198
|
+
embeddingDim: vec.length,
|
|
199
|
+
source: "genome",
|
|
200
|
+
});
|
|
201
|
+
embedded++;
|
|
202
|
+
} catch {
|
|
203
|
+
// Never let one bad section abort the whole populate pass.
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
wm.projects[projectHash] = { manifestMtime: mtime, sections: nextSections };
|
|
208
|
+
try { writeWatermark(wm, home); } catch { /* best-effort */ }
|
|
209
|
+
|
|
210
|
+
return { embedded, skipped, unchanged: false };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Test helper — drop the in-process in-flight map. */
|
|
214
|
+
export function _clearInflightForTests(): void {
|
|
215
|
+
inflight.clear();
|
|
216
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _genome-freshness.ts — render compact freshness badges for retrieved
|
|
3
|
+
* genome sections (Q2 prep, foundational work).
|
|
4
|
+
*
|
|
5
|
+
* The v2 manifest (`_manifest-v2.ts`) carries `lastUpdatedAt` on every
|
|
6
|
+
* section. Surfacing that age in grep output lets the human + the model
|
|
7
|
+
* see at a glance whether the retrieved context is current.
|
|
8
|
+
*
|
|
9
|
+
* Badge format (max ~15 chars):
|
|
10
|
+
* <1h → `[fresh]`
|
|
11
|
+
* <24h → `[fresh: Xh]`
|
|
12
|
+
* <7d → `[fresh: Xd]`
|
|
13
|
+
* <30d → `[stale: Xd]`
|
|
14
|
+
* ≥30d → `[stale: ≥30d]`
|
|
15
|
+
* undef → `""` (legacy v1 sections; render nothing — caller stays safe)
|
|
16
|
+
*
|
|
17
|
+
* Decoration is purely additive: legacy v1 sections still render unchanged
|
|
18
|
+
* because their entry in the freshness map is absent.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { loadManifestV2 } from "./_manifest-v2";
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Pure helper — no I/O, fully tested.
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Format a freshness badge from an ISO timestamp.
|
|
29
|
+
*
|
|
30
|
+
* Returns "" when `lastUpdatedAt` is undefined OR fails to parse — callers
|
|
31
|
+
* can safely splice the result into any string template without guarding.
|
|
32
|
+
*
|
|
33
|
+
* Bucket boundaries are inclusive on the LOWER bound and exclusive on the
|
|
34
|
+
* UPPER bound. e.g. exactly 1h old → `[fresh: 1h]` (>= 1h, < 24h).
|
|
35
|
+
*/
|
|
36
|
+
export function formatFreshness(
|
|
37
|
+
lastUpdatedAt: string | undefined,
|
|
38
|
+
now: Date = new Date(),
|
|
39
|
+
): string {
|
|
40
|
+
if (!lastUpdatedAt) return "";
|
|
41
|
+
const t = Date.parse(lastUpdatedAt);
|
|
42
|
+
if (!Number.isFinite(t)) return "";
|
|
43
|
+
|
|
44
|
+
const ageMs = now.getTime() - t;
|
|
45
|
+
if (ageMs < 0) {
|
|
46
|
+
// Future timestamp — treat as fresh-just-now rather than emitting
|
|
47
|
+
// misleading "stale" text or empty string.
|
|
48
|
+
return "[fresh]";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const HOUR = 60 * 60 * 1000;
|
|
52
|
+
const DAY = 24 * HOUR;
|
|
53
|
+
|
|
54
|
+
if (ageMs < HOUR) return "[fresh]";
|
|
55
|
+
if (ageMs < DAY) {
|
|
56
|
+
const hours = Math.floor(ageMs / HOUR);
|
|
57
|
+
return `[fresh: ${hours}h]`;
|
|
58
|
+
}
|
|
59
|
+
if (ageMs < 7 * DAY) {
|
|
60
|
+
const days = Math.floor(ageMs / DAY);
|
|
61
|
+
return `[fresh: ${days}d]`;
|
|
62
|
+
}
|
|
63
|
+
if (ageMs < 30 * DAY) {
|
|
64
|
+
const days = Math.floor(ageMs / DAY);
|
|
65
|
+
return `[stale: ${days}d]`;
|
|
66
|
+
}
|
|
67
|
+
return "[stale: >=30d]";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Manifest lookup
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Build a (sectionPath → lastUpdatedAt) lookup from the v2 manifest at `cwd`.
|
|
76
|
+
*
|
|
77
|
+
* Returns an empty Map when:
|
|
78
|
+
* - no genome exists,
|
|
79
|
+
* - the manifest is unreadable,
|
|
80
|
+
* - sections have no lastUpdatedAt (legacy v1 not yet re-saved).
|
|
81
|
+
*
|
|
82
|
+
* Never throws. Safe to call on every grep retrieval.
|
|
83
|
+
*/
|
|
84
|
+
export async function loadSectionFreshnessMap(
|
|
85
|
+
cwd: string,
|
|
86
|
+
): Promise<Map<string, string>> {
|
|
87
|
+
const out = new Map<string, string>();
|
|
88
|
+
try {
|
|
89
|
+
const manifest = await loadManifestV2(cwd);
|
|
90
|
+
if (!manifest) return out;
|
|
91
|
+
for (const s of manifest.sections) {
|
|
92
|
+
if (s.lastUpdatedAt) out.set(s.path, s.lastUpdatedAt);
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
// Best-effort — never break retrieval.
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Output decoration
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Inject freshness badges into core-efficiency's formatGenomeForPrompt
|
|
106
|
+
* output. Targets each `### <title> (<path>)` header — if the path is in
|
|
107
|
+
* `freshnessMap` we append ` <badge>`. Headers with no metadata are left
|
|
108
|
+
* untouched (legacy v1 safety net).
|
|
109
|
+
*
|
|
110
|
+
* Pure / synchronous. Idempotent — re-running on already-decorated output
|
|
111
|
+
* is a no-op because the regex matches only un-decorated headers.
|
|
112
|
+
*/
|
|
113
|
+
export function decorateGenomeOutputWithFreshness(
|
|
114
|
+
formatted: string,
|
|
115
|
+
freshnessMap: Map<string, string>,
|
|
116
|
+
now: Date = new Date(),
|
|
117
|
+
): string {
|
|
118
|
+
if (formatted.length === 0 || freshnessMap.size === 0) return formatted;
|
|
119
|
+
|
|
120
|
+
// Match: `### <title> (<path>)` at start of a line. Path is greedy-by-
|
|
121
|
+
// necessity (titles can contain parens), so we anchor to the LAST `(...)`
|
|
122
|
+
// on the line by requiring the trailing `)` to be followed by end-of-line
|
|
123
|
+
// or whitespace — works for the core-efficiency formatter's exact output.
|
|
124
|
+
return formatted.replace(
|
|
125
|
+
/^### (.+?) \(([^()\n]+)\)(?= *$)/gm,
|
|
126
|
+
(whole, title: string, path: string) => {
|
|
127
|
+
const ts = freshnessMap.get(path);
|
|
128
|
+
if (!ts) return whole;
|
|
129
|
+
const badge = formatFreshness(ts, now);
|
|
130
|
+
if (!badge) return whole;
|
|
131
|
+
return `### ${title} (${path}) ${badge}`;
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _genome-issues.ts — surface closed-issue sections in ashlr__grep retrieval.
|
|
3
|
+
*
|
|
4
|
+
* Q3 sibling of _genome-prs.ts. Issue sections are written by
|
|
5
|
+
* scripts/genome-cloud-sync.ts when an `issue_closed` cloud delta arrives.
|
|
6
|
+
* The grep handler calls retrieveIssueSections() so `ashlr__grep "auth bug"`
|
|
7
|
+
* returns the recently-closed issues alongside code + PRs.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors the PR retriever exactly — scans sections/issues/ directly rather
|
|
10
|
+
* than going through the v2 manifest (cloud-sync writes files out-of-band
|
|
11
|
+
* from manifest updates). Best-effort, fails-silent.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFile, readdir, stat } from "fs/promises";
|
|
15
|
+
import { existsSync } from "fs";
|
|
16
|
+
import { join } from "path";
|
|
17
|
+
import { type IssueSection, issuesDir } from "./_manifest-v2";
|
|
18
|
+
import { scoreSectionMeta, tokenize } from "./_genome-commits";
|
|
19
|
+
import { formatFreshness } from "./_genome-freshness";
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Public API
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
export interface RetrievedIssue {
|
|
26
|
+
id: string;
|
|
27
|
+
title: string;
|
|
28
|
+
closedAt: string;
|
|
29
|
+
author: string;
|
|
30
|
+
labels: string[];
|
|
31
|
+
summary: string;
|
|
32
|
+
url: string;
|
|
33
|
+
score: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Retrieve up to `limit` issue sections from `sections/issues/` matching `query`.
|
|
38
|
+
*
|
|
39
|
+
* @param cwd genome root (the directory containing `.ashlrcode/`)
|
|
40
|
+
* @param query keyword query — same scorer commit/discovery/PR use
|
|
41
|
+
* @param limit max results (default 3)
|
|
42
|
+
* @param sinceDays optional freshness filter — drop issues closed longer
|
|
43
|
+
* than N days ago. `undefined` disables the filter.
|
|
44
|
+
*/
|
|
45
|
+
export async function retrieveIssueSections(
|
|
46
|
+
cwd: string,
|
|
47
|
+
query: string,
|
|
48
|
+
limit = 3,
|
|
49
|
+
sinceDays?: number,
|
|
50
|
+
): Promise<RetrievedIssue[]> {
|
|
51
|
+
try {
|
|
52
|
+
const dir = issuesDir(cwd);
|
|
53
|
+
if (!existsSync(dir)) return [];
|
|
54
|
+
|
|
55
|
+
let entries: string[];
|
|
56
|
+
try {
|
|
57
|
+
entries = await readdir(dir);
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
const files = entries.filter((n) => n.endsWith(".json"));
|
|
62
|
+
if (files.length === 0) return [];
|
|
63
|
+
|
|
64
|
+
const cutoff =
|
|
65
|
+
typeof sinceDays === "number" && Number.isFinite(sinceDays) && sinceDays > 0
|
|
66
|
+
? Date.now() - sinceDays * 24 * 60 * 60 * 1000
|
|
67
|
+
: null;
|
|
68
|
+
|
|
69
|
+
const loaded: Array<{ section: IssueSection; mtimeMs: number }> = [];
|
|
70
|
+
for (const file of files) {
|
|
71
|
+
const abs = join(dir, file);
|
|
72
|
+
try {
|
|
73
|
+
const [raw, st] = await Promise.all([readFile(abs, "utf-8"), stat(abs)]);
|
|
74
|
+
const section = JSON.parse(raw) as IssueSection;
|
|
75
|
+
if (!section || typeof section !== "object") continue;
|
|
76
|
+
loaded.push({ section, mtimeMs: st.mtimeMs });
|
|
77
|
+
} catch {
|
|
78
|
+
// Skip corrupt file.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const queryTerms = new Set(tokenize(query));
|
|
83
|
+
|
|
84
|
+
const scored = loaded
|
|
85
|
+
.map(({ section, mtimeMs }) => {
|
|
86
|
+
// Freshness filter — drop issues older than the cutoff.
|
|
87
|
+
if (cutoff !== null) {
|
|
88
|
+
const t = section.closedAt ? Date.parse(section.closedAt) : NaN;
|
|
89
|
+
if (Number.isFinite(t) && t < cutoff) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const tags = [
|
|
94
|
+
"issue",
|
|
95
|
+
"bug",
|
|
96
|
+
section.id,
|
|
97
|
+
...(section.author ? [section.author.toLowerCase()] : []),
|
|
98
|
+
...section.labels.map((l) => l.toLowerCase()),
|
|
99
|
+
];
|
|
100
|
+
const meta = {
|
|
101
|
+
path: `issues/${section.id}.json`,
|
|
102
|
+
title: section.title,
|
|
103
|
+
summary: section.summary,
|
|
104
|
+
tags,
|
|
105
|
+
tokens: Math.ceil((section.title.length + section.summary.length) / 4),
|
|
106
|
+
updatedAt: section.closedAt || new Date(mtimeMs).toISOString(),
|
|
107
|
+
};
|
|
108
|
+
let score: number;
|
|
109
|
+
if (queryTerms.size === 0) {
|
|
110
|
+
score = 1;
|
|
111
|
+
} else {
|
|
112
|
+
score = scoreSectionMeta(meta, queryTerms);
|
|
113
|
+
}
|
|
114
|
+
return { section, score, mtimeMs };
|
|
115
|
+
})
|
|
116
|
+
.filter(
|
|
117
|
+
(x): x is { section: IssueSection; score: number; mtimeMs: number } =>
|
|
118
|
+
x !== null && x.score > 0,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (scored.length === 0) return [];
|
|
122
|
+
|
|
123
|
+
scored.sort((a, b) => {
|
|
124
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
125
|
+
const ad = a.section.closedAt || new Date(a.mtimeMs).toISOString();
|
|
126
|
+
const bd = b.section.closedAt || new Date(b.mtimeMs).toISOString();
|
|
127
|
+
return bd > ad ? 1 : bd < ad ? -1 : 0;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return scored.slice(0, limit).map(({ section, score }) => ({
|
|
131
|
+
id: section.id,
|
|
132
|
+
title: section.title,
|
|
133
|
+
closedAt: section.closedAt,
|
|
134
|
+
author: section.author,
|
|
135
|
+
labels: section.labels,
|
|
136
|
+
summary: section.summary,
|
|
137
|
+
url: section.url,
|
|
138
|
+
score,
|
|
139
|
+
}));
|
|
140
|
+
} catch {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Format a list of retrieved issue sections as a markdown block. Each
|
|
147
|
+
* issue gets `[Issue #<n> - <date>]` so it reads distinctly from PRs +
|
|
148
|
+
* commits + code in the same response.
|
|
149
|
+
*/
|
|
150
|
+
export function formatIssuesForPrompt(
|
|
151
|
+
issues: RetrievedIssue[],
|
|
152
|
+
now: Date = new Date(),
|
|
153
|
+
): string {
|
|
154
|
+
if (issues.length === 0) return "";
|
|
155
|
+
const parts = issues.map((i) => {
|
|
156
|
+
const dateShort = i.closedAt ? i.closedAt.slice(0, 10) : "unknown";
|
|
157
|
+
const badge = formatFreshness(i.closedAt || undefined, now);
|
|
158
|
+
const badgeFragment = badge ? ` · ${badge.slice(1, -1)}` : "";
|
|
159
|
+
const labels =
|
|
160
|
+
i.labels.length === 0
|
|
161
|
+
? ""
|
|
162
|
+
: `\n_labels: ${i.labels.slice(0, 6).join(", ")}${
|
|
163
|
+
i.labels.length > 6 ? ` (+${i.labels.length - 6})` : ""
|
|
164
|
+
}_\n`;
|
|
165
|
+
const author = i.author ? `_author: ${i.author}_\n` : "";
|
|
166
|
+
const link = i.url ? `_${i.url}_\n` : "";
|
|
167
|
+
return `### [Issue #${i.id} - ${dateShort}${badgeFragment}] ${i.title}${labels}${author}${link}
|
|
168
|
+
${i.summary}`;
|
|
169
|
+
});
|
|
170
|
+
return `## Issues\n\n${parts.join("\n\n---\n\n")}`;
|
|
171
|
+
}
|