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,679 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* genome-synthesizer — Q2 AI-Native Synthesis MVP entry point.
|
|
4
|
+
*
|
|
5
|
+
* Background LLM pass that converts recent commit sections + relevant static
|
|
6
|
+
* sections into *discovery sections* — short, structured insights:
|
|
7
|
+
*
|
|
8
|
+
* "3 files use this util wrong"
|
|
9
|
+
* "auth refactor introduced a flaky test pattern"
|
|
10
|
+
* "config X is duplicated across 4 modules"
|
|
11
|
+
*
|
|
12
|
+
* Tiered cadence (per WAD-D Q2 instrumentation plan):
|
|
13
|
+
* - free → synthesizer is gated off entirely (no LLM cost — hardcoded gate)
|
|
14
|
+
* - pro → once per 7 days per repo (best-effort throttle)
|
|
15
|
+
* - team → once per 24 hours per repo (best-effort throttle)
|
|
16
|
+
*
|
|
17
|
+
* Output: 0-3 discovery sections per run, stored at
|
|
18
|
+
* `.ashlrcode/genome/discoveries/<id>.json`
|
|
19
|
+
* plus a manifest entry with `kind: "discovery"` and `sourceTrust: "synthesis"`.
|
|
20
|
+
*
|
|
21
|
+
* State file: `.ashlrcode/genome/_synthesis-state.json` tracks the last run
|
|
22
|
+
* timestamp so we don't double-run within the throttle window.
|
|
23
|
+
*
|
|
24
|
+
* Privacy:
|
|
25
|
+
* - We send commit-section summaries (which the user already authored as
|
|
26
|
+
* commit messages) — NEVER raw diff content.
|
|
27
|
+
* - Paths under `secrets/`, `.env`, or matching common secret patterns are
|
|
28
|
+
* redacted before going to the LLM.
|
|
29
|
+
*
|
|
30
|
+
* Usage:
|
|
31
|
+
* bun run scripts/genome-synthesizer.ts [--dry-run] [--max-commits=10] [--force] [--cwd <dir>]
|
|
32
|
+
*
|
|
33
|
+
* Flags:
|
|
34
|
+
* --dry-run Parse + score + prompt, but don't write to disk.
|
|
35
|
+
* Returns the would-be discovery list.
|
|
36
|
+
* --max-commits How many recent commit sections to feed in (default 10).
|
|
37
|
+
* --force Bypass the throttle window. Manual-run escape hatch.
|
|
38
|
+
* --cwd <dir> Target a project other than process.cwd().
|
|
39
|
+
*
|
|
40
|
+
* Design constraints:
|
|
41
|
+
* - NEVER runs in a hook hot path. CLI-only.
|
|
42
|
+
* - Free tier MUST NEVER make an LLM call — gated at the very top.
|
|
43
|
+
* - No new external dependencies. Reuses `servers/_llm-providers/` only.
|
|
44
|
+
* - Best-effort throttle — if the state file is corrupt we re-synthesize.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import { createHash } from "crypto";
|
|
48
|
+
import { existsSync } from "fs";
|
|
49
|
+
import { mkdir, readFile, readdir, rename, unlink, writeFile } from "fs/promises";
|
|
50
|
+
import { dirname, join, resolve } from "path";
|
|
51
|
+
|
|
52
|
+
import {
|
|
53
|
+
DISCOVERIES_SUBDIR,
|
|
54
|
+
DISCOVERY_RETENTION_LIMIT,
|
|
55
|
+
commitSectionAbsPath,
|
|
56
|
+
discoverySectionAbsPath,
|
|
57
|
+
loadManifestV2,
|
|
58
|
+
saveManifestV2,
|
|
59
|
+
writeDiscoverySectionFile,
|
|
60
|
+
type CommitSection,
|
|
61
|
+
type DiscoveryEvidence,
|
|
62
|
+
type DiscoverySection,
|
|
63
|
+
type GenomeManifestV2,
|
|
64
|
+
type SectionMetaV2,
|
|
65
|
+
} from "../servers/_manifest-v2";
|
|
66
|
+
import { isProSync } from "../servers/_pro";
|
|
67
|
+
import { selectProvider, type LlmProvider } from "../servers/_llm-providers";
|
|
68
|
+
import { scoreSectionMeta, tokenize } from "../servers/_genome-commits";
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Types
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
export type Tier = "free" | "pro" | "team";
|
|
75
|
+
|
|
76
|
+
export interface SynthesizeOpts {
|
|
77
|
+
/** Repo to synthesize. Defaults to process.cwd(). */
|
|
78
|
+
cwd?: string;
|
|
79
|
+
/** Tier — controls gating + throttle window. Defaults via isProSync(). */
|
|
80
|
+
tier?: Tier;
|
|
81
|
+
/** How many recent commit sections to feed in. Default 10. */
|
|
82
|
+
maxCommits?: number;
|
|
83
|
+
/** If true, never writes — returns what would be written. Default false. */
|
|
84
|
+
dryRun?: boolean;
|
|
85
|
+
/** If true, bypass the throttle window. Default false. */
|
|
86
|
+
force?: boolean;
|
|
87
|
+
/** Optional provider override (for tests). */
|
|
88
|
+
provider?: LlmProvider;
|
|
89
|
+
/** Optional clock override (for tests). */
|
|
90
|
+
nowMs?: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface SynthesizeResult {
|
|
94
|
+
skipped: boolean;
|
|
95
|
+
reason?:
|
|
96
|
+
| "free-tier"
|
|
97
|
+
| "throttled"
|
|
98
|
+
| "no-genome"
|
|
99
|
+
| "no-commits"
|
|
100
|
+
| "no-provider"
|
|
101
|
+
| "llm-failed"
|
|
102
|
+
| "no-discoveries-produced";
|
|
103
|
+
writtenIds?: string[];
|
|
104
|
+
wouldWriteIds?: string[];
|
|
105
|
+
discoveries?: DiscoverySection[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface SynthesisState {
|
|
109
|
+
lastRunAt: string; // ISO of most recent non-dryrun synthesize()
|
|
110
|
+
lastTier: Tier; // tier at last run
|
|
111
|
+
lastDiscoveryCount: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Constants
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
const STATE_FILE = "_synthesis-state.json";
|
|
119
|
+
const THROTTLE_PRO_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
120
|
+
const THROTTLE_TEAM_MS = 1 * 24 * 60 * 60 * 1000; // 24 hours
|
|
121
|
+
|
|
122
|
+
/** Hard cap on the number of discoveries written per run. */
|
|
123
|
+
const MAX_DISCOVERIES_PER_RUN = 3;
|
|
124
|
+
|
|
125
|
+
/** Minimum discoveries per run before we treat the run as successful. */
|
|
126
|
+
const MIN_DISCOVERIES_PER_RUN = 1;
|
|
127
|
+
|
|
128
|
+
const SECRET_PATH_PATTERNS: RegExp[] = [
|
|
129
|
+
/(^|\/)\.env(\.|$)/i,
|
|
130
|
+
/(^|\/)secrets?\//i,
|
|
131
|
+
/credentials?\.json$/i,
|
|
132
|
+
/\.pem$/i,
|
|
133
|
+
/id_rsa(\.|$)/i,
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// State I/O
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
function stateFilePath(cwd: string): string {
|
|
141
|
+
return join(cwd, ".ashlrcode", "genome", STATE_FILE);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function readSynthesisState(cwd: string): Promise<SynthesisState | null> {
|
|
145
|
+
const p = stateFilePath(cwd);
|
|
146
|
+
if (!existsSync(p)) return null;
|
|
147
|
+
try {
|
|
148
|
+
const raw = await readFile(p, "utf-8");
|
|
149
|
+
const parsed = JSON.parse(raw) as SynthesisState;
|
|
150
|
+
if (typeof parsed.lastRunAt !== "string") return null;
|
|
151
|
+
return parsed;
|
|
152
|
+
} catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function writeSynthesisState(cwd: string, state: SynthesisState): Promise<void> {
|
|
158
|
+
const p = stateFilePath(cwd);
|
|
159
|
+
if (!existsSync(dirname(p))) {
|
|
160
|
+
await mkdir(dirname(p), { recursive: true });
|
|
161
|
+
}
|
|
162
|
+
const tmp = p + ".tmp";
|
|
163
|
+
try {
|
|
164
|
+
await writeFile(tmp, JSON.stringify(state, null, 2), "utf-8");
|
|
165
|
+
await rename(tmp, p);
|
|
166
|
+
} catch (e) {
|
|
167
|
+
await unlink(tmp).catch(() => {});
|
|
168
|
+
throw e;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// Tier resolution
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
export function resolveTier(opts: SynthesizeOpts): Tier {
|
|
177
|
+
if (opts.tier) return opts.tier;
|
|
178
|
+
// Default: read the cached pro-token state. We don't await network here —
|
|
179
|
+
// the synthesizer runs offline. ASHLR_PRO_ASSUME (handled by isProSync)
|
|
180
|
+
// still works as an escape hatch.
|
|
181
|
+
if (isProSync()) {
|
|
182
|
+
// We don't distinguish pro vs team here without an explicit signal.
|
|
183
|
+
// Conservative default: treat cached-pro as "pro" tier so throttle is 7d.
|
|
184
|
+
// Callers (or env ASHLR_TIER) can override to "team" for 24h.
|
|
185
|
+
const envTier = (process.env.ASHLR_TIER ?? "").toLowerCase().trim();
|
|
186
|
+
if (envTier === "team") return "team";
|
|
187
|
+
return "pro";
|
|
188
|
+
}
|
|
189
|
+
return "free";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Commit + static section loading
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
async function loadRecentCommitSections(
|
|
197
|
+
cwd: string,
|
|
198
|
+
manifest: GenomeManifestV2,
|
|
199
|
+
maxCommits: number,
|
|
200
|
+
): Promise<CommitSection[]> {
|
|
201
|
+
const commitMetas = manifest.sections
|
|
202
|
+
.filter((s) => s.kind === "commit")
|
|
203
|
+
.slice() // copy before mutating
|
|
204
|
+
.sort((a, b) => {
|
|
205
|
+
const ad = a.lastUpdatedAt ?? a.updatedAt;
|
|
206
|
+
const bd = b.lastUpdatedAt ?? b.updatedAt;
|
|
207
|
+
return bd > ad ? 1 : bd < ad ? -1 : 0;
|
|
208
|
+
})
|
|
209
|
+
.slice(0, maxCommits);
|
|
210
|
+
|
|
211
|
+
const out: CommitSection[] = [];
|
|
212
|
+
for (const meta of commitMetas) {
|
|
213
|
+
const sha = meta.path.replace(/^commits\//, "").replace(/\.json$/, "");
|
|
214
|
+
const abs = commitSectionAbsPath(cwd, sha);
|
|
215
|
+
if (!existsSync(abs)) continue;
|
|
216
|
+
try {
|
|
217
|
+
const raw = await readFile(abs, "utf-8");
|
|
218
|
+
out.push(JSON.parse(raw) as CommitSection);
|
|
219
|
+
} catch {
|
|
220
|
+
// skip corrupt — best-effort
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Pick the static sections most relevant to the recent commits. Uses the
|
|
228
|
+
* commit messages + filesChanged as the query corpus, then scores every
|
|
229
|
+
* static section against that query.
|
|
230
|
+
*
|
|
231
|
+
* Returns up to `limit` static section metas (we don't load file bodies —
|
|
232
|
+
* the LLM prompt only needs titles + summaries + tags).
|
|
233
|
+
*/
|
|
234
|
+
function selectRelevantStaticSections(
|
|
235
|
+
manifest: GenomeManifestV2,
|
|
236
|
+
commits: CommitSection[],
|
|
237
|
+
limit = 5,
|
|
238
|
+
): SectionMetaV2[] {
|
|
239
|
+
if (commits.length === 0) return [];
|
|
240
|
+
const queryText = commits
|
|
241
|
+
.map((c) => `${c.message} ${c.filesChanged.join(" ")}`)
|
|
242
|
+
.join(" ");
|
|
243
|
+
const queryTerms = new Set(tokenize(queryText));
|
|
244
|
+
if (queryTerms.size === 0) return [];
|
|
245
|
+
|
|
246
|
+
const statics = manifest.sections.filter((s) => s.kind === "static" || s.kind == null);
|
|
247
|
+
const scored = statics
|
|
248
|
+
.map((s) => ({ meta: s, score: scoreSectionMeta(s, queryTerms) }))
|
|
249
|
+
.filter((x) => x.score > 0)
|
|
250
|
+
.sort((a, b) => b.score - a.score)
|
|
251
|
+
.slice(0, limit);
|
|
252
|
+
return scored.map((x) => x.meta);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// Path redaction (privacy)
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
function redactSecretPaths(paths: string[]): string[] {
|
|
260
|
+
return paths.map((p) => (SECRET_PATH_PATTERNS.some((rx) => rx.test(p)) ? "[redacted]" : p));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function redactCommit(c: CommitSection): CommitSection {
|
|
264
|
+
return {
|
|
265
|
+
...c,
|
|
266
|
+
filesChanged: redactSecretPaths(c.filesChanged),
|
|
267
|
+
// The summary may contain raw diff paths from `git show --stat` — strip
|
|
268
|
+
// lines that mention secret paths.
|
|
269
|
+
summary: c.summary
|
|
270
|
+
.split("\n")
|
|
271
|
+
.filter((line) => !SECRET_PATH_PATTERNS.some((rx) => rx.test(line)))
|
|
272
|
+
.join("\n"),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// Prompt construction
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
export function buildSynthesisPrompt(
|
|
281
|
+
commits: CommitSection[],
|
|
282
|
+
staticSections: SectionMetaV2[],
|
|
283
|
+
): { system: string; user: string } {
|
|
284
|
+
const system = [
|
|
285
|
+
"You are a code-archaeologist for a developer's project.",
|
|
286
|
+
"Read the recent commits and static-genome summaries provided, then return",
|
|
287
|
+
"up to 3 short, actionable *discoveries* — concise insights that a busy",
|
|
288
|
+
"engineer would benefit from at a glance.",
|
|
289
|
+
"",
|
|
290
|
+
"Each discovery MUST be a JSON object with these fields:",
|
|
291
|
+
` - "summary": 1-2 sentence insight (string).`,
|
|
292
|
+
` - "evidence": array of { "path": string, "lineRange"?: [number, number] }`,
|
|
293
|
+
` using file paths drawn from the commits' filesChanged lists.`,
|
|
294
|
+
` - "sourceCommits": array of full git SHAs (strings) from the commits below`,
|
|
295
|
+
` that inspired this discovery.`,
|
|
296
|
+
` - "confidence": a number from 0 to 1 (your self-rated confidence).`,
|
|
297
|
+
"",
|
|
298
|
+
"Return ONLY a JSON array of those objects — no prose, no markdown fences,",
|
|
299
|
+
"no commentary. If you have no high-quality discoveries, return [].",
|
|
300
|
+
].join("\n");
|
|
301
|
+
|
|
302
|
+
const commitLines = commits.map((c) => {
|
|
303
|
+
const firstLine = (c.message.split("\n")[0] ?? c.sha.slice(0, 7)).slice(0, 140);
|
|
304
|
+
const files = c.filesChanged.slice(0, 8).join(", ");
|
|
305
|
+
return ` - sha=${c.sha} date=${c.date.slice(0, 10)} subject=${JSON.stringify(firstLine)} files=[${files}]`;
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const staticLines = staticSections.map((s) => {
|
|
309
|
+
const tags = s.tags.slice(0, 6).join(", ");
|
|
310
|
+
return ` - path=${s.path} title=${JSON.stringify(s.title)} tags=[${tags}] summary=${JSON.stringify((s.summary ?? "").slice(0, 160))}`;
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const user = [
|
|
314
|
+
"Recent commits (most recent first):",
|
|
315
|
+
commitLines.length ? commitLines.join("\n") : " (none)",
|
|
316
|
+
"",
|
|
317
|
+
"Relevant static genome sections:",
|
|
318
|
+
staticLines.length ? staticLines.join("\n") : " (none)",
|
|
319
|
+
"",
|
|
320
|
+
"Produce up to 3 discoveries as a JSON array.",
|
|
321
|
+
].join("\n");
|
|
322
|
+
|
|
323
|
+
return { system, user };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
// LLM response parsing
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
|
|
330
|
+
interface RawDiscovery {
|
|
331
|
+
summary?: unknown;
|
|
332
|
+
evidence?: unknown;
|
|
333
|
+
sourceCommits?: unknown;
|
|
334
|
+
confidence?: unknown;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Strict-parse the LLM response into a list of valid discoveries.
|
|
339
|
+
* Malformed entries are dropped; the returned list is never longer than
|
|
340
|
+
* MAX_DISCOVERIES_PER_RUN.
|
|
341
|
+
*
|
|
342
|
+
* The synthesizer LLM may wrap its output in markdown fences ("```json") —
|
|
343
|
+
* we strip those defensively.
|
|
344
|
+
*/
|
|
345
|
+
export function parseSynthesisResponse(
|
|
346
|
+
raw: string,
|
|
347
|
+
knownCommitShas: Set<string>,
|
|
348
|
+
): Array<Omit<DiscoverySection, "id" | "synthesizedAt">> {
|
|
349
|
+
// Strip code fences if present.
|
|
350
|
+
let stripped = raw.trim();
|
|
351
|
+
if (stripped.startsWith("```")) {
|
|
352
|
+
stripped = stripped.replace(/^```(?:json)?\s*/i, "").replace(/```\s*$/g, "").trim();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
let parsed: unknown;
|
|
356
|
+
try {
|
|
357
|
+
parsed = JSON.parse(stripped);
|
|
358
|
+
} catch {
|
|
359
|
+
return [];
|
|
360
|
+
}
|
|
361
|
+
if (!Array.isArray(parsed)) return [];
|
|
362
|
+
|
|
363
|
+
const out: Array<Omit<DiscoverySection, "id" | "synthesizedAt">> = [];
|
|
364
|
+
for (const item of parsed) {
|
|
365
|
+
if (!item || typeof item !== "object") continue;
|
|
366
|
+
const it = item as RawDiscovery;
|
|
367
|
+
|
|
368
|
+
const summary = typeof it.summary === "string" ? it.summary.trim() : "";
|
|
369
|
+
if (!summary || summary.length < 8) continue;
|
|
370
|
+
|
|
371
|
+
let evidence: DiscoveryEvidence[] = [];
|
|
372
|
+
if (Array.isArray(it.evidence)) {
|
|
373
|
+
for (const e of it.evidence) {
|
|
374
|
+
if (!e || typeof e !== "object") continue;
|
|
375
|
+
const path = typeof (e as { path?: unknown }).path === "string" ? (e as { path: string }).path : "";
|
|
376
|
+
if (!path) continue;
|
|
377
|
+
const range = (e as { lineRange?: unknown }).lineRange;
|
|
378
|
+
let lineRange: [number, number] | undefined;
|
|
379
|
+
if (
|
|
380
|
+
Array.isArray(range) &&
|
|
381
|
+
range.length === 2 &&
|
|
382
|
+
typeof range[0] === "number" &&
|
|
383
|
+
typeof range[1] === "number"
|
|
384
|
+
) {
|
|
385
|
+
lineRange = [range[0], range[1]];
|
|
386
|
+
}
|
|
387
|
+
evidence.push(lineRange ? { path, lineRange } : { path });
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
let sourceCommits: string[] = [];
|
|
392
|
+
if (Array.isArray(it.sourceCommits)) {
|
|
393
|
+
for (const c of it.sourceCommits) {
|
|
394
|
+
if (typeof c === "string" && knownCommitShas.has(c)) sourceCommits.push(c);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// Must reference at least one known commit — otherwise it's hallucinated.
|
|
398
|
+
if (sourceCommits.length === 0) continue;
|
|
399
|
+
|
|
400
|
+
let confidence = typeof it.confidence === "number" ? it.confidence : 0.5;
|
|
401
|
+
if (!Number.isFinite(confidence)) confidence = 0.5;
|
|
402
|
+
confidence = Math.max(0, Math.min(1, confidence));
|
|
403
|
+
|
|
404
|
+
out.push({ summary, evidence, sourceCommits, confidence });
|
|
405
|
+
if (out.length >= MAX_DISCOVERIES_PER_RUN) break;
|
|
406
|
+
}
|
|
407
|
+
return out;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ---------------------------------------------------------------------------
|
|
411
|
+
// Stable ID hashing
|
|
412
|
+
// ---------------------------------------------------------------------------
|
|
413
|
+
|
|
414
|
+
function discoveryId(summary: string, sourceCommits: string[]): string {
|
|
415
|
+
const h = createHash("sha256");
|
|
416
|
+
h.update(summary.trim());
|
|
417
|
+
h.update("");
|
|
418
|
+
h.update([...sourceCommits].sort().join(","));
|
|
419
|
+
return h.digest("hex").slice(0, 16);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ---------------------------------------------------------------------------
|
|
423
|
+
// Pruning (mirror commit-pruning policy)
|
|
424
|
+
// ---------------------------------------------------------------------------
|
|
425
|
+
|
|
426
|
+
async function pruneDiscoverySections(
|
|
427
|
+
cwd: string,
|
|
428
|
+
manifest: GenomeManifestV2,
|
|
429
|
+
): Promise<string[]> {
|
|
430
|
+
const metas = manifest.sections.filter((s) => s.kind === "discovery");
|
|
431
|
+
if (metas.length <= DISCOVERY_RETENTION_LIMIT) return [];
|
|
432
|
+
|
|
433
|
+
const dated = metas.map((m) => {
|
|
434
|
+
const id = m.path.replace(/^discoveries\//, "").replace(/\.json$/, "");
|
|
435
|
+
return { meta: m, id, date: m.lastUpdatedAt ?? m.updatedAt };
|
|
436
|
+
});
|
|
437
|
+
dated.sort((a, b) => (a.date > b.date ? -1 : a.date < b.date ? 1 : 0));
|
|
438
|
+
|
|
439
|
+
const keep = new Set(dated.slice(0, DISCOVERY_RETENTION_LIMIT).map((d) => d.meta.path));
|
|
440
|
+
const dropped: string[] = [];
|
|
441
|
+
for (const { meta, id } of dated) {
|
|
442
|
+
if (keep.has(meta.path)) continue;
|
|
443
|
+
dropped.push(id);
|
|
444
|
+
const idx = manifest.sections.findIndex((s) => s.path === meta.path);
|
|
445
|
+
if (idx >= 0) manifest.sections.splice(idx, 1);
|
|
446
|
+
const abs = discoverySectionAbsPath(cwd, id);
|
|
447
|
+
if (existsSync(abs)) await unlink(abs).catch(() => {});
|
|
448
|
+
}
|
|
449
|
+
return dropped;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ---------------------------------------------------------------------------
|
|
453
|
+
// Main entry — synthesize()
|
|
454
|
+
// ---------------------------------------------------------------------------
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Run one synthesis pass.
|
|
458
|
+
*
|
|
459
|
+
* Hard gate order (early returns):
|
|
460
|
+
* 1. Free tier → exit with `{skipped: true, reason: "free-tier"}`.
|
|
461
|
+
* This is the ONLY tier check that bypasses every other path; we never
|
|
462
|
+
* reach the LLM call for free users, even accidentally.
|
|
463
|
+
* 2. No genome / no commits → skip with the matching reason.
|
|
464
|
+
* 3. Throttled (and not --force) → skip.
|
|
465
|
+
* 4. LLM provider unavailable → skip with `"no-provider"`.
|
|
466
|
+
* 5. LLM call fails → skip with `"llm-failed"`.
|
|
467
|
+
* 6. No valid discoveries parsed → skip with `"no-discoveries-produced"`.
|
|
468
|
+
*
|
|
469
|
+
* Otherwise: writes 0-3 discovery sections to disk + updates the manifest +
|
|
470
|
+
* updates the state file (unless dry-run, in which case nothing is written).
|
|
471
|
+
*/
|
|
472
|
+
export async function synthesize(opts: SynthesizeOpts = {}): Promise<SynthesizeResult> {
|
|
473
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
474
|
+
const tier = resolveTier(opts);
|
|
475
|
+
const maxCommits = opts.maxCommits ?? 10;
|
|
476
|
+
const dryRun = opts.dryRun ?? false;
|
|
477
|
+
const force = opts.force ?? false;
|
|
478
|
+
const nowMs = opts.nowMs ?? Date.now();
|
|
479
|
+
|
|
480
|
+
// -------- HARD GATE: free tier never calls the LLM -----------------------
|
|
481
|
+
if (tier === "free") {
|
|
482
|
+
return { skipped: true, reason: "free-tier" };
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// -------- Genome present? ------------------------------------------------
|
|
486
|
+
const manifest = await loadManifestV2(cwd);
|
|
487
|
+
if (!manifest) {
|
|
488
|
+
return { skipped: true, reason: "no-genome" };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// -------- Throttle window -----------------------------------------------
|
|
492
|
+
if (!force && !dryRun) {
|
|
493
|
+
const state = await readSynthesisState(cwd);
|
|
494
|
+
if (state) {
|
|
495
|
+
const ageMs = nowMs - new Date(state.lastRunAt).getTime();
|
|
496
|
+
const windowMs = tier === "team" ? THROTTLE_TEAM_MS : THROTTLE_PRO_MS;
|
|
497
|
+
if (Number.isFinite(ageMs) && ageMs >= 0 && ageMs < windowMs) {
|
|
498
|
+
return { skipped: true, reason: "throttled" };
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// -------- Load + redact recent commits -----------------------------------
|
|
504
|
+
const rawCommits = await loadRecentCommitSections(cwd, manifest, maxCommits);
|
|
505
|
+
if (rawCommits.length === 0) {
|
|
506
|
+
return { skipped: true, reason: "no-commits" };
|
|
507
|
+
}
|
|
508
|
+
const commits = rawCommits.map(redactCommit);
|
|
509
|
+
const knownShas = new Set(commits.map((c) => c.sha));
|
|
510
|
+
const staticSections = selectRelevantStaticSections(manifest, commits, 5);
|
|
511
|
+
|
|
512
|
+
// -------- Build prompt + call LLM ----------------------------------------
|
|
513
|
+
const { system, user } = buildSynthesisPrompt(commits, staticSections);
|
|
514
|
+
const provider = opts.provider ?? (await selectProvider());
|
|
515
|
+
if (provider.name === "none") {
|
|
516
|
+
return { skipped: true, reason: "no-provider" };
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
let llmOutput: string;
|
|
520
|
+
try {
|
|
521
|
+
const result = await provider.summarize(user, system, { maxTokens: 800 });
|
|
522
|
+
llmOutput = result.output;
|
|
523
|
+
} catch {
|
|
524
|
+
return { skipped: true, reason: "llm-failed" };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// -------- Parse + validate ----------------------------------------------
|
|
528
|
+
const candidates = parseSynthesisResponse(llmOutput, knownShas);
|
|
529
|
+
if (candidates.length < MIN_DISCOVERIES_PER_RUN) {
|
|
530
|
+
return { skipped: true, reason: "no-discoveries-produced" };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const synthesizedAt = new Date(nowMs).toISOString();
|
|
534
|
+
const discoveries: DiscoverySection[] = candidates.map((c) => ({
|
|
535
|
+
id: discoveryId(c.summary, c.sourceCommits),
|
|
536
|
+
summary: c.summary,
|
|
537
|
+
evidence: c.evidence,
|
|
538
|
+
sourceCommits: c.sourceCommits,
|
|
539
|
+
synthesizedAt,
|
|
540
|
+
confidence: c.confidence,
|
|
541
|
+
}));
|
|
542
|
+
|
|
543
|
+
// De-duplicate against existing discovery sections by ID.
|
|
544
|
+
const existingIds = new Set(
|
|
545
|
+
manifest.sections
|
|
546
|
+
.filter((s) => s.kind === "discovery")
|
|
547
|
+
.map((s) => s.path.replace(/^discoveries\//, "").replace(/\.json$/, "")),
|
|
548
|
+
);
|
|
549
|
+
const fresh = discoveries.filter((d) => !existingIds.has(d.id));
|
|
550
|
+
|
|
551
|
+
// -------- Dry run: report would-be output ------------------------------
|
|
552
|
+
if (dryRun) {
|
|
553
|
+
return {
|
|
554
|
+
skipped: false,
|
|
555
|
+
wouldWriteIds: fresh.map((d) => d.id),
|
|
556
|
+
discoveries: fresh,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (fresh.length === 0) {
|
|
561
|
+
return { skipped: true, reason: "no-discoveries-produced" };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// -------- Persist + update manifest ------------------------------------
|
|
565
|
+
for (const d of fresh) {
|
|
566
|
+
const relPath = await writeDiscoverySectionFile(cwd, d);
|
|
567
|
+
const meta: SectionMetaV2 = {
|
|
568
|
+
path: relPath,
|
|
569
|
+
title: `discovery ${d.id.slice(0, 7)} — ${d.summary.slice(0, 80)}`,
|
|
570
|
+
summary: d.summary.slice(0, 240),
|
|
571
|
+
tags: [
|
|
572
|
+
"discovery",
|
|
573
|
+
"synthesis",
|
|
574
|
+
d.id.slice(0, 7),
|
|
575
|
+
...d.sourceCommits.map((s) => s.slice(0, 7)),
|
|
576
|
+
...d.evidence.slice(0, 5).map((e) => e.path.toLowerCase()),
|
|
577
|
+
],
|
|
578
|
+
tokens: Math.ceil((d.summary.length + JSON.stringify(d.evidence).length) / 4),
|
|
579
|
+
updatedAt: synthesizedAt,
|
|
580
|
+
lastUpdatedAt: synthesizedAt,
|
|
581
|
+
sourceTrust: "synthesis",
|
|
582
|
+
confidence: d.confidence,
|
|
583
|
+
kind: "discovery",
|
|
584
|
+
};
|
|
585
|
+
const idx = manifest.sections.findIndex((s) => s.path === relPath);
|
|
586
|
+
if (idx >= 0) manifest.sections[idx] = meta;
|
|
587
|
+
else manifest.sections.push(meta);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
await pruneDiscoverySections(cwd, manifest);
|
|
591
|
+
await saveManifestV2(cwd, manifest);
|
|
592
|
+
|
|
593
|
+
await writeSynthesisState(cwd, {
|
|
594
|
+
lastRunAt: synthesizedAt,
|
|
595
|
+
lastTier: tier,
|
|
596
|
+
lastDiscoveryCount: fresh.length,
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
return {
|
|
600
|
+
skipped: false,
|
|
601
|
+
writtenIds: fresh.map((d) => d.id),
|
|
602
|
+
discoveries: fresh,
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// ---------------------------------------------------------------------------
|
|
607
|
+
// CLI entry point
|
|
608
|
+
// ---------------------------------------------------------------------------
|
|
609
|
+
|
|
610
|
+
interface CliArgs {
|
|
611
|
+
cwd: string;
|
|
612
|
+
dryRun: boolean;
|
|
613
|
+
force: boolean;
|
|
614
|
+
maxCommits: number;
|
|
615
|
+
tier?: Tier;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export function parseCliArgs(argv: string[]): CliArgs {
|
|
619
|
+
let cwd = process.cwd();
|
|
620
|
+
let dryRun = false;
|
|
621
|
+
let force = false;
|
|
622
|
+
let maxCommits = 10;
|
|
623
|
+
let tier: Tier | undefined;
|
|
624
|
+
|
|
625
|
+
for (let i = 0; i < argv.length; i++) {
|
|
626
|
+
const a = argv[i]!;
|
|
627
|
+
if (a === "--dry-run") dryRun = true;
|
|
628
|
+
else if (a === "--force") force = true;
|
|
629
|
+
else if (a === "--cwd" && argv[i + 1]) cwd = resolve(argv[++i]!);
|
|
630
|
+
else if (a.startsWith("--max-commits=")) {
|
|
631
|
+
const v = parseInt(a.split("=")[1] ?? "", 10);
|
|
632
|
+
if (Number.isFinite(v) && v > 0) maxCommits = v;
|
|
633
|
+
} else if (a === "--max-commits" && argv[i + 1]) {
|
|
634
|
+
const v = parseInt(argv[++i]!, 10);
|
|
635
|
+
if (Number.isFinite(v) && v > 0) maxCommits = v;
|
|
636
|
+
} else if (a.startsWith("--tier=")) {
|
|
637
|
+
const t = a.split("=")[1] ?? "";
|
|
638
|
+
if (t === "free" || t === "pro" || t === "team") tier = t;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return { cwd, dryRun, force, maxCommits, tier };
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
export async function main(argv: string[] = process.argv.slice(2)): Promise<number> {
|
|
646
|
+
const args = parseCliArgs(argv);
|
|
647
|
+
const result = await synthesize({
|
|
648
|
+
cwd: args.cwd,
|
|
649
|
+
dryRun: args.dryRun,
|
|
650
|
+
force: args.force,
|
|
651
|
+
maxCommits: args.maxCommits,
|
|
652
|
+
tier: args.tier,
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
// Human-readable status line — pipe-friendly for CI logs.
|
|
656
|
+
if (result.skipped) {
|
|
657
|
+
process.stderr.write(`[genome-synthesizer] skipped: ${result.reason ?? "unknown"}\n`);
|
|
658
|
+
return 0;
|
|
659
|
+
}
|
|
660
|
+
const ids = result.writtenIds ?? result.wouldWriteIds ?? [];
|
|
661
|
+
const verb = args.dryRun ? "would write" : "wrote";
|
|
662
|
+
process.stderr.write(
|
|
663
|
+
`[genome-synthesizer] ${verb} ${ids.length} discovery section(s): ${ids.join(", ")}\n`,
|
|
664
|
+
);
|
|
665
|
+
if (args.dryRun && result.discoveries) {
|
|
666
|
+
process.stdout.write(JSON.stringify(result.discoveries, null, 2) + "\n");
|
|
667
|
+
}
|
|
668
|
+
return 0;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Bun entry: invoke main when run directly.
|
|
672
|
+
// `import.meta.main` is bun-specific; falls back to a path check.
|
|
673
|
+
if (
|
|
674
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
675
|
+
(typeof (import.meta as any).main === "boolean" && (import.meta as any).main) ||
|
|
676
|
+
(typeof process.argv[1] === "string" && process.argv[1].endsWith("genome-synthesizer.ts"))
|
|
677
|
+
) {
|
|
678
|
+
void main().then((code) => process.exit(code));
|
|
679
|
+
}
|