@quickchatai/quickreview 0.0.0-stage → 1.1.0
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/LICENSE +23 -0
- package/README.md +385 -2
- package/THIRD-PARTY-NOTICES.md +134 -0
- package/agents/pass-abstraction.md +64 -0
- package/agents/pass-ask-human.md +134 -0
- package/agents/pass-backcompat.md +67 -0
- package/agents/pass-baseline.md +61 -0
- package/agents/pass-behaviour-delta.md +64 -0
- package/agents/pass-bounds.md +36 -0
- package/agents/pass-claims.md +31 -0
- package/agents/pass-concurrency.md +168 -0
- package/agents/pass-consistency.md +57 -0
- package/agents/pass-contracts.md +30 -0
- package/agents/pass-data-safety.md +54 -0
- package/agents/pass-deferred.md +35 -0
- package/agents/pass-delivery.md +37 -0
- package/agents/pass-duplication.md +60 -0
- package/agents/pass-intent.md +63 -0
- package/agents/pass-lint-conformance.md +46 -0
- package/agents/pass-maintainability.md +67 -0
- package/agents/pass-placement.md +68 -0
- package/agents/pass-prod-risk.md +24 -0
- package/agents/pass-rollout.md +32 -0
- package/agents/pass-roundtrip.md +46 -0
- package/agents/pass-security.md +31 -0
- package/agents/pass-simplicity.md +68 -0
- package/agents/pass-statelessness.md +27 -0
- package/agents/pass-style.md +37 -0
- package/agents/pass-sweep.md +57 -0
- package/agents/pass-tests.md +58 -0
- package/agents/pass-ui.md +23 -0
- package/agents/pass-validate.md +50 -0
- package/agents/pass-widened.md +46 -0
- package/agents/scan/pass-scan.md +86 -0
- package/bin/qcr.js +11 -0
- package/config.example.toml +1067 -0
- package/deploy/ansible/README.md +356 -0
- package/deploy/ansible/group_vars/all.yml +212 -0
- package/deploy/ansible/inventory.example.ini +25 -0
- package/deploy/ansible/playbook.yml +360 -0
- package/deploy/collect-secrets.sh +289 -0
- package/deploy/collect-slack-secrets.mjs +1190 -0
- package/deploy/github-app-manifest.json +31 -0
- package/deploy/manifest.schema.json +75 -0
- package/deploy/qcr-update.sh +803 -0
- package/deploy/reviewer/AGENTS.override.md +12 -0
- package/dist/extensions/cli/bash-guard-ambient.js +42 -0
- package/dist/extensions/cli/bash-guard-ambient.js.map +1 -0
- package/dist/extensions/cli/bash-guard.js +5 -0
- package/dist/extensions/cli/bash-guard.js.map +1 -0
- package/dist/extensions/cli/companion-guard.js +5 -0
- package/dist/extensions/cli/companion-guard.js.map +1 -0
- package/dist/extensions/cli/debug-round.js +8 -0
- package/dist/extensions/cli/debug-round.js.map +1 -0
- package/dist/extensions/cli/inject-bridge.js +5 -0
- package/dist/extensions/cli/inject-bridge.js.map +1 -0
- package/dist/extensions/cli/ledger-pin.js +5 -0
- package/dist/extensions/cli/ledger-pin.js.map +1 -0
- package/dist/extensions/cli/lens.js +13 -0
- package/dist/extensions/cli/lens.js.map +1 -0
- package/dist/extensions/cli/muted-theme.js +5 -0
- package/dist/extensions/cli/muted-theme.js.map +1 -0
- package/dist/extensions/cli/onboarding.js +5 -0
- package/dist/extensions/cli/onboarding.js.map +1 -0
- package/dist/extensions/cli/setup.js +5 -0
- package/dist/extensions/cli/setup.js.map +1 -0
- package/dist/extensions/cli/tui-view.js +5 -0
- package/dist/extensions/cli/tui-view.js.map +1 -0
- package/dist/extensions/companion-guard.js +361 -0
- package/dist/extensions/companion-guard.js.map +1 -0
- package/dist/extensions/debug-round.js +204 -0
- package/dist/extensions/debug-round.js.map +1 -0
- package/dist/extensions/inject-bridge.js +603 -0
- package/dist/extensions/inject-bridge.js.map +1 -0
- package/dist/extensions/muted-theme.js +12 -0
- package/dist/extensions/muted-theme.js.map +1 -0
- package/dist/extensions/onboarding.js +175 -0
- package/dist/extensions/onboarding.js.map +1 -0
- package/dist/extensions/setup-replay.js +269 -0
- package/dist/extensions/setup-replay.js.map +1 -0
- package/dist/extensions/setup-scan.js +998 -0
- package/dist/extensions/setup-scan.js.map +1 -0
- package/dist/extensions/setup-triage.js +175 -0
- package/dist/extensions/setup-triage.js.map +1 -0
- package/dist/extensions/setup.js +1160 -0
- package/dist/extensions/setup.js.map +1 -0
- package/dist/extensions/tui-view.js +389 -0
- package/dist/extensions/tui-view.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/client.js +376 -0
- package/dist/extensions/vendor/openviking-pi/client.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/config.js +189 -0
- package/dist/extensions/vendor/openviking-pi/config.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/index.js +421 -0
- package/dist/extensions/vendor/openviking-pi/index.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +86 -0
- package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs +408 -0
- package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +41 -0
- package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-hint.js +290 -0
- package/dist/extensions/vendor/openviking-pi/qcr-hint.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-honest.js +148 -0
- package/dist/extensions/vendor/openviking-pi/qcr-honest.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-hooks.js +68 -0
- package/dist/extensions/vendor/openviking-pi/qcr-hooks.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-leaf.js +77 -0
- package/dist/extensions/vendor/openviking-pi/qcr-leaf.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-lesson.js +216 -0
- package/dist/extensions/vendor/openviking-pi/qcr-lesson.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js +154 -0
- package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-recall.js +254 -0
- package/dist/extensions/vendor/openviking-pi/qcr-recall.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-shim.js +94 -0
- package/dist/extensions/vendor/openviking-pi/qcr-shim.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-write.js +333 -0
- package/dist/extensions/vendor/openviking-pi/qcr-write.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/recall.js +108 -0
- package/dist/extensions/vendor/openviking-pi/recall.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs +529 -0
- package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs +205 -0
- package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs +442 -0
- package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs +286 -0
- package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +189 -0
- package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs +580 -0
- package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs +11 -0
- package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs +63 -0
- package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs +58 -0
- package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
- package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/sync.js +127 -0
- package/dist/extensions/vendor/openviking-pi/sync.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/takeover.js +25 -0
- package/dist/extensions/vendor/openviking-pi/takeover.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/tools.js +417 -0
- package/dist/extensions/vendor/openviking-pi/tools.js.map +1 -0
- package/dist/src/anomalies.js +483 -0
- package/dist/src/anomalies.js.map +1 -0
- package/dist/src/answerworker.js +180 -0
- package/dist/src/answerworker.js.map +1 -0
- package/dist/src/appstamp.js +68 -0
- package/dist/src/appstamp.js.map +1 -0
- package/dist/src/archive/admission.js +3 -0
- package/dist/src/archive/admission.js.map +1 -0
- package/dist/src/archive/archivedb.js +474 -0
- package/dist/src/archive/archivedb.js.map +1 -0
- package/dist/src/archive/archiver.js +1770 -0
- package/dist/src/archive/archiver.js.map +1 -0
- package/dist/src/archive/archivewrite.js +198 -0
- package/dist/src/archive/archivewrite.js.map +1 -0
- package/dist/src/archive/bundle.js +266 -0
- package/dist/src/archive/bundle.js.map +1 -0
- package/dist/src/archive/dbmove.js +638 -0
- package/dist/src/archive/dbmove.js.map +1 -0
- package/dist/src/archive/duplicatecounts.js +80 -0
- package/dist/src/archive/duplicatecounts.js.map +1 -0
- package/dist/src/archive/eligibility.js +113 -0
- package/dist/src/archive/eligibility.js.map +1 -0
- package/dist/src/archive/extract.js +288 -0
- package/dist/src/archive/extract.js.map +1 -0
- package/dist/src/archive/fsutil.js +330 -0
- package/dist/src/archive/fsutil.js.map +1 -0
- package/dist/src/archive/gzfile.js +277 -0
- package/dist/src/archive/gzfile.js.map +1 -0
- package/dist/src/archive/kinds.js +22 -0
- package/dist/src/archive/kinds.js.map +1 -0
- package/dist/src/archive/livecheckpoint.js +178 -0
- package/dist/src/archive/livecheckpoint.js.map +1 -0
- package/dist/src/archive/liveretention.js +145 -0
- package/dist/src/archive/liveretention.js.map +1 -0
- package/dist/src/archive/manifest.js +284 -0
- package/dist/src/archive/manifest.js.map +1 -0
- package/dist/src/archive/overseer.js +283 -0
- package/dist/src/archive/overseer.js.map +1 -0
- package/dist/src/archive/piglobal.js +129 -0
- package/dist/src/archive/piglobal.js.map +1 -0
- package/dist/src/archive/plan.js +398 -0
- package/dist/src/archive/plan.js.map +1 -0
- package/dist/src/archive/prarchive.js +831 -0
- package/dist/src/archive/prarchive.js.map +1 -0
- package/dist/src/archive/prfiles.js +303 -0
- package/dist/src/archive/prfiles.js.map +1 -0
- package/dist/src/archive/report.js +169 -0
- package/dist/src/archive/report.js.map +1 -0
- package/dist/src/archive/restore.js +276 -0
- package/dist/src/archive/restore.js.map +1 -0
- package/dist/src/archive/rows.js +106 -0
- package/dist/src/archive/rows.js.map +1 -0
- package/dist/src/archive/seal.js +480 -0
- package/dist/src/archive/seal.js.map +1 -0
- package/dist/src/archive/status.js +528 -0
- package/dist/src/archive/status.js.map +1 -0
- package/dist/src/archive/storepass.js +409 -0
- package/dist/src/archive/storepass.js.map +1 -0
- package/dist/src/backfill.js +217 -0
- package/dist/src/backfill.js.map +1 -0
- package/dist/src/bench/cost.js +87 -0
- package/dist/src/bench/exec.js +76 -0
- package/dist/src/bench/values.js +14 -0
- package/dist/src/botcontrol.js +127 -0
- package/dist/src/botcontrol.js.map +1 -0
- package/dist/src/botworker.js +805 -0
- package/dist/src/botworker.js.map +1 -0
- package/dist/src/capture.js +234 -0
- package/dist/src/capture.js.map +1 -0
- package/dist/src/cases.js +1491 -0
- package/dist/src/cases.js.map +1 -0
- package/dist/src/channelfs.js +27 -0
- package/dist/src/channelfs.js.map +1 -0
- package/dist/src/checklist.js +312 -0
- package/dist/src/checklist.js.map +1 -0
- package/dist/src/ciretry.js +91 -0
- package/dist/src/ciretry.js.map +1 -0
- package/dist/src/cli.js +1916 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/cliops.js +333 -0
- package/dist/src/cliops.js.map +1 -0
- package/dist/src/clonedir.js +15 -0
- package/dist/src/clonedir.js.map +1 -0
- package/dist/src/commandeffects.js +43 -0
- package/dist/src/commandeffects.js.map +1 -0
- package/dist/src/commands.js +1312 -0
- package/dist/src/commands.js.map +1 -0
- package/dist/src/companioninbox.js +203 -0
- package/dist/src/companioninbox.js.map +1 -0
- package/dist/src/compare.js +673 -0
- package/dist/src/compare.js.map +1 -0
- package/dist/src/config.js +1027 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/configcheck.js +111 -0
- package/dist/src/configcheck.js.map +1 -0
- package/dist/src/coreext.js +43 -0
- package/dist/src/coreext.js.map +1 -0
- package/dist/src/daemon.js +929 -0
- package/dist/src/daemon.js.map +1 -0
- package/dist/src/debuground.js +731 -0
- package/dist/src/debuground.js.map +1 -0
- package/dist/src/debugroundpi.js +219 -0
- package/dist/src/debugroundpi.js.map +1 -0
- package/dist/src/diagnostics.js +14 -0
- package/dist/src/diagnostics.js.map +1 -0
- package/dist/src/digest.js +139 -0
- package/dist/src/digest.js.map +1 -0
- package/dist/src/dispatch.js +1726 -0
- package/dist/src/dispatch.js.map +1 -0
- package/dist/src/engine/answerguard.js +18 -0
- package/dist/src/engine/answerguard.js.map +1 -0
- package/dist/src/engine/botsdk.js +262 -0
- package/dist/src/engine/botsdk.js.map +1 -0
- package/dist/src/engine/caserunner-sdk.js +332 -0
- package/dist/src/engine/caserunner-sdk.js.map +1 -0
- package/dist/src/engine/debugpi.js +194 -0
- package/dist/src/engine/debugpi.js.map +1 -0
- package/dist/src/eod.js +473 -0
- package/dist/src/eod.js.map +1 -0
- package/dist/src/eodpinwatch.js +391 -0
- package/dist/src/eodpinwatch.js.map +1 -0
- package/dist/src/escalation.js +1009 -0
- package/dist/src/escalation.js.map +1 -0
- package/dist/src/eventloop.js +779 -0
- package/dist/src/eventloop.js.map +1 -0
- package/dist/src/execboundary.js +430 -0
- package/dist/src/execboundary.js.map +1 -0
- package/dist/src/feedback.js +915 -0
- package/dist/src/feedback.js.map +1 -0
- package/dist/src/findingscheck.js +200 -0
- package/dist/src/findingscheck.js.map +1 -0
- package/dist/src/fsdenied.js +26 -0
- package/dist/src/fsdenied.js.map +1 -0
- package/dist/src/ghread.js +181 -0
- package/dist/src/ghread.js.map +1 -0
- package/dist/src/github.js +1063 -0
- package/dist/src/github.js.map +1 -0
- package/dist/src/homes.js +103 -0
- package/dist/src/homes.js.map +1 -0
- package/dist/src/hostroute.js +24 -0
- package/dist/src/hostroute.js.map +1 -0
- package/dist/src/improvements.js +625 -0
- package/dist/src/improvements.js.map +1 -0
- package/dist/src/install.js +946 -0
- package/dist/src/install.js.map +1 -0
- package/dist/src/installkind.js +40 -0
- package/dist/src/installkind.js.map +1 -0
- package/dist/src/ipc/capability.js +286 -0
- package/dist/src/ipc/capability.js.map +1 -0
- package/dist/src/ipc/client.js +226 -0
- package/dist/src/ipc/client.js.map +1 -0
- package/dist/src/ipc/control.js +70 -0
- package/dist/src/ipc/control.js.map +1 -0
- package/dist/src/ipc/ghreadshim.js +77 -0
- package/dist/src/ipc/ghreadshim.js.map +1 -0
- package/dist/src/ipc/launcher.js +65 -0
- package/dist/src/ipc/launcher.js.map +1 -0
- package/dist/src/ipc/permits.js +293 -0
- package/dist/src/ipc/permits.js.map +1 -0
- package/dist/src/ipc/postshim.js +137 -0
- package/dist/src/ipc/postshim.js.map +1 -0
- package/dist/src/ipc/protocol.js +239 -0
- package/dist/src/ipc/protocol.js.map +1 -0
- package/dist/src/ipc/server.js +312 -0
- package/dist/src/ipc/server.js.map +1 -0
- package/dist/src/ipc/workerspec.js +148 -0
- package/dist/src/ipc/workerspec.js.map +1 -0
- package/dist/src/learner/learner.js +1272 -0
- package/dist/src/learner/learner.js.map +1 -0
- package/dist/src/learner/miner.js +948 -0
- package/dist/src/learner/miner.js.map +1 -0
- package/dist/src/learner/packet.js +216 -0
- package/dist/src/learner/packet.js.map +1 -0
- package/dist/src/learner/receipt.js +135 -0
- package/dist/src/learner/receipt.js.map +1 -0
- package/dist/src/learner/status.js +38 -0
- package/dist/src/learner/status.js.map +1 -0
- package/dist/src/ledgercheck.js +120 -0
- package/dist/src/ledgercheck.js.map +1 -0
- package/dist/src/lessonledger.js +451 -0
- package/dist/src/lessonledger.js.map +1 -0
- package/dist/src/lessonselect.js +639 -0
- package/dist/src/lessonselect.js.map +1 -0
- package/dist/src/lessonshadow.js +540 -0
- package/dist/src/lessonshadow.js.map +1 -0
- package/dist/src/levers.js +302 -0
- package/dist/src/levers.js.map +1 -0
- package/dist/src/live/projection.js +450 -0
- package/dist/src/live/projection.js.map +1 -0
- package/dist/src/live/publish.js +83 -0
- package/dist/src/live/publish.js.map +1 -0
- package/dist/src/live/read.js +60 -0
- package/dist/src/live/read.js.map +1 -0
- package/dist/src/livecli.js +288 -0
- package/dist/src/livecli.js.map +1 -0
- package/dist/src/loginhint.js +42 -0
- package/dist/src/loginhint.js.map +1 -0
- package/dist/src/loginrefresh/atomic.js +66 -0
- package/dist/src/loginrefresh/atomic.js.map +1 -0
- package/dist/src/loginrefresh/lock.js +93 -0
- package/dist/src/loginrefresh/lock.js.map +1 -0
- package/dist/src/loginrefresh/main.js +58 -0
- package/dist/src/loginrefresh/main.js.map +1 -0
- package/dist/src/loginrefresh/pirefresh.js +49 -0
- package/dist/src/loginrefresh/pirefresh.js.map +1 -0
- package/dist/src/loginrefresh/refresher.js +286 -0
- package/dist/src/loginrefresh/refresher.js.map +1 -0
- package/dist/src/loginrefresh/supervisor.js +253 -0
- package/dist/src/loginrefresh/supervisor.js.map +1 -0
- package/dist/src/loginrefresh/types.js +4 -0
- package/dist/src/loginrefresh/types.js.map +1 -0
- package/dist/src/memory/context.js +251 -0
- package/dist/src/memory/context.js.map +1 -0
- package/dist/src/memory/datafence.js +5 -0
- package/dist/src/memory/datafence.js.map +1 -0
- package/dist/src/memory/delivery.js +131 -0
- package/dist/src/memory/delivery.js.map +1 -0
- package/dist/src/memory/eventtags.js +81 -0
- package/dist/src/memory/eventtags.js.map +1 -0
- package/dist/src/memory/health.js +713 -0
- package/dist/src/memory/health.js.map +1 -0
- package/dist/src/memory/import.js +504 -0
- package/dist/src/memory/import.js.map +1 -0
- package/dist/src/memory/layout.js +252 -0
- package/dist/src/memory/layout.js.map +1 -0
- package/dist/src/memory/legacy-file.js +100 -0
- package/dist/src/memory/legacy-file.js.map +1 -0
- package/dist/src/memory/lessons.js +370 -0
- package/dist/src/memory/lessons.js.map +1 -0
- package/dist/src/memory/ovsession.js +449 -0
- package/dist/src/memory/ovsession.js.map +1 -0
- package/dist/src/memory/relayout.js +259 -0
- package/dist/src/memory/relayout.js.map +1 -0
- package/dist/src/memory/roundactivity.js +180 -0
- package/dist/src/memory/roundactivity.js.map +1 -0
- package/dist/src/memory/selection.js +690 -0
- package/dist/src/memory/selection.js.map +1 -0
- package/dist/src/memory/shadowstatus.js +69 -0
- package/dist/src/memory/shadowstatus.js.map +1 -0
- package/dist/src/memory/store.js +83 -0
- package/dist/src/memory/store.js.map +1 -0
- package/dist/src/memory/textidentity.js +8 -0
- package/dist/src/memory/textidentity.js.map +1 -0
- package/dist/src/metrics.js +773 -0
- package/dist/src/metrics.js.map +1 -0
- package/dist/src/missingconfig.js +8 -0
- package/dist/src/missingconfig.js.map +1 -0
- package/dist/src/npmverbs.js +226 -0
- package/dist/src/npmverbs.js.map +1 -0
- package/dist/src/onboard/choices.js +191 -0
- package/dist/src/onboard/choices.js.map +1 -0
- package/dist/src/onboard/engine.js +344 -0
- package/dist/src/onboard/engine.js.map +1 -0
- package/dist/src/onboard/launch.js +83 -0
- package/dist/src/onboard/launch.js.map +1 -0
- package/dist/src/onboard/steps/appslug.js +211 -0
- package/dist/src/onboard/steps/appslug.js.map +1 -0
- package/dist/src/onboard/steps/bringup.js +408 -0
- package/dist/src/onboard/steps/bringup.js.map +1 -0
- package/dist/src/onboard/steps/compose.js +343 -0
- package/dist/src/onboard/steps/compose.js.map +1 -0
- package/dist/src/onboard/steps/configdelivery.js +50 -0
- package/dist/src/onboard/steps/configdelivery.js.map +1 -0
- package/dist/src/onboard/steps/configfill.js +1148 -0
- package/dist/src/onboard/steps/configfill.js.map +1 -0
- package/dist/src/onboard/steps/deploysh.js +80 -0
- package/dist/src/onboard/steps/deploysh.js.map +1 -0
- package/dist/src/onboard/steps/embeddings.js +472 -0
- package/dist/src/onboard/steps/embeddings.js.map +1 -0
- package/dist/src/onboard/steps/escalation.js +571 -0
- package/dist/src/onboard/steps/escalation.js.map +1 -0
- package/dist/src/onboard/steps/githubapp.js +1092 -0
- package/dist/src/onboard/steps/githubapp.js.map +1 -0
- package/dist/src/onboard/steps/golive.js +506 -0
- package/dist/src/onboard/steps/golive.js.map +1 -0
- package/dist/src/onboard/steps/index.js +67 -0
- package/dist/src/onboard/steps/index.js.map +1 -0
- package/dist/src/onboard/steps/ingress.js +762 -0
- package/dist/src/onboard/steps/ingress.js.map +1 -0
- package/dist/src/onboard/steps/inventory.js +420 -0
- package/dist/src/onboard/steps/inventory.js.map +1 -0
- package/dist/src/onboard/steps/llmauth.js +893 -0
- package/dist/src/onboard/steps/llmauth.js.map +1 -0
- package/dist/src/onboard/steps/memory.js +1060 -0
- package/dist/src/onboard/steps/memory.js.map +1 -0
- package/dist/src/onboard/steps/memorykeys.js +43 -0
- package/dist/src/onboard/steps/memorykeys.js.map +1 -0
- package/dist/src/onboard/steps/preflight.js +172 -0
- package/dist/src/onboard/steps/preflight.js.map +1 -0
- package/dist/src/onboard/steps/proof.js +502 -0
- package/dist/src/onboard/steps/proof.js.map +1 -0
- package/dist/src/onboard/steps/ssh.js +300 -0
- package/dist/src/onboard/steps/ssh.js.map +1 -0
- package/dist/src/onboard/steps/topology.js +291 -0
- package/dist/src/onboard/steps/topology.js.map +1 -0
- package/dist/src/onboard/steps/transport.js +511 -0
- package/dist/src/onboard/steps/transport.js.map +1 -0
- package/dist/src/onboard/steps/verifyup.js +362 -0
- package/dist/src/onboard/steps/verifyup.js.map +1 -0
- package/dist/src/onboard/steps/vmdeploy.js +368 -0
- package/dist/src/onboard/steps/vmdeploy.js.map +1 -0
- package/dist/src/onboard/toml.js +53 -0
- package/dist/src/onboard/toml.js.map +1 -0
- package/dist/src/opencli.js +508 -0
- package/dist/src/opencli.js.map +1 -0
- package/dist/src/openpr.js +457 -0
- package/dist/src/openpr.js.map +1 -0
- package/dist/src/openviking/client.js +821 -0
- package/dist/src/openviking/client.js.map +1 -0
- package/dist/src/openviking/context.js +218 -0
- package/dist/src/openviking/context.js.map +1 -0
- package/dist/src/openviking/embedder.js +457 -0
- package/dist/src/openviking/embedder.js.map +1 -0
- package/dist/src/openviking/gapsink.js +38 -0
- package/dist/src/openviking/gapsink.js.map +1 -0
- package/dist/src/openviking/health.js +69 -0
- package/dist/src/openviking/health.js.map +1 -0
- package/dist/src/openviking/index.js +24 -0
- package/dist/src/openviking/index.js.map +1 -0
- package/dist/src/openviking/lessonfind.js +54 -0
- package/dist/src/openviking/lessonfind.js.map +1 -0
- package/dist/src/openviking/probe.js +89 -0
- package/dist/src/openviking/probe.js.map +1 -0
- package/dist/src/openviking/proc.js +95 -0
- package/dist/src/openviking/proc.js.map +1 -0
- package/dist/src/openviking/provisionreport.js +144 -0
- package/dist/src/openviking/provisionreport.js.map +1 -0
- package/dist/src/openviking/reembed.js +317 -0
- package/dist/src/openviking/reembed.js.map +1 -0
- package/dist/src/openviking/relayout.js +153 -0
- package/dist/src/openviking/relayout.js.map +1 -0
- package/dist/src/openviking/sidecar-main.js +39 -0
- package/dist/src/openviking/sidecar-main.js.map +1 -0
- package/dist/src/openviking/sidecar.js +79 -0
- package/dist/src/openviking/sidecar.js.map +1 -0
- package/dist/src/openviking/store.js +1758 -0
- package/dist/src/openviking/store.js.map +1 -0
- package/dist/src/openviking/supervisor.js +422 -0
- package/dist/src/openviking/supervisor.js.map +1 -0
- package/dist/src/openviking/types.js +5 -0
- package/dist/src/openviking/types.js.map +1 -0
- package/dist/src/operator-recovery.js +172 -0
- package/dist/src/operator-recovery.js.map +1 -0
- package/dist/src/operatormessage.js +44 -0
- package/dist/src/operatormessage.js.map +1 -0
- package/dist/src/outcomes.js +1361 -0
- package/dist/src/outcomes.js.map +1 -0
- package/dist/src/piauthcopy.js +5 -0
- package/dist/src/piauthcopy.js.map +1 -0
- package/dist/src/picli.js +45 -0
- package/dist/src/picli.js.map +1 -0
- package/dist/src/picwd.js +25 -0
- package/dist/src/picwd.js.map +1 -0
- package/dist/src/pilogin.js +35 -0
- package/dist/src/pilogin.js.map +1 -0
- package/dist/src/pingpong.js +1028 -0
- package/dist/src/pingpong.js.map +1 -0
- package/dist/src/posting-journal.js +441 -0
- package/dist/src/posting-journal.js.map +1 -0
- package/dist/src/posting-outbox.js +662 -0
- package/dist/src/posting-outbox.js.map +1 -0
- package/dist/src/posting-receipt.js +203 -0
- package/dist/src/posting-receipt.js.map +1 -0
- package/dist/src/posting.js +348 -0
- package/dist/src/posting.js.map +1 -0
- package/dist/src/postops.js +637 -0
- package/dist/src/postops.js.map +1 -0
- package/dist/src/pr-activity.js +11 -0
- package/dist/src/pr-activity.js.map +1 -0
- package/dist/src/pr-author.js +5 -0
- package/dist/src/pr-author.js.map +1 -0
- package/dist/src/pr-conversation.js +355 -0
- package/dist/src/pr-conversation.js.map +1 -0
- package/dist/src/pr-session-lifecycle.js +542 -0
- package/dist/src/pr-session-lifecycle.js.map +1 -0
- package/dist/src/preflight.js +341 -0
- package/dist/src/preflight.js.map +1 -0
- package/dist/src/priorresolve.js +275 -0
- package/dist/src/priorresolve.js.map +1 -0
- package/dist/src/qcrbin.js +180 -0
- package/dist/src/qcrbin.js.map +1 -0
- package/dist/src/recheck.js +1398 -0
- package/dist/src/recheck.js.map +1 -0
- package/dist/src/reconcile.js +820 -0
- package/dist/src/reconcile.js.map +1 -0
- package/dist/src/recordedfindings.js +15 -0
- package/dist/src/recordedfindings.js.map +1 -0
- package/dist/src/regression.js +546 -0
- package/dist/src/regression.js.map +1 -0
- package/dist/src/replay.js +598 -0
- package/dist/src/replay.js.map +1 -0
- package/dist/src/requiredpasses.js +113 -0
- package/dist/src/requiredpasses.js.map +1 -0
- package/dist/src/resumecheck.js +168 -0
- package/dist/src/resumecheck.js.map +1 -0
- package/dist/src/reviewcli.js +671 -0
- package/dist/src/reviewcli.js.map +1 -0
- package/dist/src/reviewcontext.js +22 -0
- package/dist/src/reviewcontext.js.map +1 -0
- package/dist/src/reviewdetach.js +574 -0
- package/dist/src/reviewdetach.js.map +1 -0
- package/dist/src/reviewhistory.js +284 -0
- package/dist/src/reviewhistory.js.map +1 -0
- package/dist/src/reviewhome.js +26 -0
- package/dist/src/reviewhome.js.map +1 -0
- package/dist/src/reviewmemory.js +110 -0
- package/dist/src/reviewmemory.js.map +1 -0
- package/dist/src/reviewrendercli.js +104 -0
- package/dist/src/reviewrendercli.js.map +1 -0
- package/dist/src/round-work.js +20 -0
- package/dist/src/round-work.js.map +1 -0
- package/dist/src/runhost.js +72 -0
- package/dist/src/runhost.js.map +1 -0
- package/dist/src/scanauth.js +25 -0
- package/dist/src/scanauth.js.map +1 -0
- package/dist/src/scanbox.js +846 -0
- package/dist/src/scanbox.js.map +1 -0
- package/dist/src/scancli.js +1074 -0
- package/dist/src/scancli.js.map +1 -0
- package/dist/src/scandriverlog.js +30 -0
- package/dist/src/scandriverlog.js.map +1 -0
- package/dist/src/session-bridge.js +75 -0
- package/dist/src/session-bridge.js.map +1 -0
- package/dist/src/session-command-payload.js +29 -0
- package/dist/src/session-command-payload.js.map +1 -0
- package/dist/src/session-command-supervisor.js +939 -0
- package/dist/src/session-command-supervisor.js.map +1 -0
- package/dist/src/sessionjsonl.js +85 -0
- package/dist/src/sessionjsonl.js.map +1 -0
- package/dist/src/sessions.js +541 -0
- package/dist/src/sessions.js.map +1 -0
- package/dist/src/setup/adapters.js +330 -0
- package/dist/src/setup/adapters.js.map +1 -0
- package/dist/src/setup/baselines.js +64 -0
- package/dist/src/setup/baselines.js.map +1 -0
- package/dist/src/setup/caserunner.js +554 -0
- package/dist/src/setup/caserunner.js.map +1 -0
- package/dist/src/setup/configcommit.js +358 -0
- package/dist/src/setup/configcommit.js.map +1 -0
- package/dist/src/setup/engine.js +672 -0
- package/dist/src/setup/engine.js.map +1 -0
- package/dist/src/setup/facts.js +593 -0
- package/dist/src/setup/facts.js.map +1 -0
- package/dist/src/setup/findings.js +948 -0
- package/dist/src/setup/findings.js.map +1 -0
- package/dist/src/setup/history.js +338 -0
- package/dist/src/setup/history.js.map +1 -0
- package/dist/src/setup/interview.js +185 -0
- package/dist/src/setup/interview.js.map +1 -0
- package/dist/src/setup/ledger.js +207 -0
- package/dist/src/setup/ledger.js.map +1 -0
- package/dist/src/setup/pins.js +43 -0
- package/dist/src/setup/pins.js.map +1 -0
- package/dist/src/setup/portrait.js +432 -0
- package/dist/src/setup/portrait.js.map +1 -0
- package/dist/src/setup/presentation.js +9 -0
- package/dist/src/setup/presentation.js.map +1 -0
- package/dist/src/setup/profile.js +417 -0
- package/dist/src/setup/profile.js.map +1 -0
- package/dist/src/setup/replay.js +240 -0
- package/dist/src/setup/replay.js.map +1 -0
- package/dist/src/setup/replaybuild.js +311 -0
- package/dist/src/setup/replaybuild.js.map +1 -0
- package/dist/src/setup/replayrun.js +806 -0
- package/dist/src/setup/replayrun.js.map +1 -0
- package/dist/src/setup/replaysource.js +169 -0
- package/dist/src/setup/replaysource.js.map +1 -0
- package/dist/src/setup/retune.js +333 -0
- package/dist/src/setup/retune.js.map +1 -0
- package/dist/src/setup/retunelog.js +51 -0
- package/dist/src/setup/retunelog.js.map +1 -0
- package/dist/src/setup/scan.js +725 -0
- package/dist/src/setup/scan.js.map +1 -0
- package/dist/src/setup/scanbuild.js +133 -0
- package/dist/src/setup/scanbuild.js.map +1 -0
- package/dist/src/setup/scanfacts.js +96 -0
- package/dist/src/setup/scanfacts.js.map +1 -0
- package/dist/src/setup/scanlease.js +251 -0
- package/dist/src/setup/scanlease.js.map +1 -0
- package/dist/src/setup/scanreport.js +289 -0
- package/dist/src/setup/scanreport.js.map +1 -0
- package/dist/src/setup/scanrun.js +1733 -0
- package/dist/src/setup/scanrun.js.map +1 -0
- package/dist/src/setup/scantree.js +257 -0
- package/dist/src/setup/scantree.js.map +1 -0
- package/dist/src/setup/stage2.js +617 -0
- package/dist/src/setup/stage2.js.map +1 -0
- package/dist/src/setup/stage2request.js +259 -0
- package/dist/src/setup/stage2request.js.map +1 -0
- package/dist/src/setup/survey.js +307 -0
- package/dist/src/setup/survey.js.map +1 -0
- package/dist/src/setup/surveyrun.js +549 -0
- package/dist/src/setup/surveyrun.js.map +1 -0
- package/dist/src/setup/textsniff.js +89 -0
- package/dist/src/setup/textsniff.js.map +1 -0
- package/dist/src/setup/trackersink.js +395 -0
- package/dist/src/setup/trackersink.js.map +1 -0
- package/dist/src/setup/triage.js +636 -0
- package/dist/src/setup/triage.js.map +1 -0
- package/dist/src/setup/tuning.js +585 -0
- package/dist/src/setup/tuning.js.map +1 -0
- package/dist/src/setup/unitcut.js +437 -0
- package/dist/src/setup/unitcut.js.map +1 -0
- package/dist/src/singleflight.js +72 -0
- package/dist/src/singleflight.js.map +1 -0
- package/dist/src/slack/announce.js +725 -0
- package/dist/src/slack/announce.js.map +1 -0
- package/dist/src/slack/api.js +227 -0
- package/dist/src/slack/api.js.map +1 -0
- package/dist/src/slack/messages.js +158 -0
- package/dist/src/slack/messages.js.map +1 -0
- package/dist/src/slack/origin.js +550 -0
- package/dist/src/slack/origin.js.map +1 -0
- package/dist/src/slack/policy.js +225 -0
- package/dist/src/slack/policy.js.map +1 -0
- package/dist/src/slack/reply.js +153 -0
- package/dist/src/slack/reply.js.map +1 -0
- package/dist/src/slack/router.js +189 -0
- package/dist/src/slack/router.js.map +1 -0
- package/dist/src/slack/socket.js +323 -0
- package/dist/src/slack/socket.js.map +1 -0
- package/dist/src/slack/types.js +11 -0
- package/dist/src/slack/types.js.map +1 -0
- package/dist/src/sqlitehint.js +30 -0
- package/dist/src/sqlitehint.js.map +1 -0
- package/dist/src/ssh-observation.js +25 -0
- package/dist/src/ssh-observation.js.map +1 -0
- package/dist/src/state.js +5869 -0
- package/dist/src/state.js.map +1 -0
- package/dist/src/statemachine.js +220 -0
- package/dist/src/statemachine.js.map +1 -0
- package/dist/src/stubs.js +52 -0
- package/dist/src/stubs.js.map +1 -0
- package/dist/src/takeover.js +506 -0
- package/dist/src/takeover.js.map +1 -0
- package/dist/src/takeoverpi.js +249 -0
- package/dist/src/takeoverpi.js.map +1 -0
- package/dist/src/terminal-failure.js +26 -0
- package/dist/src/terminal-failure.js.map +1 -0
- package/dist/src/thread-resolution-refusal.js +4 -0
- package/dist/src/thread-resolution-refusal.js.map +1 -0
- package/dist/src/tmux.js +126 -0
- package/dist/src/tmux.js.map +1 -0
- package/dist/src/trackerid.js +43 -0
- package/dist/src/trackerid.js.map +1 -0
- package/dist/src/transcriptindex.js +512 -0
- package/dist/src/transcriptindex.js.map +1 -0
- package/dist/src/tui/actions.js +404 -0
- package/dist/src/tui/actions.js.map +1 -0
- package/dist/src/tui/alerts.js +523 -0
- package/dist/src/tui/alerts.js.map +1 -0
- package/dist/src/tui/app.js +1835 -0
- package/dist/src/tui/app.js.map +1 -0
- package/dist/src/tui/archiveops.js +164 -0
- package/dist/src/tui/archiveops.js.map +1 -0
- package/dist/src/tui/autofile.js +191 -0
- package/dist/src/tui/autofile.js.map +1 -0
- package/dist/src/tui/board.js +1143 -0
- package/dist/src/tui/board.js.map +1 -0
- package/dist/src/tui/candidates.js +699 -0
- package/dist/src/tui/candidates.js.map +1 -0
- package/dist/src/tui/cellwidth.js +151 -0
- package/dist/src/tui/cellwidth.js.map +1 -0
- package/dist/src/tui/changelog-store.js +404 -0
- package/dist/src/tui/changelog-store.js.map +1 -0
- package/dist/src/tui/chord.js +26 -0
- package/dist/src/tui/chord.js.map +1 -0
- package/dist/src/tui/clipboard.js +81 -0
- package/dist/src/tui/clipboard.js.map +1 -0
- package/dist/src/tui/cmdbar.js +253 -0
- package/dist/src/tui/cmdbar.js.map +1 -0
- package/dist/src/tui/companion.js +1275 -0
- package/dist/src/tui/companion.js.map +1 -0
- package/dist/src/tui/companionplan.js +200 -0
- package/dist/src/tui/companionplan.js.map +1 -0
- package/dist/src/tui/companiontab.js +61 -0
- package/dist/src/tui/companiontab.js.map +1 -0
- package/dist/src/tui/confighome.js +105 -0
- package/dist/src/tui/confighome.js.map +1 -0
- package/dist/src/tui/connect.js +44 -0
- package/dist/src/tui/connect.js.map +1 -0
- package/dist/src/tui/deploy-ceremony.js +75 -0
- package/dist/src/tui/deploy-ceremony.js.map +1 -0
- package/dist/src/tui/deploy-drain.js +93 -0
- package/dist/src/tui/deploy-drain.js.map +1 -0
- package/dist/src/tui/deploy-process.js +234 -0
- package/dist/src/tui/deploy-process.js.map +1 -0
- package/dist/src/tui/deploytab.js +1263 -0
- package/dist/src/tui/deploytab.js.map +1 -0
- package/dist/src/tui/design.js +111 -0
- package/dist/src/tui/design.js.map +1 -0
- package/dist/src/tui/digesttab.js +148 -0
- package/dist/src/tui/digesttab.js.map +1 -0
- package/dist/src/tui/emptystate.js +32 -0
- package/dist/src/tui/emptystate.js.map +1 -0
- package/dist/src/tui/evidence.js +790 -0
- package/dist/src/tui/evidence.js.map +1 -0
- package/dist/src/tui/feed.js +415 -0
- package/dist/src/tui/feed.js.map +1 -0
- package/dist/src/tui/feedbacktab.js +907 -0
- package/dist/src/tui/feedbacktab.js.map +1 -0
- package/dist/src/tui/filters.js +72 -0
- package/dist/src/tui/filters.js.map +1 -0
- package/dist/src/tui/ghread.js +272 -0
- package/dist/src/tui/ghread.js.map +1 -0
- package/dist/src/tui/historypull.js +61 -0
- package/dist/src/tui/historypull.js.map +1 -0
- package/dist/src/tui/inboxpull.js +197 -0
- package/dist/src/tui/inboxpull.js.map +1 -0
- package/dist/src/tui/index.js +1425 -0
- package/dist/src/tui/index.js.map +1 -0
- package/dist/src/tui/issuesink.js +206 -0
- package/dist/src/tui/issuesink.js.map +1 -0
- package/dist/src/tui/jsonfile.js +32 -0
- package/dist/src/tui/jsonfile.js.map +1 -0
- package/dist/src/tui/keys.js +97 -0
- package/dist/src/tui/keys.js.map +1 -0
- package/dist/src/tui/layout.js +70 -0
- package/dist/src/tui/layout.js.map +1 -0
- package/dist/src/tui/learning.js +469 -0
- package/dist/src/tui/learning.js.map +1 -0
- package/dist/src/tui/lessonswitch.js +103 -0
- package/dist/src/tui/lessonswitch.js.map +1 -0
- package/dist/src/tui/livecontrol.js +167 -0
- package/dist/src/tui/livecontrol.js.map +1 -0
- package/dist/src/tui/memforward.js +310 -0
- package/dist/src/tui/memforward.js.map +1 -0
- package/dist/src/tui/memincident.js +287 -0
- package/dist/src/tui/memincident.js.map +1 -0
- package/dist/src/tui/memory.js +485 -0
- package/dist/src/tui/memory.js.map +1 -0
- package/dist/src/tui/memtab.js +513 -0
- package/dist/src/tui/memtab.js.map +1 -0
- package/dist/src/tui/metricstab.js +979 -0
- package/dist/src/tui/metricstab.js.map +1 -0
- package/dist/src/tui/model.js +104 -0
- package/dist/src/tui/model.js.map +1 -0
- package/dist/src/tui/mouse.js +83 -0
- package/dist/src/tui/mouse.js.map +1 -0
- package/dist/src/tui/msg.js +196 -0
- package/dist/src/tui/msg.js.map +1 -0
- package/dist/src/tui/nativecompanion.js +321 -0
- package/dist/src/tui/nativecompanion.js.map +1 -0
- package/dist/src/tui/openurl.js +17 -0
- package/dist/src/tui/openurl.js.map +1 -0
- package/dist/src/tui/paint.js +501 -0
- package/dist/src/tui/paint.js.map +1 -0
- package/dist/src/tui/panes.js +74 -0
- package/dist/src/tui/panes.js.map +1 -0
- package/dist/src/tui/pipane.js +166 -0
- package/dist/src/tui/pipane.js.map +1 -0
- package/dist/src/tui/poll-hooks.js +11 -0
- package/dist/src/tui/poll-hooks.js.map +1 -0
- package/dist/src/tui/poll.js +113 -0
- package/dist/src/tui/poll.js.map +1 -0
- package/dist/src/tui/proposals.js +715 -0
- package/dist/src/tui/proposals.js.map +1 -0
- package/dist/src/tui/prpause.js +132 -0
- package/dist/src/tui/prpause.js.map +1 -0
- package/dist/src/tui/reload.js +351 -0
- package/dist/src/tui/reload.js.map +1 -0
- package/dist/src/tui/remote.js +312 -0
- package/dist/src/tui/remote.js.map +1 -0
- package/dist/src/tui/rollover.js +767 -0
- package/dist/src/tui/rollover.js.map +1 -0
- package/dist/src/tui/scantab.js +1053 -0
- package/dist/src/tui/scantab.js.map +1 -0
- package/dist/src/tui/setupentry.js +81 -0
- package/dist/src/tui/setupentry.js.map +1 -0
- package/dist/src/tui/sidebar.js +602 -0
- package/dist/src/tui/sidebar.js.map +1 -0
- package/dist/src/tui/sshtriage.js +393 -0
- package/dist/src/tui/sshtriage.js.map +1 -0
- package/dist/src/tui/startup.js +172 -0
- package/dist/src/tui/startup.js.map +1 -0
- package/dist/src/tui/tabs.js +41 -0
- package/dist/src/tui/tabs.js.map +1 -0
- package/dist/src/tui/termmirror.js +217 -0
- package/dist/src/tui/termmirror.js.map +1 -0
- package/dist/src/tui/theme.js +95 -0
- package/dist/src/tui/theme.js.map +1 -0
- package/dist/src/tui/ticketstab.js +224 -0
- package/dist/src/tui/ticketstab.js.map +1 -0
- package/dist/src/tui/tmuxsocket.js +101 -0
- package/dist/src/tui/tmuxsocket.js.map +1 -0
- package/dist/src/tui/trackertickets.js +77 -0
- package/dist/src/tui/trackertickets.js.map +1 -0
- package/dist/src/tui/unwedge.js +245 -0
- package/dist/src/tui/unwedge.js.map +1 -0
- package/dist/src/tui/viewer.js +479 -0
- package/dist/src/tui/viewer.js.map +1 -0
- package/dist/src/tui/viewerinput.js +125 -0
- package/dist/src/tui/viewerinput.js.map +1 -0
- package/dist/src/updatenotice.js +143 -0
- package/dist/src/updatenotice.js.map +1 -0
- package/dist/src/usage.js +1007 -0
- package/dist/src/usage.js.map +1 -0
- package/dist/src/verdicts.js +3848 -0
- package/dist/src/verdicts.js.map +1 -0
- package/dist/src/version.js +58 -0
- package/dist/src/version.js.map +1 -0
- package/dist/src/wakes.js +411 -0
- package/dist/src/wakes.js.map +1 -0
- package/dist/src/webhook.js +725 -0
- package/dist/src/webhook.js.map +1 -0
- package/dist/src/webhookheal.js +238 -0
- package/dist/src/webhookheal.js.map +1 -0
- package/dist/src/wire.js +1801 -0
- package/dist/src/wire.js.map +1 -0
- package/dist/src/worker-session-host.js +531 -0
- package/dist/src/worker-session-host.js.map +1 -0
- package/dist/src/workerbreaker.js +112 -0
- package/dist/src/workerbreaker.js.map +1 -0
- package/dist/src/workercontrol.js +49 -0
- package/dist/src/workercontrol.js.map +1 -0
- package/dist/src/workerlease.js +351 -0
- package/dist/src/workerlease.js.map +1 -0
- package/dist/src/workerliveness.js +84 -0
- package/dist/src/workerliveness.js.map +1 -0
- package/dist/src/workerlog.js +65 -0
- package/dist/src/workerlog.js.map +1 -0
- package/dist/src/workerruntime.js +10 -0
- package/dist/src/workerruntime.js.map +1 -0
- package/dist/src/workersupervisor.js +1206 -0
- package/dist/src/workersupervisor.js.map +1 -0
- package/dist/src/workerusage.js +23 -0
- package/dist/src/workerusage.js.map +1 -0
- package/dist/src/workerworld.js +332 -0
- package/dist/src/workerworld.js.map +1 -0
- package/dist/src/worktrees.js +984 -0
- package/dist/src/worktrees.js.map +1 -0
- package/docker/Dockerfile +284 -0
- package/docker/Dockerfile.dockerignore +35 -0
- package/docker/README.md +221 -0
- package/docker/companion/Dockerfile +139 -0
- package/docker/companion/agents/evidence.md +34 -0
- package/docker/companion/agents/explore.md +26 -0
- package/docker/companion/agents/pass-survey.md +49 -0
- package/docker/companion/agents/study.md +26 -0
- package/docker/companion/entrypoint.sh +343 -0
- package/docker/companion/memory-health.mjs +269 -0
- package/docker/entrypoint.sh +782 -0
- package/docker/memory/Dockerfile +191 -0
- package/docker/memory/Dockerfile.dockerignore +20 -0
- package/docker/memory/README.md +60 -0
- package/docker/memory/entrypoint.sh +163 -0
- package/docker/setid/qcr-setid.c +109 -0
- package/docker/tunnel/Dockerfile.cloudflared +51 -0
- package/docker/tunnel/Dockerfile.ngrok +40 -0
- package/docker/tunnel/sidecar.mjs +411 -0
- package/docker/vm/tmux.conf +26 -0
- package/docker-compose.yml +348 -0
- package/extensions/cli/bash-guard-ambient.ts +71 -0
- package/extensions/cli/bash-guard.ts +12 -0
- package/extensions/cli/companion-guard.ts +8 -0
- package/extensions/cli/debug-round.ts +9 -0
- package/extensions/cli/inject-bridge.ts +8 -0
- package/extensions/cli/ledger-pin.ts +13 -0
- package/extensions/cli/lens.ts +22 -0
- package/extensions/cli/muted-theme.ts +9 -0
- package/extensions/cli/onboarding.ts +8 -0
- package/extensions/cli/setup.ts +8 -0
- package/extensions/cli/tui-view.ts +8 -0
- package/extensions/companion-guard.ts +411 -0
- package/extensions/debug-round.ts +281 -0
- package/extensions/inject-bridge.ts +630 -0
- package/extensions/inline-bash.ts +102 -0
- package/extensions/muted-theme.json +68 -0
- package/extensions/muted-theme.ts +23 -0
- package/extensions/onboarding.ts +233 -0
- package/extensions/setup-replay.ts +336 -0
- package/extensions/setup-scan.ts +1236 -0
- package/extensions/setup-triage.ts +190 -0
- package/extensions/setup.ts +1428 -0
- package/extensions/tui-view.ts +489 -0
- package/extensions/vendor/openviking-pi/LICENSE +201 -0
- package/extensions/vendor/openviking-pi/NOTICE +68 -0
- package/extensions/vendor/openviking-pi/README.md +550 -0
- package/extensions/vendor/openviking-pi/client.ts +588 -0
- package/extensions/vendor/openviking-pi/config.json +28 -0
- package/extensions/vendor/openviking-pi/config.ts +271 -0
- package/extensions/vendor/openviking-pi/index.ts +523 -0
- package/extensions/vendor/openviking-pi/lib/capture-adapter.d.mts +10 -0
- package/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +88 -0
- package/extensions/vendor/openviking-pi/lib/takeover-core.d.mts +80 -0
- package/extensions/vendor/openviking-pi/lib/takeover-core.mjs +406 -0
- package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.d.mts +1 -0
- package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +42 -0
- package/extensions/vendor/openviking-pi/patches/0000-vendoring-and-build-conformance.patch +726 -0
- package/extensions/vendor/openviking-pi/patches/0001-remove-session-before-compact-handler.patch +101 -0
- package/extensions/vendor/openviking-pi/patches/0002-no-ambient-work-in-subagent-leaves.patch +202 -0
- package/extensions/vendor/openviking-pi/patches/0003-never-auto-create-openviking-sessions.patch +120 -0
- package/extensions/vendor/openviking-pi/patches/0004-viking-write-durable-batch-write.patch +521 -0
- package/extensions/vendor/openviking-pi/patches/0005-second-memory-namespace.patch +786 -0
- package/extensions/vendor/openviking-pi/patches/0006-writes-that-stick-and-honest-errors.patch +996 -0
- package/extensions/vendor/openviking-pi/patches/0007-bounded-recall-no-commit.patch +794 -0
- package/extensions/vendor/openviking-pi/patches/0008-pr-derived-recall.patch +379 -0
- package/extensions/vendor/openviking-pi/patches/0009-pr-session-tools-in-round-areas.patch +375 -0
- package/extensions/vendor/openviking-pi/patches/0010-typed-lessons.patch +448 -0
- package/extensions/vendor/openviking-pi/patches/0011-review-session-memory-boundary.patch +382 -0
- package/extensions/vendor/openviking-pi/patches/0012-typecheck-against-the-real-pi.patch +129 -0
- package/extensions/vendor/openviking-pi/patches/0013-createopenviking-factory.patch +1168 -0
- package/extensions/vendor/openviking-pi/qcr-hint.ts +313 -0
- package/extensions/vendor/openviking-pi/qcr-honest.ts +183 -0
- package/extensions/vendor/openviking-pi/qcr-hooks.ts +87 -0
- package/extensions/vendor/openviking-pi/qcr-leaf.ts +78 -0
- package/extensions/vendor/openviking-pi/qcr-lesson.ts +231 -0
- package/extensions/vendor/openviking-pi/qcr-namespaces.ts +198 -0
- package/extensions/vendor/openviking-pi/qcr-recall.ts +288 -0
- package/extensions/vendor/openviking-pi/qcr-shim.ts +142 -0
- package/extensions/vendor/openviking-pi/qcr-write.ts +427 -0
- package/extensions/vendor/openviking-pi/recall.ts +149 -0
- package/extensions/vendor/openviking-pi/shared/capture-utils.d.mts +9 -0
- package/extensions/vendor/openviking-pi/shared/capture-utils.mjs +515 -0
- package/extensions/vendor/openviking-pi/shared/credentials.d.mts +14 -0
- package/extensions/vendor/openviking-pi/shared/credentials.mjs +228 -0
- package/extensions/vendor/openviking-pi/shared/pending-queue.d.mts +19 -0
- package/extensions/vendor/openviking-pi/shared/pending-queue.mjs +458 -0
- package/extensions/vendor/openviking-pi/shared/profile-inject.d.mts +17 -0
- package/extensions/vendor/openviking-pi/shared/profile-inject.mjs +294 -0
- package/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +195 -0
- package/extensions/vendor/openviking-pi/shared/recall-core.d.mts +13 -0
- package/extensions/vendor/openviking-pi/shared/recall-core.mjs +609 -0
- package/extensions/vendor/openviking-pi/shared/retryable.mjs +9 -0
- package/extensions/vendor/openviking-pi/shared/session-model.d.mts +3 -0
- package/extensions/vendor/openviking-pi/shared/session-model.mjs +57 -0
- package/extensions/vendor/openviking-pi/shared/uri-guard.mjs +56 -0
- package/extensions/vendor/openviking-pi/shared/workspace-peer.d.mts +5 -0
- package/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
- package/extensions/vendor/openviking-pi/sync.ts +163 -0
- package/extensions/vendor/openviking-pi/takeover.ts +36 -0
- package/extensions/vendor/openviking-pi/tools.ts +479 -0
- package/extensions/vendor/openviking-pi/upstream.json +48 -0
- package/package.json +138 -4
- package/profiles/default/README.md +21 -0
- package/profiles/default/addenda/.gitkeep +0 -0
- package/profiles/default/profile.toml +17 -0
- package/profiles/example-django/README.md +21 -0
- package/profiles/example-django/addenda/10-migration-discipline.md +13 -0
- package/profiles/example-django/addenda/20-new-column-nullability.md +12 -0
- package/profiles/example-django/addenda/30-test-doctrine.md +12 -0
- package/profiles/example-django/addenda/40-orm-usage.md +19 -0
- package/profiles/example-django/addenda/50-declared-shape.md +10 -0
- package/profiles/example-django/addenda/60-config-placement.md +14 -0
- package/profiles/example-django/heuristics.toml +24 -0
- package/profiles/example-django/profile.toml +9 -0
- package/profiles/fast/README.md +24 -0
- package/profiles/fast/profile.toml +59 -0
- package/profiles/minimal/README.md +17 -0
- package/profiles/minimal/addenda/.gitkeep +0 -0
- package/profiles/minimal/profile.toml +8 -0
- package/profiles/snappy/README.md +24 -0
- package/profiles/snappy/addenda/10-race-as-question.md +6 -0
- package/profiles/snappy/profile.toml +46 -0
- package/prompts/companion-day-open.md +46 -0
- package/prompts/companion-first-run.md +104 -0
- package/prompts/companion.md +821 -0
- package/prompts/conversation-system.md +117 -0
- package/prompts/eod-sweep.md +120 -0
- package/prompts/onboarding.md +74 -0
- package/prompts/pass-common.md +52 -0
- package/prompts/pass-quality-common.md +22 -0
- package/prompts/round-first.md +874 -0
- package/prompts/round-follow-up.md +991 -0
- package/prompts/round-re-review.md +889 -0
- package/prompts/round-recheck.md +196 -0
- package/prompts/round-staleness.md +856 -0
- package/schemas/review-result.v1.json +391 -0
- package/schemas/scan-result.v1.json +421 -0
- package/schemas/version.v1.json +57 -0
- package/scripts/approve-pr.sh +16 -0
- package/scripts/build-stamp.mjs +67 -0
- package/scripts/commit-verdict.sh +155 -0
- package/scripts/companion-e2e.sh +250 -0
- package/scripts/companion-memory-forward.mjs +1966 -0
- package/scripts/config-fingerprint.sh +28 -0
- package/scripts/deploy-manifest.mjs +141 -0
- package/scripts/deploy.sh +1435 -0
- package/scripts/directory-lock.mjs +193 -0
- package/scripts/heartbeat-fresh.mjs +54 -0
- package/scripts/install-locator.sh +328 -0
- package/scripts/install-wrapper.sh +76 -0
- package/scripts/memory-search-check.mjs +98 -0
- package/scripts/mint-token.sh +212 -0
- package/scripts/openviking-embedder.py +392 -0
- package/scripts/openviking-ov-conf.mjs +161 -0
- package/scripts/openviking-provision.mjs +1122 -0
- package/scripts/openviking-reembed.mjs +1154 -0
- package/scripts/post-review.sh +16 -0
- package/scripts/pr-checks.sh +256 -0
- package/scripts/pr-context.py +174 -0
- package/scripts/pr-context.sh +63 -0
- package/scripts/pr-self.sh +144 -0
- package/scripts/pr-threads.sh +145 -0
- package/scripts/qcr +15 -0
- package/scripts/qcr-host.sh +769 -0
- package/scripts/render-review.sh +24 -0
- package/scripts/reply-thread.sh +16 -0
- package/scripts/required-passes.py +112 -0
- package/scripts/smoke-companion.sh +211 -0
- package/scripts/tui-deploy-runner.mjs +61 -0
- package/scripts/verify-dist.mjs +201 -0
- package/scripts/vm-ghcr-login.sh +188 -0
- package/seeds/agents/pass-baseline.md +61 -0
- package/seeds/agents/pass-contracts.md +30 -0
- package/seeds/agents/pass-data-safety.md +54 -0
- package/seeds/agents/pass-delivery.md +37 -0
- package/seeds/agents/pass-prod-risk.md +24 -0
- package/seeds/agents/pass-security.md +31 -0
- package/seeds/agents/pass-statelessness.md +27 -0
- package/seeds/agents/pass-ui.md +23 -0
- package/seeds/agents/pass-validate.md +50 -0
- package/seeds/prompts/conversation-system.md +117 -0
- package/seeds/prompts/eod-sweep.md +120 -0
- package/seeds/prompts/pass-common.md +52 -0
- package/skills/companion/adjudicate/SKILL.md +247 -0
- package/skills/companion/evidence/SKILL.md +150 -0
- package/skills/companion/land/SKILL.md +200 -0
- package/skills/companion/replay/SKILL.md +138 -0
- package/skills/companion/setup/SKILL.md +167 -0
- package/skills/companion/survey/SKILL.md +160 -0
- package/skills/selfimprove/README.md +34 -0
- package/skills/selfimprove/SKILL.md +127 -0
- package/skills/selfimprove/agents/openai.yaml +2 -0
- package/skills/selfimprove/commands/selfimprove.md +6 -0
- package/skills/selfimprove/references/fix-loop.md +49 -0
- package/skills/selfimprove/references/ledger.md +20 -0
- package/skills/selfimprove/references/setup.md +40 -0
- package/src/anomalies.ts +566 -0
- package/src/answerworker.ts +251 -0
- package/src/appstamp.ts +65 -0
- package/src/archive/admission.ts +3 -0
- package/src/archive/archivedb.ts +548 -0
- package/src/archive/archiver.ts +1963 -0
- package/src/archive/archivewrite.ts +279 -0
- package/src/archive/bundle.ts +333 -0
- package/src/archive/dbmove.ts +911 -0
- package/src/archive/duplicatecounts.ts +98 -0
- package/src/archive/eligibility.ts +181 -0
- package/src/archive/extract.ts +342 -0
- package/src/archive/fsutil.ts +415 -0
- package/src/archive/gzfile.ts +370 -0
- package/src/archive/kinds.ts +21 -0
- package/src/archive/livecheckpoint.ts +217 -0
- package/src/archive/liveretention.ts +180 -0
- package/src/archive/manifest.ts +519 -0
- package/src/archive/overseer.ts +336 -0
- package/src/archive/piglobal.ts +174 -0
- package/src/archive/plan.ts +612 -0
- package/src/archive/prarchive.ts +984 -0
- package/src/archive/prfiles.ts +406 -0
- package/src/archive/report.ts +252 -0
- package/src/archive/restore.ts +330 -0
- package/src/archive/rows.ts +187 -0
- package/src/archive/seal.ts +630 -0
- package/src/archive/status.ts +719 -0
- package/src/archive/storepass.ts +581 -0
- package/src/backfill.ts +362 -0
- package/src/bench/cost.ts +113 -0
- package/src/bench/exec.ts +90 -0
- package/src/bench/values.ts +13 -0
- package/src/botcontrol.ts +176 -0
- package/src/botworker.ts +953 -0
- package/src/capture.ts +321 -0
- package/src/cases.ts +1789 -0
- package/src/channelfs.ts +26 -0
- package/src/checklist.ts +506 -0
- package/src/ciretry.ts +141 -0
- package/src/cli.ts +2271 -0
- package/src/cliops.ts +384 -0
- package/src/clonedir.ts +15 -0
- package/src/commandeffects.ts +57 -0
- package/src/commands.ts +1678 -0
- package/src/companioninbox.ts +239 -0
- package/src/compare.ts +957 -0
- package/src/config.ts +2050 -0
- package/src/configcheck.ts +133 -0
- package/src/coreext.ts +47 -0
- package/src/daemon.ts +1302 -0
- package/src/debuground.ts +927 -0
- package/src/debugroundpi.ts +262 -0
- package/src/diagnostics.ts +14 -0
- package/src/digest.ts +183 -0
- package/src/dispatch.ts +2250 -0
- package/src/engine/answerguard.ts +27 -0
- package/src/engine/botsdk.ts +336 -0
- package/src/engine/caserunner-sdk.ts +423 -0
- package/src/engine/debugpi.ts +235 -0
- package/src/eod.ts +669 -0
- package/src/eodpinwatch.ts +556 -0
- package/src/escalation.ts +1253 -0
- package/src/eventloop.ts +1045 -0
- package/src/execboundary.ts +633 -0
- package/src/feedback.ts +1162 -0
- package/src/findingscheck.ts +298 -0
- package/src/fsdenied.ts +24 -0
- package/src/ghread.ts +242 -0
- package/src/github.ts +1497 -0
- package/src/homes.ts +175 -0
- package/src/hostroute.ts +53 -0
- package/src/improvements.ts +787 -0
- package/src/install.ts +1103 -0
- package/src/installkind.ts +50 -0
- package/src/ipc/capability.ts +333 -0
- package/src/ipc/client.ts +269 -0
- package/src/ipc/control.ts +90 -0
- package/src/ipc/ghreadshim.ts +94 -0
- package/src/ipc/launcher.ts +65 -0
- package/src/ipc/permits.ts +362 -0
- package/src/ipc/postshim.ts +164 -0
- package/src/ipc/protocol.ts +342 -0
- package/src/ipc/server.ts +371 -0
- package/src/ipc/workerspec.ts +218 -0
- package/src/learner/learner.ts +1655 -0
- package/src/learner/miner.ts +1290 -0
- package/src/learner/packet.ts +340 -0
- package/src/learner/receipt.ts +168 -0
- package/src/learner/status.ts +107 -0
- package/src/ledgercheck.ts +157 -0
- package/src/lessonledger.ts +703 -0
- package/src/lessonselect.ts +888 -0
- package/src/lessonshadow.ts +810 -0
- package/src/levers.ts +393 -0
- package/src/live/projection.ts +491 -0
- package/src/live/publish.ts +92 -0
- package/src/live/read.ts +86 -0
- package/src/livecli.ts +336 -0
- package/src/loginhint.ts +56 -0
- package/src/loginrefresh/atomic.ts +76 -0
- package/src/loginrefresh/lock.ts +104 -0
- package/src/loginrefresh/main.ts +59 -0
- package/src/loginrefresh/pirefresh.ts +57 -0
- package/src/loginrefresh/refresher.ts +326 -0
- package/src/loginrefresh/supervisor.ts +273 -0
- package/src/loginrefresh/types.ts +35 -0
- package/src/memory/context.ts +385 -0
- package/src/memory/datafence.ts +9 -0
- package/src/memory/delivery.ts +171 -0
- package/src/memory/eventtags.ts +80 -0
- package/src/memory/health.ts +953 -0
- package/src/memory/import.ts +664 -0
- package/src/memory/layout.ts +261 -0
- package/src/memory/legacy-file.ts +104 -0
- package/src/memory/lessons.ts +447 -0
- package/src/memory/ovsession.ts +601 -0
- package/src/memory/relayout.ts +335 -0
- package/src/memory/roundactivity.ts +209 -0
- package/src/memory/selection.ts +989 -0
- package/src/memory/shadowstatus.ts +125 -0
- package/src/memory/store.ts +275 -0
- package/src/memory/textidentity.ts +8 -0
- package/src/metrics.ts +1056 -0
- package/src/missingconfig.ts +7 -0
- package/src/npmverbs.ts +264 -0
- package/src/onboard/choices.ts +222 -0
- package/src/onboard/engine.ts +518 -0
- package/src/onboard/launch.ts +115 -0
- package/src/onboard/steps/appslug.ts +265 -0
- package/src/onboard/steps/bringup.ts +482 -0
- package/src/onboard/steps/compose.ts +459 -0
- package/src/onboard/steps/configdelivery.ts +58 -0
- package/src/onboard/steps/configfill.ts +1352 -0
- package/src/onboard/steps/deploysh.ts +105 -0
- package/src/onboard/steps/embeddings.ts +550 -0
- package/src/onboard/steps/escalation.ts +640 -0
- package/src/onboard/steps/githubapp.ts +1414 -0
- package/src/onboard/steps/golive.ts +577 -0
- package/src/onboard/steps/index.ts +71 -0
- package/src/onboard/steps/ingress.ts +907 -0
- package/src/onboard/steps/inventory.ts +526 -0
- package/src/onboard/steps/llmauth.ts +1095 -0
- package/src/onboard/steps/memory.ts +1290 -0
- package/src/onboard/steps/memorykeys.ts +85 -0
- package/src/onboard/steps/preflight.ts +191 -0
- package/src/onboard/steps/proof.ts +624 -0
- package/src/onboard/steps/ssh.ts +377 -0
- package/src/onboard/steps/topology.ts +337 -0
- package/src/onboard/steps/transport.ts +665 -0
- package/src/onboard/steps/verifyup.ts +448 -0
- package/src/onboard/steps/vmdeploy.ts +442 -0
- package/src/onboard/toml.ts +50 -0
- package/src/opencli.ts +591 -0
- package/src/openpr.ts +568 -0
- package/src/openviking/client.ts +1131 -0
- package/src/openviking/context.ts +263 -0
- package/src/openviking/embedder.ts +516 -0
- package/src/openviking/gapsink.ts +63 -0
- package/src/openviking/health.ts +83 -0
- package/src/openviking/index.ts +119 -0
- package/src/openviking/lessonfind.ts +92 -0
- package/src/openviking/probe.ts +129 -0
- package/src/openviking/proc.ts +110 -0
- package/src/openviking/provisionreport.ts +160 -0
- package/src/openviking/reembed.ts +412 -0
- package/src/openviking/relayout.ts +177 -0
- package/src/openviking/sidecar-main.ts +38 -0
- package/src/openviking/sidecar.ts +106 -0
- package/src/openviking/store.ts +2025 -0
- package/src/openviking/supervisor.ts +474 -0
- package/src/openviking/types.ts +174 -0
- package/src/operator-recovery.ts +237 -0
- package/src/operatormessage.ts +52 -0
- package/src/outcomes.ts +1670 -0
- package/src/piauthcopy.ts +12 -0
- package/src/picli.ts +49 -0
- package/src/picwd.ts +25 -0
- package/src/pilogin.ts +37 -0
- package/src/pingpong.ts +1339 -0
- package/src/posting-journal.ts +549 -0
- package/src/posting-outbox.ts +802 -0
- package/src/posting-receipt.ts +324 -0
- package/src/posting.ts +603 -0
- package/src/postops.ts +832 -0
- package/src/pr-activity.ts +20 -0
- package/src/pr-author.ts +6 -0
- package/src/pr-conversation.ts +432 -0
- package/src/pr-session-lifecycle.ts +631 -0
- package/src/preflight.ts +391 -0
- package/src/priorresolve.ts +343 -0
- package/src/qcrbin.ts +186 -0
- package/src/recheck.ts +1775 -0
- package/src/reconcile.ts +1030 -0
- package/src/recordedfindings.ts +27 -0
- package/src/regression.ts +833 -0
- package/src/replay.ts +816 -0
- package/src/requiredpasses.ts +148 -0
- package/src/resumecheck.ts +246 -0
- package/src/reviewcli.ts +787 -0
- package/src/reviewcontext.ts +35 -0
- package/src/reviewdetach.ts +734 -0
- package/src/reviewhistory.ts +427 -0
- package/src/reviewhome.ts +36 -0
- package/src/reviewmemory.ts +133 -0
- package/src/reviewrendercli.ts +139 -0
- package/src/round-work.ts +42 -0
- package/src/runhost.ts +108 -0
- package/src/scanauth.ts +36 -0
- package/src/scanbox.ts +1081 -0
- package/src/scancli.ts +1296 -0
- package/src/scandriverlog.ts +32 -0
- package/src/session-bridge.ts +139 -0
- package/src/session-command-payload.ts +35 -0
- package/src/session-command-supervisor.ts +1142 -0
- package/src/sessionjsonl.ts +88 -0
- package/src/sessions.ts +659 -0
- package/src/setup/adapters.ts +335 -0
- package/src/setup/baselines.ts +110 -0
- package/src/setup/caserunner.ts +611 -0
- package/src/setup/configcommit.ts +443 -0
- package/src/setup/engine.ts +1001 -0
- package/src/setup/facts.ts +627 -0
- package/src/setup/findings.ts +1264 -0
- package/src/setup/history.ts +443 -0
- package/src/setup/interview.ts +243 -0
- package/src/setup/ledger.ts +324 -0
- package/src/setup/pins.ts +66 -0
- package/src/setup/portrait.ts +635 -0
- package/src/setup/presentation.ts +18 -0
- package/src/setup/profile.ts +508 -0
- package/src/setup/replay.ts +370 -0
- package/src/setup/replaybuild.ts +392 -0
- package/src/setup/replayrun.ts +1096 -0
- package/src/setup/replaysource.ts +198 -0
- package/src/setup/retune.ts +415 -0
- package/src/setup/retunelog.ts +95 -0
- package/src/setup/scan.ts +1033 -0
- package/src/setup/scanbuild.ts +168 -0
- package/src/setup/scanfacts.ts +107 -0
- package/src/setup/scanlease.ts +297 -0
- package/src/setup/scanreport.ts +373 -0
- package/src/setup/scanrun.ts +2268 -0
- package/src/setup/scantree.ts +259 -0
- package/src/setup/stage2.ts +926 -0
- package/src/setup/stage2request.ts +347 -0
- package/src/setup/survey.ts +458 -0
- package/src/setup/surveyrun.ts +758 -0
- package/src/setup/textsniff.ts +95 -0
- package/src/setup/trackersink.ts +433 -0
- package/src/setup/triage.ts +768 -0
- package/src/setup/tuning.ts +766 -0
- package/src/setup/unitcut.ts +523 -0
- package/src/singleflight.ts +86 -0
- package/src/slack/announce.ts +911 -0
- package/src/slack/api.ts +303 -0
- package/src/slack/messages.ts +288 -0
- package/src/slack/origin.ts +599 -0
- package/src/slack/policy.ts +354 -0
- package/src/slack/reply.ts +201 -0
- package/src/slack/router.ts +285 -0
- package/src/slack/socket.ts +384 -0
- package/src/slack/types.ts +108 -0
- package/src/sqlitehint.ts +32 -0
- package/src/ssh-observation.ts +30 -0
- package/src/state.ts +8243 -0
- package/src/statemachine.ts +369 -0
- package/src/stubs.ts +68 -0
- package/src/takeover.ts +634 -0
- package/src/takeoverpi.ts +293 -0
- package/src/terminal-failure.ts +41 -0
- package/src/thread-resolution-refusal.ts +2 -0
- package/src/tmux.ts +153 -0
- package/src/trackerid.ts +41 -0
- package/src/transcriptindex.ts +665 -0
- package/src/tui/actions.ts +484 -0
- package/src/tui/alerts.ts +666 -0
- package/src/tui/app.ts +1979 -0
- package/src/tui/archiveops.ts +192 -0
- package/src/tui/autofile.ts +233 -0
- package/src/tui/board.ts +1432 -0
- package/src/tui/candidates.ts +889 -0
- package/src/tui/cellwidth.ts +148 -0
- package/src/tui/changelog-store.ts +503 -0
- package/src/tui/chord.ts +25 -0
- package/src/tui/clipboard.ts +92 -0
- package/src/tui/cmdbar.ts +296 -0
- package/src/tui/companion.ts +1427 -0
- package/src/tui/companionplan.ts +247 -0
- package/src/tui/companiontab.ts +83 -0
- package/src/tui/confighome.ts +155 -0
- package/src/tui/connect.ts +68 -0
- package/src/tui/deploy-ceremony.ts +104 -0
- package/src/tui/deploy-drain.ts +129 -0
- package/src/tui/deploy-process.ts +265 -0
- package/src/tui/deploytab.ts +1523 -0
- package/src/tui/design.ts +125 -0
- package/src/tui/digesttab.ts +214 -0
- package/src/tui/emptystate.ts +39 -0
- package/src/tui/evidence.ts +999 -0
- package/src/tui/feed.ts +604 -0
- package/src/tui/feedbacktab.ts +1077 -0
- package/src/tui/filters.ts +90 -0
- package/src/tui/ghread.ts +321 -0
- package/src/tui/historypull.ts +81 -0
- package/src/tui/inboxpull.ts +236 -0
- package/src/tui/index.ts +1565 -0
- package/src/tui/issuesink.ts +238 -0
- package/src/tui/jsonfile.ts +42 -0
- package/src/tui/keys.ts +109 -0
- package/src/tui/layout.ts +108 -0
- package/src/tui/learning.ts +627 -0
- package/src/tui/lessonswitch.ts +118 -0
- package/src/tui/livecontrol.ts +228 -0
- package/src/tui/memforward.ts +443 -0
- package/src/tui/memincident.ts +363 -0
- package/src/tui/memory.ts +620 -0
- package/src/tui/memtab.ts +606 -0
- package/src/tui/metricstab.ts +1177 -0
- package/src/tui/model.ts +131 -0
- package/src/tui/mouse.ts +98 -0
- package/src/tui/msg.ts +268 -0
- package/src/tui/nativecompanion.ts +417 -0
- package/src/tui/openurl.ts +21 -0
- package/src/tui/paint.ts +511 -0
- package/src/tui/panes.ts +98 -0
- package/src/tui/pipane.ts +219 -0
- package/src/tui/poll-hooks.ts +12 -0
- package/src/tui/poll.ts +147 -0
- package/src/tui/proposals.ts +838 -0
- package/src/tui/prpause.ts +150 -0
- package/src/tui/reload.ts +436 -0
- package/src/tui/remote.ts +413 -0
- package/src/tui/rollover.ts +969 -0
- package/src/tui/scantab.ts +1290 -0
- package/src/tui/setupentry.ts +93 -0
- package/src/tui/sidebar.ts +749 -0
- package/src/tui/sshtriage.ts +510 -0
- package/src/tui/startup.ts +231 -0
- package/src/tui/tabs.ts +45 -0
- package/src/tui/termmirror.ts +224 -0
- package/src/tui/theme.ts +95 -0
- package/src/tui/ticketstab.ts +310 -0
- package/src/tui/tmuxsocket.ts +129 -0
- package/src/tui/trackertickets.ts +84 -0
- package/src/tui/unwedge.ts +286 -0
- package/src/tui/viewer.ts +575 -0
- package/src/tui/viewerinput.ts +173 -0
- package/src/updatenotice.ts +178 -0
- package/src/usage.ts +1154 -0
- package/src/verdicts.ts +4689 -0
- package/src/version.ts +80 -0
- package/src/wakes.ts +476 -0
- package/src/webhook.ts +942 -0
- package/src/webhookheal.ts +276 -0
- package/src/wire.ts +2153 -0
- package/src/worker-session-host.ts +602 -0
- package/src/workerbreaker.ts +156 -0
- package/src/workercontrol.ts +104 -0
- package/src/workerlease.ts +441 -0
- package/src/workerliveness.ts +103 -0
- package/src/workerlog.ts +79 -0
- package/src/workerruntime.ts +30 -0
- package/src/workersupervisor.ts +1538 -0
- package/src/workerusage.ts +42 -0
- package/src/workerworld.ts +427 -0
- package/src/worktrees.ts +1138 -0
- package/tests/fixtures/regression-cases.json +795 -0
- package/tests/fixtures/seed-manifest.json +19 -0
package/src/dispatch.ts
ADDED
|
@@ -0,0 +1,2250 @@
|
|
|
1
|
+
// Deterministic dispatch. No LLM in this path: FIFO by trigger time, at most
|
|
2
|
+
// `daemon.max_concurrent_rounds` rounds in flight, triggers consumed at
|
|
3
|
+
// dispatch, lenses/gates derived from the files API by the heuristics.
|
|
4
|
+
//
|
|
5
|
+
// Before paying for a re-review round, a SYSTEM-provenance trigger (push / CI /
|
|
6
|
+
// staleness — `pr_state.trigger_provenance`, stamped by
|
|
7
|
+
// statemachine.applyEvent) is run through the recheck delta classifier; an
|
|
8
|
+
// empty/trivial delta since the reviewed head consumes the trigger and upholds
|
|
9
|
+
// the standing verdict instead of dispatching. A human ask (fresh label, fresh
|
|
10
|
+
// team re-review request, author reply) is never short-circuited — but like
|
|
11
|
+
// every trigger it runs no round at a head already reviewed with no author
|
|
12
|
+
// reply since (guard 2's daemon half): it settles on the standing verdict.
|
|
13
|
+
//
|
|
14
|
+
// The round order (types.ts `RoundOrder`) is the daemon↔session contract; the
|
|
15
|
+
// plan and the order are built by the review planner (packages/pi-plugin/src/planner.ts), and the
|
|
16
|
+
// prompt renderer (packages/pi-plugin/src/templates.ts) is injected so this module has no content
|
|
17
|
+
// dependency.
|
|
18
|
+
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { join, resolve } from "node:path";
|
|
20
|
+
import type { Config } from "./config.js";
|
|
21
|
+
import { githubCloneDir, sandboxRoot } from "./config.js";
|
|
22
|
+
import {
|
|
23
|
+
fingerprintRoundIntegrity,
|
|
24
|
+
INTEGRITY_BUDGET_MS,
|
|
25
|
+
roundIntegrityOpts,
|
|
26
|
+
type IntegrityFingerprint,
|
|
27
|
+
type RoundIntegrityOpts,
|
|
28
|
+
} from "./execboundary.js";
|
|
29
|
+
import { withinBudget } from "@quickchatai/pi-quickreview/src/budget.js";
|
|
30
|
+
import type { GateResult } from "@quickchatai/pi-quickreview/src/gate.js";
|
|
31
|
+
import type { PrFileEntry } from "./github.js";
|
|
32
|
+
import { DJANGO_ECOSYSTEM, type Ecosystem } from "@quickchatai/pi-quickreview/src/ecosystem.js";
|
|
33
|
+
import { STYLE_PASS, LINT_CONFORMANCE_PASS } from "@quickchatai/pi-quickreview/src/heuristics.js";
|
|
34
|
+
import type { HeuristicsExtension } from "@quickchatai/pi-quickreview/src/heuristics.js";
|
|
35
|
+
import { lensEnabled } from "@quickchatai/pi-quickreview/src/lenstuning.js";
|
|
36
|
+
import type { MigrationCollision } from "@quickchatai/pi-quickreview/src/migrations.js";
|
|
37
|
+
import { VALIDATE_PASS } from "@quickchatai/pi-quickreview/src/include.js";
|
|
38
|
+
import { hasNewEvidence } from "@quickchatai/pi-quickreview/src/guards.js";
|
|
39
|
+
import {
|
|
40
|
+
addTierOf,
|
|
41
|
+
buildPassPlan,
|
|
42
|
+
buildRoundOrder,
|
|
43
|
+
decideFollowUp,
|
|
44
|
+
lensesOf,
|
|
45
|
+
optionalOrderOf,
|
|
46
|
+
renderDeltaPatch,
|
|
47
|
+
reviewedHeadOf,
|
|
48
|
+
roundDeltaOf,
|
|
49
|
+
sweepOrderOf,
|
|
50
|
+
type FollowUpDecision,
|
|
51
|
+
type FollowUpFullReason,
|
|
52
|
+
type PassPlan,
|
|
53
|
+
type PreparedTree,
|
|
54
|
+
} from "@quickchatai/pi-quickreview/src/planner.js";
|
|
55
|
+
import {
|
|
56
|
+
assembleRoundMemory,
|
|
57
|
+
describeGaps,
|
|
58
|
+
pinnedHeadState,
|
|
59
|
+
MEMORY_LESS,
|
|
60
|
+
roundContextQuery,
|
|
61
|
+
ROUND_CONTEXT_DEFAULTS,
|
|
62
|
+
type RoundMemory,
|
|
63
|
+
type RoundMemorySeed,
|
|
64
|
+
} from "./memory/context.js";
|
|
65
|
+
import {
|
|
66
|
+
carryWithheldMark,
|
|
67
|
+
reviewedOwnership,
|
|
68
|
+
classifyDelta,
|
|
69
|
+
ownershipSet,
|
|
70
|
+
recheckFingerprint,
|
|
71
|
+
verifyMerges,
|
|
72
|
+
type DeltaClass,
|
|
73
|
+
type DeltaList,
|
|
74
|
+
} from "./recheck.js";
|
|
75
|
+
import { postingReceiptPath, readPostingReceipt } from "./posting-receipt.js";
|
|
76
|
+
import type { RoundLessonHook, RoundSelectionResult } from "./lessonselect.js";
|
|
77
|
+
import {
|
|
78
|
+
buildRoundDelivery,
|
|
79
|
+
deliversAnything,
|
|
80
|
+
deliveryCounts,
|
|
81
|
+
onlyWritten,
|
|
82
|
+
type RoundLessonDelivery,
|
|
83
|
+
} from "./memory/delivery.js";
|
|
84
|
+
import { findStyleGuide } from "@quickchatai/pi-quickreview/src/styleguide.js";
|
|
85
|
+
import { findCiLinters } from "@quickchatai/pi-quickreview/src/lintconfig.js";
|
|
86
|
+
import { writeLessonFiles, writePassTasks } from "@quickchatai/pi-quickreview/src/passtasks.js";
|
|
87
|
+
import {
|
|
88
|
+
CONTEXT_PACK_FILE,
|
|
89
|
+
readWorktreeFileForPack,
|
|
90
|
+
renderContextPack,
|
|
91
|
+
} from "@quickchatai/pi-quickreview/src/contextpack.js";
|
|
92
|
+
import { renderGateNotes } from "@quickchatai/pi-quickreview/src/templates.js";
|
|
93
|
+
import { firstPartyDirectiveFiles } from "@quickchatai/pi-quickreview/src/severity.js";
|
|
94
|
+
import type { DispatchAnnouncer } from "./slack/announce.js";
|
|
95
|
+
import { applyEvent, restoreStandingLifecycle, setPrGates } from "./statemachine.js";
|
|
96
|
+
import type { ApprovalWithheldRow, PrStateRow, ReviewedHeadRow, StateStore } from "./state.js";
|
|
97
|
+
import { snapshotMark, standingReview } from "@quickchatai/pi-quickreview/src/standing-review.js";
|
|
98
|
+
import { roundPromptMarker } from "@quickchatai/pi-quickreview/src/types.js";
|
|
99
|
+
import type {
|
|
100
|
+
LedgerCarry,
|
|
101
|
+
PassPlanEntry,
|
|
102
|
+
PassTier,
|
|
103
|
+
RecordedAddablePass,
|
|
104
|
+
RoundDelta,
|
|
105
|
+
RoundOrder,
|
|
106
|
+
RoundRecoveryContext,
|
|
107
|
+
RoundType,
|
|
108
|
+
SessionHooks,
|
|
109
|
+
} from "@quickchatai/pi-quickreview/src/types.js";
|
|
110
|
+
import { preparePriorFindings } from "@quickchatai/pi-quickreview/src/priorfindings.js";
|
|
111
|
+
import { type PrOwnedDelta, prWorktreeRel } from "./worktrees.js";
|
|
112
|
+
|
|
113
|
+
/** The slice of GitHubAdapter dispatch needs — all mutations are gated inside
|
|
114
|
+
* the adapter, so this module never touches GitHub directly. */
|
|
115
|
+
export interface DispatchAdapter {
|
|
116
|
+
listReviewsFull?(pr: number): Promise<Array<{ user: string; body: string; commit_id: string }>>;
|
|
117
|
+
listPrFiles(n: number): Promise<PrFileEntry[] & { truncated?: boolean }>;
|
|
118
|
+
/** Read-only; dispatch needs `base_ref` to keep the base branch fresh, and
|
|
119
|
+
* `body` as pass-intent's artifact + substance-bar input. Absent body ⇒ the
|
|
120
|
+
* intent lens is never selected. */
|
|
121
|
+
getPr(n: number): Promise<{ base_ref: string; body?: string }>;
|
|
122
|
+
addLabel(n: number, label: string): Promise<GateResult>;
|
|
123
|
+
removeLabel(n: number, label: string): Promise<GateResult>;
|
|
124
|
+
deleteTeamReviewRequest(n: number): Promise<GateResult>;
|
|
125
|
+
/** COMMENT delivery for the migration-collision pre-check — the
|
|
126
|
+
* same gated `POST /pulls/<n>/reviews` the session's review uses. */
|
|
127
|
+
createReview(
|
|
128
|
+
n: number,
|
|
129
|
+
payload: {
|
|
130
|
+
commit_id: string;
|
|
131
|
+
event: "APPROVE" | "REQUEST_CHANGES" | "COMMENT";
|
|
132
|
+
body: string;
|
|
133
|
+
comments?: unknown[];
|
|
134
|
+
},
|
|
135
|
+
options?: { purpose?: "migration_precheck" },
|
|
136
|
+
): Promise<GateResult>;
|
|
137
|
+
/** `GET /compare/<base>...<head>` — read-only, the trivial-delta
|
|
138
|
+
* short-circuit's delta source. Optional: an adapter without it (stub-wired
|
|
139
|
+
* core, test fakes) never short-circuits — every re-trigger pays a full
|
|
140
|
+
* round. */
|
|
141
|
+
compareFiles?(base: string, head: string): Promise<DeltaList>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface DispatchDeps {
|
|
145
|
+
cfg: Config;
|
|
146
|
+
store: StateStore;
|
|
147
|
+
adapter: DispatchAdapter;
|
|
148
|
+
hooks: SessionHooks;
|
|
149
|
+
/**
|
|
150
|
+
* The profile-selected ecosystem seam: the tier-forcing migration gate, the
|
|
151
|
+
* round pre-check, and the prompt's gate prose all come off it. Absent ⇒
|
|
152
|
+
* Django; the production assembly (wire.ts) always passes the profile's
|
|
153
|
+
* selection explicitly, so a `none` profile really runs none of the
|
|
154
|
+
* machinery.
|
|
155
|
+
*/
|
|
156
|
+
ecosystem?: Ecosystem;
|
|
157
|
+
/** The template renderer, injected. Default: the order as JSON. The second
|
|
158
|
+
* argument is the round's memory seed; a renderer may ignore it. */
|
|
159
|
+
renderPrompt?: (order: RoundOrder, memory: RoundMemory) => string;
|
|
160
|
+
/**
|
|
161
|
+
* Round-memory seeding: the assembler the one per-round context call goes
|
|
162
|
+
* through, plus its budget/deadline. Absent ⇒ every round renders
|
|
163
|
+
* memory-less — memory is a seasoning on the prompt, never a precondition
|
|
164
|
+
* for a round.
|
|
165
|
+
*/
|
|
166
|
+
memory?: RoundMemorySeed;
|
|
167
|
+
/**
|
|
168
|
+
* The round's lesson selection (src/lessonselect.ts). Under
|
|
169
|
+
* `[memory] delivery = "shadow"` it is started once per round, after the
|
|
170
|
+
* round was handed to its session (seed, prompt and marker done), and
|
|
171
|
+
* never awaited — nothing the session sees depends on it, and starting it
|
|
172
|
+
* after the seed keeps its finds out of the seed's way on the server's
|
|
173
|
+
* one-thread embedder. Under `on` (`delivering()`) it is awaited
|
|
174
|
+
* beside the seed (`select`), no lesson find sent before the seed's search
|
|
175
|
+
* is, and the round's task files, lessons files and prompt are rendered
|
|
176
|
+
* from it; a round discarded before its handoff is `discard`ed from the
|
|
177
|
+
* ledger. Absent ⇒ nothing runs. Must not throw (a throw is logged and
|
|
178
|
+
* swallowed).
|
|
179
|
+
*/
|
|
180
|
+
lessons?: RoundLessonHook;
|
|
181
|
+
/** SETTLED digest carried from the session ledger. When absent, the digest
|
|
182
|
+
* comes from `ledgerCarry`'s SETTLED items. */
|
|
183
|
+
settledDigest?: (pr: number) => string;
|
|
184
|
+
/**
|
|
185
|
+
* The PR's mirrored ledger, re-handed in every round order
|
|
186
|
+
* (packages/pi-plugin/src/ledgercarry.ts). Production reads `state/ledgers/pr-<n>.md`
|
|
187
|
+
* (daemon.ts). Total: undefined carry ⇒ the order has no `ledger_carry`.
|
|
188
|
+
* Absent ⇒ no carry and an empty digest.
|
|
189
|
+
*/
|
|
190
|
+
ledgerCarry?: (pr: number) => { carry?: LedgerCarry; settled: string };
|
|
191
|
+
worktreeFor?: (pr: number) => string;
|
|
192
|
+
/**
|
|
193
|
+
* Fetch the PR head + its base branch and materialise the worktree, before
|
|
194
|
+
* the round order is built. Returns what is actually on disk. Optional: the
|
|
195
|
+
* stub-wired daemon core and unit tests have no git, and fall back to the PR
|
|
196
|
+
* row's sha with no base facts.
|
|
197
|
+
*/
|
|
198
|
+
prepareWorktree?: (pr: number, baseRef: string, expectedHead: string) => Promise<PreparedTree>;
|
|
199
|
+
/**
|
|
200
|
+
* Every migration file present on the base branch at a commit. Read-only
|
|
201
|
+
* git in the base clone, which the worktree preparation has just
|
|
202
|
+
* re-fetched — so `base_sha` really is the branch's tip this round. Optional:
|
|
203
|
+
* absent (stub-wired core, unit tests without git) ⇒ the collision pre-check
|
|
204
|
+
* is skipped and logged, never fatal.
|
|
205
|
+
*/
|
|
206
|
+
listBaseMigrations?: (baseSha: string) => Promise<string[]>;
|
|
207
|
+
/**
|
|
208
|
+
* The most recent human author of `paths` in the history reachable from
|
|
209
|
+
* `sha` (WorktreeManager.lastHumanAuthor). Read-only git in the
|
|
210
|
+
* base clone. Optional: absent (stub-wired core, unit tests without git) ⇒
|
|
211
|
+
* the escalation mention falls back to config
|
|
212
|
+
* `[review] escalation_mention_default`.
|
|
213
|
+
*/
|
|
214
|
+
directiveAuthor?: (
|
|
215
|
+
sha: string,
|
|
216
|
+
paths: string[],
|
|
217
|
+
) => Promise<{ login: string | null; name: string; email: string } | null>;
|
|
218
|
+
/** Hand-resolved merge files between two shas (conflict resolutions are
|
|
219
|
+
* never trivial), for the trivial-delta short-circuit's classification.
|
|
220
|
+
* Same seam RecheckDeps carries. Absent ⇒ merges classify by the ownership
|
|
221
|
+
* rule alone. */
|
|
222
|
+
resolvedFilesInMerges?: (
|
|
223
|
+
from: string,
|
|
224
|
+
to: string,
|
|
225
|
+
pr?: number,
|
|
226
|
+
) => Promise<{ files: string[]; degraded: boolean }>;
|
|
227
|
+
/**
|
|
228
|
+
* The PR-OWNED change between the last reviewed head and this round's head
|
|
229
|
+
* (`WorktreeManager.prOwnedDelta`): the reviewed head replayed onto the
|
|
230
|
+
* round's merge base and diffed against the new head. Absent (stub-wired
|
|
231
|
+
* core, unit tests without git) ⇒ every later round runs in full —
|
|
232
|
+
* `followup_full_round {reason: "no-adapter"}`.
|
|
233
|
+
*/
|
|
234
|
+
prOwnedDelta?: (q: {
|
|
235
|
+
pr: number;
|
|
236
|
+
from: string;
|
|
237
|
+
to: string;
|
|
238
|
+
mergeBase: string;
|
|
239
|
+
baseSha?: string;
|
|
240
|
+
/** The merge base the reviewed round recorded, when known. */
|
|
241
|
+
fromMergeBase?: string;
|
|
242
|
+
}) => Promise<PrOwnedDelta>;
|
|
243
|
+
/** Pin a head a round was dispatched to review against gc
|
|
244
|
+
* (`WorktreeManager.pinReviewedHead`), so the next follow-up round can
|
|
245
|
+
* replay it after a force-push. Best-effort; absent ⇒ nothing pinned. */
|
|
246
|
+
pinReviewedHead?: (pr: number, sha: string) => Promise<boolean>;
|
|
247
|
+
/** `<root>/state/findings` — where round findings records live, read by
|
|
248
|
+
* the follow-up round's prior-findings list. Default: under the
|
|
249
|
+
* sandbox root's state dir. */
|
|
250
|
+
findingsDir?: string;
|
|
251
|
+
/** `<root>/state/posting-receipts` — read to prove an interrupted
|
|
252
|
+
* approval retry posted nothing before its withheld mark is restored.
|
|
253
|
+
* Default: under the sandbox root's state dir. */
|
|
254
|
+
postingReceiptDir?: string;
|
|
255
|
+
/** The round's integrity baseline, taken before the session hears about
|
|
256
|
+
* the round. Default: `fingerprintRoundIntegrity` — real, async reads of the
|
|
257
|
+
* clone, worktree, pi home and sandbox policy surface. Injected only by a
|
|
258
|
+
* test whose fake clock cannot wait on real file reads. */
|
|
259
|
+
integrityFingerprint?: (opts: RoundIntegrityOpts) => Promise<IntegrityFingerprint>;
|
|
260
|
+
/** How long the baseline may take before the round goes unchecked.
|
|
261
|
+
* Default INTEGRITY_BUDGET_MS. */
|
|
262
|
+
integrityBudgetMs?: number;
|
|
263
|
+
/**
|
|
264
|
+
* Unpack the third-party sources this PR touches and return the rendered
|
|
265
|
+
* note for the order (packages/pi-plugin/src/depsources.ts; `[review] dependency_sources`).
|
|
266
|
+
* Must never throw or hang past its own budget. Absent ⇒ no note.
|
|
267
|
+
*/
|
|
268
|
+
dependencySources?: (worktreePath: string, files: PrFileEntry[]) => Promise<string>;
|
|
269
|
+
/** Slack announcer. Optional: absent ⇒ no Slack at all, which
|
|
270
|
+
* is what the stub-wired daemon core and every non-Slack test see. */
|
|
271
|
+
announce?: DispatchAnnouncer;
|
|
272
|
+
/** Profile lens-heuristics extension: the selected repo profile's
|
|
273
|
+
* extra per-lens path/added-line predicates, loaded ONCE at assembly
|
|
274
|
+
* (wire.ts, `loadProfileHeuristics`). Absent ⇒ the compiled-in generic
|
|
275
|
+
* selection core alone. */
|
|
276
|
+
heuristics?: HeuristicsExtension;
|
|
277
|
+
now?: () => Date;
|
|
278
|
+
log?: (msg: string, extra?: unknown) => void;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function defaultWorktree(pr: number): string {
|
|
282
|
+
return prWorktreeRel(pr);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** PR size at the reviewed head, summed over the files API response. */
|
|
286
|
+
export function diffstatOf(files: PrFileEntry[]): {
|
|
287
|
+
additions: number;
|
|
288
|
+
deletions: number;
|
|
289
|
+
changed_files: number;
|
|
290
|
+
} {
|
|
291
|
+
return {
|
|
292
|
+
additions: files.reduce((n, f) => n + (f.additions ?? 0), 0),
|
|
293
|
+
deletions: files.reduce((n, f) => n + (f.deletions ?? 0), 0),
|
|
294
|
+
changed_files: files.length,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Round type: no prior round → "first"; a staleness recheck → "staleness";
|
|
299
|
+
* anything else → "re-review". `priorRounds` counts live verdicted rounds
|
|
300
|
+
* only: backfilled rounds and cancelled or verdict-less attempts are not
|
|
301
|
+
* counted, so such an attempt leaves the next round "first". */
|
|
302
|
+
export function roundTypeFor(row: PrStateRow, priorRounds: number): RoundType {
|
|
303
|
+
if (priorRounds === 0) return "first";
|
|
304
|
+
if (row.stale_recheck) return "staleness";
|
|
305
|
+
return "re-review";
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Queued PRs in FIFO order by trigger time (nulls last, then PR number).
|
|
309
|
+
* Overseer-paused PRs never dispatch. */
|
|
310
|
+
export function queuedInOrder(store: StateStore): PrStateRow[] {
|
|
311
|
+
return store
|
|
312
|
+
.allPrs()
|
|
313
|
+
.filter((p) => p.state === "queued" && !p.paused)
|
|
314
|
+
.sort((a, b) => {
|
|
315
|
+
const at = a.trigger_ts ?? "";
|
|
316
|
+
const bt = b.trigger_ts ?? "";
|
|
317
|
+
if (at === bt) return a.number - b.number;
|
|
318
|
+
if (!at) return 1;
|
|
319
|
+
if (!bt) return -1;
|
|
320
|
+
return at < bt ? -1 : 1;
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Review rounds in flight, for the case writer: rounds running
|
|
325
|
+
* (a provisional one included — its seed is about to read memory) and PRs
|
|
326
|
+
* QUEUED that the pump would dispatch — not paused, not held FAILED_LOUD
|
|
327
|
+
* (those wait for the operator or their retry time, not for a pump tick),
|
|
328
|
+
* not blocked by a FAILED session command (it waits for the supervisor's
|
|
329
|
+
* recovery — which puts it back to pending, counted again, or opens the
|
|
330
|
+
* FAILED_LOUD hold — or for the operator's restart-session, never for a pump
|
|
331
|
+
* tick), and none while a drain holds the pump. A row skipped for a PENDING
|
|
332
|
+
* or RUNNING session command or for held wakes still counts: it dispatches
|
|
333
|
+
* once those drain on their own. */
|
|
334
|
+
export interface ReviewsInFlight {
|
|
335
|
+
running: number;
|
|
336
|
+
queued: number;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function reviewsInFlight(store: StateStore): ReviewsInFlight {
|
|
340
|
+
const running = store.runningRounds().length;
|
|
341
|
+
if (store.drainState().draining) return { running, queued: 0 };
|
|
342
|
+
const queued = queuedInOrder(store).filter(
|
|
343
|
+
(row) =>
|
|
344
|
+
!store.openReviewFailure(row.number) &&
|
|
345
|
+
// The pump skips a row while ANY blocking command stands (dispatchUnlocked's
|
|
346
|
+
// `blockingSessionCommand`); a failed one among them does not drain itself.
|
|
347
|
+
!store.blockingSessionCommandsForPr(row.number).some((c) => c.status === "failed"),
|
|
348
|
+
).length;
|
|
349
|
+
return { running, queued };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function capacity(store: StateStore, cfg: Config): number {
|
|
353
|
+
return Math.max(0, cfg.daemon.max_concurrent_rounds - store.runningRounds().length);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** A consume target that is already gone (label removed in an earlier round,
|
|
357
|
+
* no pending team request) comes back as a 404 — that is consumption already
|
|
358
|
+
* done, not a failure. */
|
|
359
|
+
function alreadyConsumed(err: unknown): boolean {
|
|
360
|
+
return /→ 404\b|"status":\s*"404"/.test(String(err));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* The round's ecosystem pre-check (behind the ecosystem seam), run once
|
|
365
|
+
* per round after the worktree is prepared and BEFORE the session is handed
|
|
366
|
+
* the order.
|
|
367
|
+
*
|
|
368
|
+
* The behavior is the ecosystem's (`packages/pi-plugin/src/ecosystem.ts`): Django detects
|
|
369
|
+
* migration-number collisions at the pinned base sha and posts the remedy as a
|
|
370
|
+
* gated **COMMENT** — never a blocker, never a verdict — while `none` is a
|
|
371
|
+
* first-class configured-off (`{ status: "off" }`): nothing runs, nothing is
|
|
372
|
+
* read, nothing is logged. What stays HERE is the plumbing the seam must not
|
|
373
|
+
* own: the store, the gate-backed adapter, and the mapping back onto the round
|
|
374
|
+
* order's `migration_collisions` / `migration_precheck_degraded` fields. The
|
|
375
|
+
* seam is total — every failure resolves to an outcome — so a round is never
|
|
376
|
+
* lost to the pre-check.
|
|
377
|
+
*
|
|
378
|
+
* A DEGRADED check (Django only: the check exists but could not run while the
|
|
379
|
+
* PR touches migrations) must never be quieter than no check at all ("more
|
|
380
|
+
* reliable, never quieter"): the caller renders an explicit
|
|
381
|
+
* "the pre-check did not run — check collisions yourself" note into the round
|
|
382
|
+
* prompt, which is what conditionally un-muzzles pass-data-safety's withdrawn
|
|
383
|
+
* finding class for exactly that round (see packages/pi-plugin/src/include.ts,
|
|
384
|
+
* MIGRATION_NUMBERING_ADDENDUM) — and raises the overseer anomaly. "off"
|
|
385
|
+
* takes none of those paths: configured-off is not degradation.
|
|
386
|
+
*
|
|
387
|
+
* A WITHHELD comment (the ledger holds a collision-comment row it cannot
|
|
388
|
+
* read, so a duplicate cannot be ruled out) is the other way a
|
|
389
|
+
* checked round gets quieter, and it gets the same operator surface: an
|
|
390
|
+
* overseer anomaly, raised ONCE per PR (`StateStore.anomalyRaised`) because
|
|
391
|
+
* the torn row is torn on every round until someone repairs it, and one wake
|
|
392
|
+
* per round would bury the anomalies that need reading. The event itself is
|
|
393
|
+
* logged on every withheld round — only the alert is deduplicated.
|
|
394
|
+
*/
|
|
395
|
+
async function ecosystemPreCheck(
|
|
396
|
+
deps: DispatchDeps,
|
|
397
|
+
eco: Ecosystem,
|
|
398
|
+
args: {
|
|
399
|
+
round_id: number;
|
|
400
|
+
pr: number;
|
|
401
|
+
files: PrFileEntry[];
|
|
402
|
+
head_sha: string;
|
|
403
|
+
base_ref: string;
|
|
404
|
+
base_sha: string;
|
|
405
|
+
},
|
|
406
|
+
): Promise<{
|
|
407
|
+
collisions: MigrationCollision[];
|
|
408
|
+
degraded: boolean;
|
|
409
|
+
commentWithheld: boolean;
|
|
410
|
+
commentUndelivered: boolean;
|
|
411
|
+
}> {
|
|
412
|
+
const outcome = await eco.preCheck(
|
|
413
|
+
{
|
|
414
|
+
logEvent: (kind, pr, payload) => deps.store.logEvent(kind, pr, payload),
|
|
415
|
+
isDryRun: deps.cfg.dry_run,
|
|
416
|
+
...(deps.adapter.listReviewsFull
|
|
417
|
+
? {
|
|
418
|
+
findCollisionComment: async (pr: number, marker: string, head: string) =>
|
|
419
|
+
(await deps.adapter.listReviewsFull!(pr)).some(
|
|
420
|
+
(review) =>
|
|
421
|
+
review.user === deps.cfg.github.bot_login &&
|
|
422
|
+
review.commit_id === head &&
|
|
423
|
+
review.body.split(/\r?\n/).includes(marker),
|
|
424
|
+
),
|
|
425
|
+
}
|
|
426
|
+
: {}),
|
|
427
|
+
migrationCollisionCommented: (pr, fingerprint) =>
|
|
428
|
+
deps.store.migrationCollisionCommented(pr, fingerprint, { live: !deps.cfg.dry_run }),
|
|
429
|
+
createReview: async (pr, payload, options) => {
|
|
430
|
+
// A throw after transport begins is ambiguous. Persist that BEFORE the
|
|
431
|
+
// gated call, then narrow it only from the gate's returned fact. The
|
|
432
|
+
// pre-fanout auto-retry class excludes ambiguous/executed rounds.
|
|
433
|
+
deps.store.recordRoundPreflightPosting(args.round_id, "ambiguous");
|
|
434
|
+
const result = await deps.adapter.createReview(pr, payload, options);
|
|
435
|
+
deps.store.recordRoundPreflightPosting(
|
|
436
|
+
args.round_id,
|
|
437
|
+
result.executed ? "executed" : result.dryRunFile ? "dryrun" : "refused",
|
|
438
|
+
);
|
|
439
|
+
return result;
|
|
440
|
+
},
|
|
441
|
+
...(deps.listBaseMigrations ? { listBaseMigrations: deps.listBaseMigrations } : {}),
|
|
442
|
+
...(deps.log ? { log: deps.log } : {}),
|
|
443
|
+
},
|
|
444
|
+
args,
|
|
445
|
+
);
|
|
446
|
+
return {
|
|
447
|
+
collisions: outcome.status === "checked" ? outcome.collisions : [],
|
|
448
|
+
degraded: outcome.status === "degraded",
|
|
449
|
+
commentWithheld: outcome.status === "checked" && outcome.comment_withheld === true,
|
|
450
|
+
commentUndelivered: outcome.status === "checked" && outcome.comment_undelivered === true,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* First-party directive detection + the deterministic escalation-mention pick,
|
|
456
|
+
* run per round after the worktree is prepared. Pure path predicate
|
|
457
|
+
* over the files API (`isFirstPartyDirectivePath`), plus one read-only git log
|
|
458
|
+
* at the round's MERGE BASE — pre-PR history, so the PR's own commits never
|
|
459
|
+
* elect the PR author as their own approver. The pick is logged
|
|
460
|
+
* (`escalation_mention_selected`) with its source; every failure degrades to
|
|
461
|
+
* the config fallback, never to a lost round.
|
|
462
|
+
*/
|
|
463
|
+
async function directiveEscalationFor(
|
|
464
|
+
deps: DispatchDeps,
|
|
465
|
+
args: { pr: number; files: PrFileEntry[]; merge_base: string; base_sha: string },
|
|
466
|
+
): Promise<{ files: string[]; mention: string } | undefined> {
|
|
467
|
+
const log = deps.log ?? (() => {});
|
|
468
|
+
// The vocabulary is the compiled-in default extended by [review.directives]
|
|
469
|
+
// — extension only, never a narrowing.
|
|
470
|
+
const directiveFiles = firstPartyDirectiveFiles(args.files, deps.cfg.review.directives);
|
|
471
|
+
if (directiveFiles.length === 0) return undefined;
|
|
472
|
+
// An unset [review] escalation_mention_default falls back to the owner's
|
|
473
|
+
// GitHub login — the config default itself stays "".
|
|
474
|
+
const fallback = deps.cfg.review.escalation_mention_default || deps.cfg.owner.github_login;
|
|
475
|
+
const sha = args.merge_base || args.base_sha;
|
|
476
|
+
let author: { login: string | null; name: string; email: string } | null = null;
|
|
477
|
+
if (deps.directiveAuthor && sha) {
|
|
478
|
+
try {
|
|
479
|
+
author = await deps.directiveAuthor(sha, directiveFiles);
|
|
480
|
+
} catch (err) {
|
|
481
|
+
log(`dispatch: directive-author lookup failed on #${args.pr}`, err);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const mention = author?.login ?? fallback;
|
|
485
|
+
deps.store.logEvent("escalation_mention_selected", args.pr, {
|
|
486
|
+
files: directiveFiles,
|
|
487
|
+
mention,
|
|
488
|
+
source: author?.login ? "git-log" : "config-default",
|
|
489
|
+
author_name: author?.name ?? null,
|
|
490
|
+
sha: sha || null,
|
|
491
|
+
});
|
|
492
|
+
return { files: directiveFiles, mention };
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Seed one round's prompt with memory: the guaranteed pinned head
|
|
497
|
+
* plus EXACTLY ONE budgeted context block, queried from the PR's title and
|
|
498
|
+
* diff shape.
|
|
499
|
+
*
|
|
500
|
+
* This is the single context call per round — one call site, one
|
|
501
|
+
* `assembleRoundMemory`, which itself calls `contextBlock` once. It runs after
|
|
502
|
+
* the worktree is prepared and the order is built, so the context is seeded
|
|
503
|
+
* from the same point the passes read.
|
|
504
|
+
*
|
|
505
|
+
* It cannot fail a round. `assembleRoundMemory` is total (deadline + swallowed
|
|
506
|
+
* throws); the extra try/catch here is belt-and-braces for a client that
|
|
507
|
+
* breaks the contract, and the outcome is identical either way: log the gap,
|
|
508
|
+
* tell the memory health monitor (`seed.onSeeded`), dispatch memory-less.
|
|
509
|
+
*
|
|
510
|
+
* There is no per-round memory anomaly: the health monitor
|
|
511
|
+
* (src/memory/health.ts) owns telling a person, on state transitions rather
|
|
512
|
+
* than once per round. The `round_memory_seeded` event is the per-round
|
|
513
|
+
* record, and it is what the monitor is fed from.
|
|
514
|
+
*/
|
|
515
|
+
async function seedRoundMemory(
|
|
516
|
+
deps: DispatchDeps,
|
|
517
|
+
order: RoundOrder,
|
|
518
|
+
files: PrFileEntry[],
|
|
519
|
+
/** Told once the round's context search was issued (the lesson finds
|
|
520
|
+
* under `delivery = "on"` wait for it). Optional. */
|
|
521
|
+
onContextSent?: () => void,
|
|
522
|
+
): Promise<RoundMemory> {
|
|
523
|
+
const seed = deps.memory;
|
|
524
|
+
if (!seed) return MEMORY_LESS;
|
|
525
|
+
const log = deps.log ?? (() => {});
|
|
526
|
+
const query = roundContextQuery({ title: order.title, files });
|
|
527
|
+
// The session's own recall searches for the same thing.
|
|
528
|
+
try {
|
|
529
|
+
seed.publishQuery?.(order.pr, query);
|
|
530
|
+
} catch (err) {
|
|
531
|
+
log(`dispatch: #${order.pr} recall hint not published (ignored)`, err);
|
|
532
|
+
}
|
|
533
|
+
const timeoutMs = seed.timeoutMs ?? ROUND_CONTEXT_DEFAULTS.timeoutMs;
|
|
534
|
+
// The seed's search waits — inside its own budget, never past
|
|
535
|
+
// it — until the case writer's last batch is estimated done embedding, so
|
|
536
|
+
// it is not queued behind those embeddings on the one-thread embedder.
|
|
537
|
+
const activity = seed.activity;
|
|
538
|
+
const quiet =
|
|
539
|
+
activity && typeof activity.whenEmbedderQuiet === "function"
|
|
540
|
+
? (maxWaitMs: number) => activity.whenEmbedderQuiet!(maxWaitMs)
|
|
541
|
+
: undefined;
|
|
542
|
+
let mem: RoundMemory;
|
|
543
|
+
try {
|
|
544
|
+
mem = await assembleRoundMemory(seed.assembler, query, {
|
|
545
|
+
...(seed.maxTokens !== undefined ? { maxTokens: seed.maxTokens } : {}),
|
|
546
|
+
timeoutMs,
|
|
547
|
+
log,
|
|
548
|
+
...(onContextSent ? { onContextSent } : {}),
|
|
549
|
+
...(quiet ? { quiet } : {}),
|
|
550
|
+
...(seed.searchReserveMs !== undefined ? { searchReserveMs: seed.searchReserveMs } : {}),
|
|
551
|
+
});
|
|
552
|
+
} catch (err) {
|
|
553
|
+
log(`dispatch: #${order.pr} memory seeding threw — round proceeds memory-less`, err);
|
|
554
|
+
mem = {
|
|
555
|
+
...MEMORY_LESS,
|
|
556
|
+
asked: true,
|
|
557
|
+
query,
|
|
558
|
+
max_tokens: seed.maxTokens ?? ROUND_CONTEXT_DEFAULTS.maxTokens,
|
|
559
|
+
degraded: true,
|
|
560
|
+
gaps: [{ part: "head", reason: `error: ${String(err)}` }],
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
const headState = pinnedHeadState(mem);
|
|
564
|
+
// The baseline for the one transition that is silent by construction: a
|
|
565
|
+
// readable store whose pinned area went from populated to empty. Read
|
|
566
|
+
// BEFORE this round's row is written, and only when it could matter.
|
|
567
|
+
const previousHead = headState === "empty" ? deps.store.lastReadPinnedHead() : null;
|
|
568
|
+
deps.store.logEvent("round_memory_seeded", order.pr, {
|
|
569
|
+
round_id: order.round_id,
|
|
570
|
+
round_no: order.round_no,
|
|
571
|
+
query: mem.query,
|
|
572
|
+
max_tokens: mem.max_tokens,
|
|
573
|
+
head_state: headState,
|
|
574
|
+
head_chars: mem.head.length,
|
|
575
|
+
block_chars: mem.block.length,
|
|
576
|
+
clamped: mem.clamped,
|
|
577
|
+
degraded: mem.degraded,
|
|
578
|
+
gaps: mem.gaps,
|
|
579
|
+
ms: mem.ms,
|
|
580
|
+
// How long the search waited for the case writer's embedding tail,
|
|
581
|
+
// inside `ms` (absent when no tracker is wired).
|
|
582
|
+
...(mem.quiet_wait_ms !== undefined ? { quiet_wait_ms: mem.quiet_wait_ms } : {}),
|
|
583
|
+
});
|
|
584
|
+
if (previousHead && previousHead.head_chars > 0) {
|
|
585
|
+
// Empty is not degraded: a genuinely empty pinned area is a healthy
|
|
586
|
+
// answer — right for a fresh install, wrong for a container recreated
|
|
587
|
+
// without its data volume. One anomaly on the transition itself carries
|
|
588
|
+
// that signal; the next round's baseline is this empty row, so it cannot
|
|
589
|
+
// repeat. An owner who unpinned their last standing instruction sees it
|
|
590
|
+
// once too, and knows why.
|
|
591
|
+
deps.store.logAnomaly(
|
|
592
|
+
"pinned_head_emptied",
|
|
593
|
+
order.pr,
|
|
594
|
+
order.round_no,
|
|
595
|
+
`the pinned area read as EMPTY with the store readable; the last seeded round (#${previousHead.pr ?? "?"} at ${previousHead.ts}) carried ${previousHead.head_chars} chars of standing instructions`,
|
|
596
|
+
[
|
|
597
|
+
`ANOMALY (memory) — PR #${order.pr} round ${order.round_no}`,
|
|
598
|
+
`The memory store answered, and the pinned area is EMPTY. At the last seeded round (#${previousHead.pr ?? "?"}, ${previousHead.ts}) it held ${previousHead.head_chars} chars of standing instructions.`,
|
|
599
|
+
"If nobody unpinned them on purpose, the store lost its data — a container recreated without its state volume looks exactly like this. " +
|
|
600
|
+
"Check the openviking data directory on the state volume and the TUI's memory tab. The round was dispatched normally; this fires once, on the transition.",
|
|
601
|
+
].join("\n"),
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
try {
|
|
605
|
+
seed.onSeeded?.(mem);
|
|
606
|
+
} catch (err) {
|
|
607
|
+
log(`dispatch: #${order.pr} memory health observer threw (ignored)`, err);
|
|
608
|
+
}
|
|
609
|
+
if (mem.degraded) {
|
|
610
|
+
log(`dispatch: #${order.pr} round ${order.round_no} seeded memory-less (${describeGaps(mem)})`);
|
|
611
|
+
}
|
|
612
|
+
return mem;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Open the round's memory window (`RoundMemorySeed.activity`):
|
|
617
|
+
* from just before the seed until the round's lesson selection has taken
|
|
618
|
+
* over, the case writer starts no batch, so no case embedding is queued in
|
|
619
|
+
* front of the round's own reads on the server's one-thread embedder. Returns
|
|
620
|
+
* its idempotent `end`; total — a broken tracker is a no-op, never a throw.
|
|
621
|
+
*/
|
|
622
|
+
function openMemoryWindow(
|
|
623
|
+
deps: DispatchDeps,
|
|
624
|
+
pr: number,
|
|
625
|
+
log: (msg: string, extra?: unknown) => void,
|
|
626
|
+
): () => void {
|
|
627
|
+
const activity = deps.memory?.activity;
|
|
628
|
+
if (!activity) return () => {};
|
|
629
|
+
try {
|
|
630
|
+
const end = activity.begin(`round #${pr}`);
|
|
631
|
+
return () => {
|
|
632
|
+
try {
|
|
633
|
+
end();
|
|
634
|
+
} catch (err) {
|
|
635
|
+
log(`dispatch: #${pr} memory window not closed (ignored)`, err);
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
} catch (err) {
|
|
639
|
+
log(`dispatch: #${pr} memory window not opened (ignored)`, err);
|
|
640
|
+
return () => {};
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/** `[memory] delivery = "on"` with a hook that can deliver. Total: a
|
|
645
|
+
* throwing hook is "not delivering" — the shadow path. */
|
|
646
|
+
function lessonsDelivering(
|
|
647
|
+
deps: DispatchDeps,
|
|
648
|
+
log: (msg: string, extra?: unknown) => void,
|
|
649
|
+
): boolean {
|
|
650
|
+
const hook = deps.lessons;
|
|
651
|
+
if (!hook || typeof hook.select !== "function" || typeof hook.delivering !== "function")
|
|
652
|
+
return false;
|
|
653
|
+
try {
|
|
654
|
+
return hook.delivering() === true;
|
|
655
|
+
} catch (err) {
|
|
656
|
+
log("dispatch: lesson delivery switch unreadable — delivering nothing (ignored)", err);
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/** The round's selection under `on`, awaited beside the seed. Total: the
|
|
662
|
+
* hook bounds it by its prefetch budget; a throw or a rejection is `null`
|
|
663
|
+
* and the round renders as if nothing were selected. */
|
|
664
|
+
async function selectRoundLessons(
|
|
665
|
+
deps: DispatchDeps,
|
|
666
|
+
order: RoundOrder,
|
|
667
|
+
files: PrFileEntry[],
|
|
668
|
+
after: Promise<void>,
|
|
669
|
+
): Promise<RoundSelectionResult | null> {
|
|
670
|
+
try {
|
|
671
|
+
return (await deps.lessons?.select?.(order, files, { after })) ?? null;
|
|
672
|
+
} catch (err) {
|
|
673
|
+
(deps.log ?? (() => {}))(
|
|
674
|
+
`dispatch: #${order.pr} lesson selection failed — no lessons this round (ignored)`,
|
|
675
|
+
err,
|
|
676
|
+
);
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Hand the round's delivered lessons to its prompt: write the
|
|
683
|
+
* lessons files (the validator's, and each lens the session may add) and
|
|
684
|
+
* return the seed carrying the delivery — or the seed untouched when nothing
|
|
685
|
+
* was selected, so the prompt keeps the standard bleed rule verbatim. Total: a
|
|
686
|
+
* lessons file that cannot be written is simply not named in the prompt.
|
|
687
|
+
*/
|
|
688
|
+
function deliverRoundLessons(
|
|
689
|
+
deps: DispatchDeps,
|
|
690
|
+
input: {
|
|
691
|
+
seeded: RoundMemory;
|
|
692
|
+
delivery: RoundLessonDelivery | null;
|
|
693
|
+
dir: string;
|
|
694
|
+
},
|
|
695
|
+
): RoundMemory {
|
|
696
|
+
const log = deps.log ?? (() => {});
|
|
697
|
+
let d = input.delivery;
|
|
698
|
+
if (!d || !deliversAnything(d)) return input.seeded;
|
|
699
|
+
try {
|
|
700
|
+
d = writeLessonFiles(input.dir, d);
|
|
701
|
+
} catch (err) {
|
|
702
|
+
log(`dispatch: #${d.pr} lessons files unavailable — the round names none`, err);
|
|
703
|
+
}
|
|
704
|
+
// A lessons file that was not written delivers nothing: neither the index
|
|
705
|
+
// nor the bleed rule may claim it did.
|
|
706
|
+
d = onlyWritten(d);
|
|
707
|
+
return deliversAnything(d) ? { ...input.seeded, lessons: d } : input.seeded;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/** The `lesson_delivered` record — written once the prompt carrying the
|
|
711
|
+
* delivery was handed to the session. */
|
|
712
|
+
function logLessonDelivery(store: StateStore, roundId: number, d: RoundLessonDelivery): void {
|
|
713
|
+
const counts = deliveryCounts(d);
|
|
714
|
+
store.logEvent("lesson_delivered", d.pr, {
|
|
715
|
+
round_id: roundId,
|
|
716
|
+
round_no: d.round_no,
|
|
717
|
+
lessons: counts.lessons,
|
|
718
|
+
cases: counts.cases,
|
|
719
|
+
rows: d.rows.filter((r) => r.lessons.length > 0).length,
|
|
720
|
+
validator: d.validator?.lessons.length ?? 0,
|
|
721
|
+
validator_file: d.validator?.file !== undefined,
|
|
722
|
+
addable_files: d.addable.filter((a) => a.file !== undefined).map((a) => a.lens),
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/** The trivial-delta short-circuit's answer for one system-provenance re-trigger. */
|
|
727
|
+
type ShortCircuit =
|
|
728
|
+
| { skip: false }
|
|
729
|
+
| {
|
|
730
|
+
skip: true;
|
|
731
|
+
reason: "unchanged-head" | "empty-delta" | "trivial-delta";
|
|
732
|
+
class: DeltaClass;
|
|
733
|
+
files: string[];
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Classify the delta (last reviewed head → current head) for a
|
|
738
|
+
* SYSTEM-provenance re-trigger, using the one recheck classifier
|
|
739
|
+
* (`recheck.classifyDelta`) — never a copy. Every uncertainty resolves to
|
|
740
|
+
* "run the full round": an unknown head, a missing compare seam, or a failed
|
|
741
|
+
* read must never quietly swallow a review.
|
|
742
|
+
*/
|
|
743
|
+
async function classifySystemRetrigger(
|
|
744
|
+
deps: DispatchDeps,
|
|
745
|
+
row: PrStateRow,
|
|
746
|
+
mark: ReviewedHeadRow,
|
|
747
|
+
): Promise<ShortCircuit> {
|
|
748
|
+
const log = deps.log ?? (() => {});
|
|
749
|
+
// Unknown head is always "new" — the same reading guards.hasNewEvidence takes.
|
|
750
|
+
if (!row.head_sha) return { skip: false };
|
|
751
|
+
if (row.head_sha === mark.head_sha) {
|
|
752
|
+
// Same head. An author reply pending on the mark is a person's ask (the
|
|
753
|
+
// rule guard 2 reads) → full round; anything else would be a full round
|
|
754
|
+
// whose output posting-time guard 2 throws away.
|
|
755
|
+
if (hasNewEvidence(mark, row.head_sha)) return { skip: false };
|
|
756
|
+
return { skip: true, reason: "unchanged-head", class: "empty", files: [] };
|
|
757
|
+
}
|
|
758
|
+
if (!deps.adapter.compareFiles) return { skip: false };
|
|
759
|
+
try {
|
|
760
|
+
const delta = await deps.adapter.compareFiles(mark.head_sha, row.head_sha);
|
|
761
|
+
// The union with the reviewed head's ownership: a push reverting a
|
|
762
|
+
// reviewed file is the PR's own change, not a base merge.
|
|
763
|
+
// A truncated listing leaves ownership unknown (undefined).
|
|
764
|
+
const prFiles = await reviewedOwnership(
|
|
765
|
+
deps.adapter,
|
|
766
|
+
row.number,
|
|
767
|
+
mark.head_sha,
|
|
768
|
+
delta,
|
|
769
|
+
ownershipSet(await deps.adapter.listPrFiles(row.number)),
|
|
770
|
+
);
|
|
771
|
+
// The recheck's own merge check: a degraded one keeps the delta-only
|
|
772
|
+
// classification but is never silently quieter.
|
|
773
|
+
const resolvedFiles = new Set(
|
|
774
|
+
await verifyMerges(
|
|
775
|
+
deps.store,
|
|
776
|
+
deps.resolvedFilesInMerges,
|
|
777
|
+
mark.head_sha,
|
|
778
|
+
row.head_sha,
|
|
779
|
+
row.number,
|
|
780
|
+
"dispatch",
|
|
781
|
+
),
|
|
782
|
+
);
|
|
783
|
+
const cls = classifyDelta(delta, {
|
|
784
|
+
...(prFiles ? { prFiles } : {}),
|
|
785
|
+
resolvedFiles,
|
|
786
|
+
});
|
|
787
|
+
if (cls.class === "significant") return { skip: false };
|
|
788
|
+
return {
|
|
789
|
+
skip: true,
|
|
790
|
+
reason: cls.class === "empty" ? "empty-delta" : "trivial-delta",
|
|
791
|
+
class: cls.class,
|
|
792
|
+
files: cls.files.map((f) => `${f.filename} (${f.reason})`),
|
|
793
|
+
};
|
|
794
|
+
} catch (err) {
|
|
795
|
+
log(`dispatch: #${row.number} delta classification failed — full round`, err);
|
|
796
|
+
deps.store.logEvent("dispatch_classifier_failed", row.number, { error: String(err) });
|
|
797
|
+
return { skip: false };
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/** Consume both triggers at dispatch: the label and the team review request.
|
|
802
|
+
* Both go through the adapter (and therefore the dry-run gate); failures are
|
|
803
|
+
* logged, never fatal — the reconcile poll re-derives the truth. Returns the
|
|
804
|
+
* triggers really consumed: one the gate recorded instead of executing (dry
|
|
805
|
+
* run) or a guard refused is still on GitHub, and is logged as such. */
|
|
806
|
+
async function consumeTriggers(
|
|
807
|
+
deps: DispatchDeps,
|
|
808
|
+
pr: number,
|
|
809
|
+
/** The no-new-evidence skip consumes the triggers without starting
|
|
810
|
+
* a round, so it must not paint the PR "AI review in progress". */
|
|
811
|
+
opts: { markInProgress?: boolean } = {},
|
|
812
|
+
): Promise<string[]> {
|
|
813
|
+
const consumed: string[] = [];
|
|
814
|
+
const log = deps.log ?? (() => {});
|
|
815
|
+
/** One trigger: consumed, already gone (consumed too), not executed, or
|
|
816
|
+
* failed — logged. */
|
|
817
|
+
const consume = async (
|
|
818
|
+
which: "label" | "team_request",
|
|
819
|
+
what: string,
|
|
820
|
+
op: () => Promise<GateResult | undefined>,
|
|
821
|
+
): Promise<void> => {
|
|
822
|
+
try {
|
|
823
|
+
const result = await op();
|
|
824
|
+
if (result?.executed === false) {
|
|
825
|
+
deps.store.logEvent("trigger_consume_not_executed", pr, {
|
|
826
|
+
which,
|
|
827
|
+
...(result.dryRunFile ? { dry_run: true } : {}),
|
|
828
|
+
...(result.blocked ? { blocked: result.blocked } : {}),
|
|
829
|
+
});
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
consumed.push(which);
|
|
833
|
+
} catch (err) {
|
|
834
|
+
if (alreadyConsumed(err)) {
|
|
835
|
+
consumed.push(which);
|
|
836
|
+
deps.store.logEvent("trigger_already_consumed", pr, { which });
|
|
837
|
+
} else {
|
|
838
|
+
log(`dispatch: ${what} failed on #${pr}`, err);
|
|
839
|
+
deps.store.logEvent("trigger_consume_failed", pr, { which, error: String(err) });
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
await consume("label", "label removal", () =>
|
|
844
|
+
deps.adapter.removeLabel(pr, deps.cfg.github.trigger_label),
|
|
845
|
+
);
|
|
846
|
+
await consume("team_request", "team-request DELETE", () =>
|
|
847
|
+
deps.adapter.deleteTeamReviewRequest(pr),
|
|
848
|
+
);
|
|
849
|
+
if (opts.markInProgress !== false) {
|
|
850
|
+
try {
|
|
851
|
+
await deps.adapter.addLabel(pr, deps.cfg.github.in_progress_label);
|
|
852
|
+
} catch (err) {
|
|
853
|
+
log(`dispatch: in-progress label failed on #${pr}`, err);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return consumed;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* A withheld approval's retry round that ended without deciding anything —
|
|
861
|
+
* cancelled by a restart's orphan sweep, a self-requeue or an operator, or
|
|
862
|
+
* held FAILED_LOUD until the operator's `u` — still owes the approval:
|
|
863
|
+
* dispatch spent the mark at that round's handoff, and the round that would
|
|
864
|
+
* have re-decided it never did. The mark is re-recorded here from the spend's
|
|
865
|
+
* own record (`approval_retry_dispatched`), so the round that replaces it
|
|
866
|
+
* runs instead of being skipped as no new evidence. A round that decided (a
|
|
867
|
+
* verdict, a guard-2 no-op) or is still running owes nothing; neither does a
|
|
868
|
+
* spend at another head or one a later round has overtaken.
|
|
869
|
+
*
|
|
870
|
+
* Only a round that PROVABLY posted nothing is restored: a restored mark
|
|
871
|
+
* takes the replacement round past guard 2's daemon half, and the approval
|
|
872
|
+
* route has no guard 2 of its own, so a round whose APPROVE went out would
|
|
873
|
+
* post a second one. So the round has no posting receipt but a guard-2
|
|
874
|
+
* refusal (a `prepared` or `deferred` one is unknown, never "not posted"), its
|
|
875
|
+
* row records no posting, and the reviewed-head mark has not been stamped
|
|
876
|
+
* since it started.
|
|
877
|
+
*/
|
|
878
|
+
function restoreInterruptedApprovalRetry(
|
|
879
|
+
store: DispatchDeps["store"],
|
|
880
|
+
row: Pick<PrStateRow, "number" | "head_sha">,
|
|
881
|
+
postingReceiptDir: string,
|
|
882
|
+
): ApprovalWithheldRow | undefined {
|
|
883
|
+
const spent = store.db
|
|
884
|
+
.prepare(
|
|
885
|
+
"SELECT payload FROM events WHERE pr = ? AND kind = 'approval_retry_dispatched' ORDER BY id DESC LIMIT 1",
|
|
886
|
+
)
|
|
887
|
+
.get(row.number) as { payload: string } | undefined;
|
|
888
|
+
if (!spent) return undefined;
|
|
889
|
+
let record: { head_sha?: unknown; round_id?: unknown; ci_state?: unknown; reason?: unknown };
|
|
890
|
+
try {
|
|
891
|
+
record = JSON.parse(spent.payload) as typeof record;
|
|
892
|
+
} catch {
|
|
893
|
+
return undefined;
|
|
894
|
+
}
|
|
895
|
+
if (record.head_sha !== row.head_sha || typeof record.round_id !== "number") return undefined;
|
|
896
|
+
const round = store.roundsFor(row.number).find((r) => r.id === record.round_id);
|
|
897
|
+
if (!round || round.status === "running" || round.status === "done") return undefined;
|
|
898
|
+
if (round.status === "cancelled" && round.cancel_cause === "no_new_evidence") return undefined;
|
|
899
|
+
if (store.hasLaterRound(row.number, round.round_no)) return undefined;
|
|
900
|
+
// A receipt file that cannot be read is unknown too.
|
|
901
|
+
if (existsSync(postingReceiptPath(postingReceiptDir, row.number, round.round_no))) {
|
|
902
|
+
const receipt = readPostingReceipt(postingReceiptDir, row.number, round.round_no);
|
|
903
|
+
if (receipt?.status !== "skipped") return undefined;
|
|
904
|
+
}
|
|
905
|
+
if (round.posted_event || round.gh_review_id !== null) return undefined;
|
|
906
|
+
const mark = store.reviewedHead(row.number);
|
|
907
|
+
if (mark && mark.reviewed_at >= round.started_at) return undefined;
|
|
908
|
+
store.recordApprovalWithheld(row.number, {
|
|
909
|
+
head_sha: row.head_sha,
|
|
910
|
+
ci_state: typeof record.ci_state === "string" ? record.ci_state : null,
|
|
911
|
+
reason: typeof record.reason === "string" ? record.reason : "red-ci",
|
|
912
|
+
});
|
|
913
|
+
store.logEvent("approval_retry_restored", row.number, {
|
|
914
|
+
head_sha: row.head_sha,
|
|
915
|
+
round_id: round.id,
|
|
916
|
+
round_no: round.round_no,
|
|
917
|
+
round_status: round.status,
|
|
918
|
+
cancel_cause: round.cancel_cause ?? null,
|
|
919
|
+
});
|
|
920
|
+
return store.approvalWithheld(row.number);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Did a webhook move this PR while dispatch awaited GitHub? The skip paths
|
|
925
|
+
* decide from the row they read, then await trigger consumption (two GitHub
|
|
926
|
+
* round-trips) before settling the PR back onto its standing verdict. A push
|
|
927
|
+
* or a fresh review request applied in that window updates the QUEUED row —
|
|
928
|
+
* nothing records a successor for a row that is not `reviewing` — and
|
|
929
|
+
* settling it would erase that newer review debt: the stale standing verdict
|
|
930
|
+
* replayed, the new trigger cleared. So a moved row is left queued for the
|
|
931
|
+
* next pump, which decides again from what it now says. A row that left the
|
|
932
|
+
* queue (a close, the operator) is not settled either: it is no longer this
|
|
933
|
+
* dispatch's to decide. Returns the reason, or null when the row still says
|
|
934
|
+
* what the decision read.
|
|
935
|
+
*/
|
|
936
|
+
function movedDuringConsume(
|
|
937
|
+
store: DispatchDeps["store"],
|
|
938
|
+
row: Pick<PrStateRow, "number" | "head_sha" | "trigger_ts" | "trigger_provenance">,
|
|
939
|
+
): string | null {
|
|
940
|
+
const now = store.getPr(row.number);
|
|
941
|
+
if (!now) return null;
|
|
942
|
+
if (now.state !== "queued") return "left_queue";
|
|
943
|
+
if (now.head_sha !== row.head_sha) return "head_moved";
|
|
944
|
+
// A person's ask on a queued row keeps its trigger time and upgrades only
|
|
945
|
+
// the provenance — a re-request the system short-circuit must not settle.
|
|
946
|
+
if (now.trigger_ts !== row.trigger_ts || now.trigger_provenance !== row.trigger_provenance)
|
|
947
|
+
return "retriggered";
|
|
948
|
+
return null;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function logSupersededSettlement(
|
|
952
|
+
deps: DispatchDeps,
|
|
953
|
+
row: { number: number; head_sha: string; trigger_ts: string | null },
|
|
954
|
+
path: string,
|
|
955
|
+
reason: string,
|
|
956
|
+
): void {
|
|
957
|
+
const now = deps.store.getPr(row.number);
|
|
958
|
+
deps.store.logEvent("dispatch_settlement_superseded", row.number, {
|
|
959
|
+
path,
|
|
960
|
+
reason,
|
|
961
|
+
decided_head_sha: row.head_sha,
|
|
962
|
+
decided_trigger_ts: row.trigger_ts,
|
|
963
|
+
head_sha: now?.head_sha ?? null,
|
|
964
|
+
trigger_ts: now?.trigger_ts ?? null,
|
|
965
|
+
});
|
|
966
|
+
deps.log?.(
|
|
967
|
+
`dispatch: #${row.number} ${path} not settled — the PR moved while its triggers were consumed (${reason}); left queued`,
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
export { ALL_PASSES } from "@quickchatai/pi-quickreview/src/passes.js";
|
|
972
|
+
|
|
973
|
+
// The review planner moved to packages/pi-plugin/src/planner.ts (the review core); re-exported so
|
|
974
|
+
// the app's importers keep their one module.
|
|
975
|
+
export {
|
|
976
|
+
addTierOf,
|
|
977
|
+
buildPassPlan,
|
|
978
|
+
buildRoundOrder,
|
|
979
|
+
decideFollowUp,
|
|
980
|
+
followUpSweepOwed,
|
|
981
|
+
LEAN_ADDABLE_PASSES,
|
|
982
|
+
leanFollowUpPlan,
|
|
983
|
+
lensesOf,
|
|
984
|
+
optionalOrderOf,
|
|
985
|
+
planFollowUp,
|
|
986
|
+
renderDeltaPatch,
|
|
987
|
+
reviewedHeadOf,
|
|
988
|
+
roundDeltaOf,
|
|
989
|
+
slowestFirst,
|
|
990
|
+
sweepOrderOf,
|
|
991
|
+
validatorTier,
|
|
992
|
+
type FollowUpDecision,
|
|
993
|
+
type FollowUpFullReason,
|
|
994
|
+
type FollowUpHistory,
|
|
995
|
+
type PassPlan,
|
|
996
|
+
type PreparedTree,
|
|
997
|
+
type PrOwnedDeltaReader,
|
|
998
|
+
} from "@quickchatai/pi-quickreview/src/planner.js";
|
|
999
|
+
|
|
1000
|
+
/** What the round row records of a plan — the same projection on the
|
|
1001
|
+
* normal path and on the patch-write fallback, so the stored round and the
|
|
1002
|
+
* order always describe one plan. A follow-up round's optional rows ride
|
|
1003
|
+
* the addable record, so the executed-pass telemetry reads a run one as
|
|
1004
|
+
* offered work rather than an unknown addition. */
|
|
1005
|
+
function recordedPlan(
|
|
1006
|
+
cfg: Config,
|
|
1007
|
+
plan: PassPlan,
|
|
1008
|
+
optionalPlan: readonly PassPlanEntry[] = [],
|
|
1009
|
+
): {
|
|
1010
|
+
plannedPasses: PassPlanEntry[];
|
|
1011
|
+
lenses: string[];
|
|
1012
|
+
addablePasses: RecordedAddablePass[];
|
|
1013
|
+
validateTier: PassTier | null;
|
|
1014
|
+
} {
|
|
1015
|
+
const plannedPasses = plan.pass_plan;
|
|
1016
|
+
const optional: RecordedAddablePass[] = optionalPlan.map((p) => ({
|
|
1017
|
+
lens: p.lens,
|
|
1018
|
+
tier: p.tier,
|
|
1019
|
+
optional: true,
|
|
1020
|
+
raisable_to: p.raisable_to,
|
|
1021
|
+
}));
|
|
1022
|
+
return {
|
|
1023
|
+
plannedPasses,
|
|
1024
|
+
lenses: lensesOf(plannedPasses),
|
|
1025
|
+
addablePasses: [
|
|
1026
|
+
...plan.addable_lenses.map((lens) => ({ lens, tier: addTierOf(cfg, lens) })),
|
|
1027
|
+
...optional,
|
|
1028
|
+
],
|
|
1029
|
+
// Null when the profile switched the validator off: the round had no
|
|
1030
|
+
// validator, and recording the configured tier would say it did.
|
|
1031
|
+
validateTier: plannedPasses.find((p) => p.lens === VALIDATE_PASS)?.tier ?? null,
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// A reconcile, webhook and periodic tick may all ask for a pump at once. The
|
|
1036
|
+
// store is the daemon-instance identity; joiners wait for the active pump and
|
|
1037
|
+
// return no duplicate orders. This closes the race where two pumps both saw a
|
|
1038
|
+
// queued PR/capacity slot and created two rounds for it.
|
|
1039
|
+
const activePumps = new WeakMap<StateStore, Promise<RoundOrder[]>>();
|
|
1040
|
+
|
|
1041
|
+
/** One serialized dispatch pump: drain the queue up to the concurrency cap. */
|
|
1042
|
+
export async function dispatchOnce(deps: DispatchDeps): Promise<RoundOrder[]> {
|
|
1043
|
+
const active = activePumps.get(deps.store);
|
|
1044
|
+
if (active) {
|
|
1045
|
+
await active;
|
|
1046
|
+
return [];
|
|
1047
|
+
}
|
|
1048
|
+
const pump = dispatchUnlocked(deps);
|
|
1049
|
+
activePumps.set(deps.store, pump);
|
|
1050
|
+
try {
|
|
1051
|
+
return await pump;
|
|
1052
|
+
} finally {
|
|
1053
|
+
if (activePumps.get(deps.store) === pump) activePumps.delete(deps.store);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
async function dispatchUnlocked(deps: DispatchDeps): Promise<RoundOrder[]> {
|
|
1058
|
+
const { store, cfg } = deps;
|
|
1059
|
+
const eco = deps.ecosystem ?? DJANGO_ECOSYSTEM;
|
|
1060
|
+
const now = deps.now ?? (() => new Date());
|
|
1061
|
+
const render =
|
|
1062
|
+
deps.renderPrompt ?? ((o: RoundOrder, _m: RoundMemory) => JSON.stringify(o, null, 2));
|
|
1063
|
+
const worktreeFor = deps.worktreeFor ?? defaultWorktree;
|
|
1064
|
+
const ledgerCarry =
|
|
1065
|
+
deps.ledgerCarry ?? ((): { carry?: LedgerCarry; settled: string } => ({ settled: "" }));
|
|
1066
|
+
const log = deps.log ?? (() => {});
|
|
1067
|
+
|
|
1068
|
+
// Deploy-window drain: the pump starts NOTHING while draining.
|
|
1069
|
+
// In-flight rounds keep running to their natural end; queued rows simply
|
|
1070
|
+
// wait and dispatch when the drain lifts. The refusal stays quiet at the
|
|
1071
|
+
// 15s tick — the drain itself is loud (status.json, qcr status, the board
|
|
1072
|
+
// banner, the drain_on event).
|
|
1073
|
+
if (store.drainState().draining) return [];
|
|
1074
|
+
|
|
1075
|
+
const slots = capacity(store, cfg);
|
|
1076
|
+
if (slots <= 0) return [];
|
|
1077
|
+
|
|
1078
|
+
const dispatched: RoundOrder[] = [];
|
|
1079
|
+
/** Why this queued PR may not dispatch now, or null. */
|
|
1080
|
+
const heldBack = (row: PrStateRow): string | null => {
|
|
1081
|
+
// One PR = one worktree = at most one round in flight. A queued row with a
|
|
1082
|
+
// round still running (requeue raced the verdict) waits for the next pump —
|
|
1083
|
+
// two rounds sharing wt/pr-<n> fight over the git index.
|
|
1084
|
+
if (store.hasRunningRound(row.number)) return "skipped — a round is already running";
|
|
1085
|
+
const failure = store.openReviewFailure(row.number);
|
|
1086
|
+
if (failure) {
|
|
1087
|
+
const action =
|
|
1088
|
+
failure.next_action === "retry_wait" && failure.next_retry_at
|
|
1089
|
+
? `automatic retry at ${failure.next_retry_at}`
|
|
1090
|
+
: "operator u required";
|
|
1091
|
+
return `held FAILED_LOUD (${failure.incident_id}); ${action}`;
|
|
1092
|
+
}
|
|
1093
|
+
const active = store.blockingSessionCommand(`pr-${row.number}`);
|
|
1094
|
+
if (active) return `skipped — session command ${active.id} is ${active.status}`;
|
|
1095
|
+
// The operator holds the session (src/takeover.ts): the round waits for the release.
|
|
1096
|
+
// A debug lease (src/debuground.ts) is armed for exactly this round: it goes out,
|
|
1097
|
+
// and the worker host hands it to the debug pi in place of a worker.
|
|
1098
|
+
const lease = store.hostLease(row.number);
|
|
1099
|
+
if (lease && lease.kind !== "debug")
|
|
1100
|
+
return `skipped — under a ${lease.kind} lease (${lease.holder})`;
|
|
1101
|
+
if (store.hasHeldWakes(row.number))
|
|
1102
|
+
return "skipped — held conversational wakes must drain first";
|
|
1103
|
+
return null;
|
|
1104
|
+
};
|
|
1105
|
+
const eligible = queuedInOrder(store).filter((row) => {
|
|
1106
|
+
const why = heldBack(row);
|
|
1107
|
+
if (why) log(`dispatch: #${row.number} ${why}`);
|
|
1108
|
+
return why === null;
|
|
1109
|
+
});
|
|
1110
|
+
// A slot is spent by a round that went running — handed off, or retained
|
|
1111
|
+
// behind its durable command — never by a PR this pump skipped, settled or
|
|
1112
|
+
// failed to prepare: one PR failing every tick must not starve the queue
|
|
1113
|
+
// behind it.
|
|
1114
|
+
let started = 0;
|
|
1115
|
+
for (const queued of eligible) {
|
|
1116
|
+
if (started >= slots) break;
|
|
1117
|
+
// Eligibility holds when the work starts, not only when the pump listed
|
|
1118
|
+
// the queue: every PR before this one awaited GitHub, and a drain, a
|
|
1119
|
+
// close, a pause or a fresh hold may have landed meanwhile. The row is
|
|
1120
|
+
// read again, so the round is decided from what it says now.
|
|
1121
|
+
if (store.drainState().draining) break;
|
|
1122
|
+
const row = store.getPr(queued.number);
|
|
1123
|
+
if (!row || row.state !== "queued" || row.paused) {
|
|
1124
|
+
log(`dispatch: #${queued.number} skipped — no longer queued (${row?.state ?? "gone"})`);
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
const why = heldBack(row);
|
|
1128
|
+
if (why) {
|
|
1129
|
+
log(`dispatch: #${row.number} ${why}`);
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
let provisionalRoundId: number | undefined;
|
|
1133
|
+
// The round's memory window (opened before the seed, below).
|
|
1134
|
+
let endMemoryWindow: () => void = () => {};
|
|
1135
|
+
// Spends the withheld-approval mark this round inherits (set below).
|
|
1136
|
+
let spendWithholding: (roundId: number) => void = () => {};
|
|
1137
|
+
// Whether this dispatch painted the PR "in progress" (trigger consumption
|
|
1138
|
+
// for a round, below): a failure before any round owns the review takes
|
|
1139
|
+
// the label off again.
|
|
1140
|
+
let markedInProgress = false;
|
|
1141
|
+
try {
|
|
1142
|
+
// Backfilled (imported) rounds are excluded: a historical review must
|
|
1143
|
+
// never flip a still-open PR's next live round to "re-review", whose
|
|
1144
|
+
// template references a settled digest that never existed.
|
|
1145
|
+
const priorRounds = store.priorRoundCount(row.number);
|
|
1146
|
+
const roundType = roundTypeFor(row, priorRounds);
|
|
1147
|
+
|
|
1148
|
+
// Guard 2, daemon half: a tick with nothing new to look at is a
|
|
1149
|
+
// no-op. The triggers are still consumed (leaving them would re-queue the
|
|
1150
|
+
// PR on the next reconcile, forever) and the PR returns to the state its
|
|
1151
|
+
// last verdict put it in — that verdict still stands.
|
|
1152
|
+
//
|
|
1153
|
+
// The mark is read through the one standing-review interpretation
|
|
1154
|
+
// (standing-review.ts): the shell stamps `posted_event` and the watcher
|
|
1155
|
+
// may never enrich `verdict`, so `verdict` alone can read null for a
|
|
1156
|
+
// standing REQUEST_CHANGES. Guard 2 keys on head + author reply and
|
|
1157
|
+
// nothing else, so THAT is the test here — provenance (a label, a team
|
|
1158
|
+
// re-request, a reconcile re-derivation) is a scheduling signal and
|
|
1159
|
+
// never widens the posting boundary. If the standing verdict cannot be
|
|
1160
|
+
// read off the mark (a COMMENT-only row), no round runs either — guard
|
|
1161
|
+
// 2 would still refuse it — but nothing is guessed: the PR lands in
|
|
1162
|
+
// `tracked` with a precise anomaly instead of an invented verdict.
|
|
1163
|
+
const mark = store.reviewedHead(row.number);
|
|
1164
|
+
const standingInfo = standingReview(mark);
|
|
1165
|
+
const standing = standingInfo.kind === "decided" ? standingInfo.verdict : null;
|
|
1166
|
+
// A withholding standing at THIS head is new evidence of its
|
|
1167
|
+
// own. The last round decided "no blockers, approval withheld for CI",
|
|
1168
|
+
// and something re-queued the PR — CI going green (cigate.ts) or a
|
|
1169
|
+
// person. Skipping here would leave that PR waiting for a verdict that is
|
|
1170
|
+
// never re-attempted, which is the stuck-PR failure the gate must not
|
|
1171
|
+
// create.
|
|
1172
|
+
const withheld =
|
|
1173
|
+
store.approvalWithheld(row.number) ??
|
|
1174
|
+
restoreInterruptedApprovalRetry(
|
|
1175
|
+
store,
|
|
1176
|
+
row,
|
|
1177
|
+
deps.postingReceiptDir ?? join(sandboxRoot(), "state", "posting-receipts"),
|
|
1178
|
+
);
|
|
1179
|
+
const withheldHere = withheld?.head_sha === row.head_sha;
|
|
1180
|
+
// The round about to run re-decides the verdict from scratch, so the
|
|
1181
|
+
// withholding it inherits is spent — once the round has a durable owner
|
|
1182
|
+
// (the session handoff), never before: a failure before the handoff
|
|
1183
|
+
// leaves the mark standing, so the next pump still owes the approval
|
|
1184
|
+
// retry and the reconcile backstop still sees it. Only the mark read
|
|
1185
|
+
// here is spent; a new one is recorded if this round's approval is
|
|
1186
|
+
// withheld again (guards.ts guard 4, or the verdict's
|
|
1187
|
+
// `approval_withheld`).
|
|
1188
|
+
let withholdingSpent = false;
|
|
1189
|
+
spendWithholding = (roundId) => {
|
|
1190
|
+
if (!withheld || withholdingSpent) return;
|
|
1191
|
+
store.db.transaction(() => {
|
|
1192
|
+
const current = store.approvalWithheld(row.number);
|
|
1193
|
+
if (
|
|
1194
|
+
current &&
|
|
1195
|
+
current.head_sha === withheld.head_sha &&
|
|
1196
|
+
current.created_at === withheld.created_at
|
|
1197
|
+
)
|
|
1198
|
+
store.clearApprovalWithheld(row.number);
|
|
1199
|
+
// The spend's own record: what `restoreInterruptedApprovalRetry`
|
|
1200
|
+
// re-records should this round end without deciding.
|
|
1201
|
+
if (withheldHere)
|
|
1202
|
+
store.logEvent("approval_retry_dispatched", row.number, {
|
|
1203
|
+
head_sha: row.head_sha,
|
|
1204
|
+
round_id: roundId,
|
|
1205
|
+
ci_state: withheld.ci_state,
|
|
1206
|
+
reason: withheld.reason,
|
|
1207
|
+
});
|
|
1208
|
+
})();
|
|
1209
|
+
withholdingSpent = true;
|
|
1210
|
+
};
|
|
1211
|
+
if (cfg.guards.skip_unchanged_head && !withheldHere && !hasNewEvidence(mark, row.head_sha)) {
|
|
1212
|
+
// Captured BEFORE the transition below clears them: current PR state
|
|
1213
|
+
// is not historical evidence (a later failure overwrites the
|
|
1214
|
+
// provenance).
|
|
1215
|
+
const decision = {
|
|
1216
|
+
stage: "dispatch",
|
|
1217
|
+
head_sha: row.head_sha,
|
|
1218
|
+
trigger_ts: row.trigger_ts,
|
|
1219
|
+
trigger_provenance: row.trigger_provenance,
|
|
1220
|
+
standing_verdict: standing,
|
|
1221
|
+
standing_source: standingInfo.kind === "decided" ? standingInfo.source : null,
|
|
1222
|
+
// Duplicate of standing_verdict, for readers of the older payload shape.
|
|
1223
|
+
last_verdict: standing,
|
|
1224
|
+
reviewed_at: mark!.reviewed_at,
|
|
1225
|
+
mark: snapshotMark(mark!),
|
|
1226
|
+
};
|
|
1227
|
+
await consumeTriggers(deps, row.number, { markInProgress: false });
|
|
1228
|
+
// An author reply landing during the consumption is new evidence the
|
|
1229
|
+
// skip did not see: the next pump runs the round it now owes.
|
|
1230
|
+
const movedSkip =
|
|
1231
|
+
movedDuringConsume(store, row) ??
|
|
1232
|
+
(hasNewEvidence(store.reviewedHead(row.number), row.head_sha) ? "new_evidence" : null);
|
|
1233
|
+
if (movedSkip) {
|
|
1234
|
+
logSupersededSettlement(deps, row, "no-new-evidence skip", movedSkip);
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
store.logEvent("round_skipped_no_new_evidence", row.number, decision);
|
|
1238
|
+
// The head this skip settles is reviewed already: a successor owed at
|
|
1239
|
+
// it is answered, and is not promoted again on every reconcile pass.
|
|
1240
|
+
store.settleSuccessor(row.number, row.head_sha);
|
|
1241
|
+
if (standingInfo.kind === "unknown") {
|
|
1242
|
+
store.logAnomaly(
|
|
1243
|
+
"standing_review_unknown",
|
|
1244
|
+
row.number,
|
|
1245
|
+
null,
|
|
1246
|
+
`#${row.number} was re-triggered at ${row.head_sha.slice(0, 7)}, which is already reviewed with no author reply since, but the reviewed-head mark carries no verdict (posted_event=${standingInfo.posted_event ?? "null"}). No round was run — posting guard 2 would refuse its review — and no verdict was guessed; the PR is tracked until the author pushes or replies.`,
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
const restored = restoreStandingLifecycle(store, row.number, standing, {
|
|
1250
|
+
botLogin: cfg.github.bot_login,
|
|
1251
|
+
now,
|
|
1252
|
+
reason: standing ? "no-new-evidence" : "no-new-evidence:standing-unknown",
|
|
1253
|
+
});
|
|
1254
|
+
log(
|
|
1255
|
+
`dispatch: #${row.number} skipped — no new evidence at ${row.head_sha.slice(0, 7)}; standing ${standing ?? "review (verdict unknown)"} stands, PR ${restored}`,
|
|
1256
|
+
);
|
|
1257
|
+
continue;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// A SYSTEM-provenance re-trigger (push / CI / staleness at an existing
|
|
1261
|
+
// trigger state — never a fresh human ask, which applyEvent stamps
|
|
1262
|
+
// "human") consults the recheck delta classifier before paying for a
|
|
1263
|
+
// full round. Empty/trivial delta since the reviewed head ⇒ consume the
|
|
1264
|
+
// trigger and leave state as an uphold would; significant ⇒ the full
|
|
1265
|
+
// round below runs. First reviews never short-circuit, and a withheld
|
|
1266
|
+
// approval at this head is its own new evidence: that round must run so
|
|
1267
|
+
// the approval can finally post.
|
|
1268
|
+
if (
|
|
1269
|
+
roundType !== "first" &&
|
|
1270
|
+
(standing === "APPROVE" || standing === "REQUEST_CHANGES") &&
|
|
1271
|
+
mark?.head_sha &&
|
|
1272
|
+
!withheldHere &&
|
|
1273
|
+
row.trigger_provenance === "system"
|
|
1274
|
+
) {
|
|
1275
|
+
const sc = await classifySystemRetrigger(deps, row, mark);
|
|
1276
|
+
if (sc.skip) {
|
|
1277
|
+
await consumeTriggers(deps, row.number, { markInProgress: false });
|
|
1278
|
+
const movedShort = movedDuringConsume(store, row);
|
|
1279
|
+
if (movedShort) {
|
|
1280
|
+
logSupersededSettlement(deps, row, "system short-circuit", movedShort);
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
if (row.head_sha !== mark.head_sha) {
|
|
1284
|
+
// Uphold semantics, daemon-side: a never-posted (withheld) approval
|
|
1285
|
+
// follows the head on a trivial/empty delta.
|
|
1286
|
+
carryWithheldMark(
|
|
1287
|
+
store,
|
|
1288
|
+
row.number,
|
|
1289
|
+
row.head_sha,
|
|
1290
|
+
`dispatch-short-circuit:${sc.reason}`,
|
|
1291
|
+
(m) => log(m),
|
|
1292
|
+
);
|
|
1293
|
+
if (standing === "APPROVE") {
|
|
1294
|
+
// This head's push question is answered (trivially). Claim the
|
|
1295
|
+
// same fingerprint the reconcile push-backstop checks so it is
|
|
1296
|
+
// not re-asked every sweep; INSERT OR IGNORE means a claim a
|
|
1297
|
+
// real recheck already holds is left untouched. No wake is sent,
|
|
1298
|
+
// so no receipt can exist: never the undelivered sweep's to
|
|
1299
|
+
// release.
|
|
1300
|
+
store.claimRecheck(
|
|
1301
|
+
row.number,
|
|
1302
|
+
recheckFingerprint({ trigger: "push" }, row.head_sha),
|
|
1303
|
+
{
|
|
1304
|
+
head_sha: row.head_sha,
|
|
1305
|
+
trigger: "push",
|
|
1306
|
+
reason: `dispatch-short-circuit:${sc.reason}`,
|
|
1307
|
+
nudge: false,
|
|
1308
|
+
},
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
// The trivial delta answers this head: a successor owed at it is
|
|
1313
|
+
// settled, so reconcile does not promote it again on every pass.
|
|
1314
|
+
store.settleSuccessor(row.number, row.head_sha);
|
|
1315
|
+
log(
|
|
1316
|
+
`dispatch: #${row.number} short-circuited (${sc.reason}) — system re-trigger, standing ${standing}`,
|
|
1317
|
+
);
|
|
1318
|
+
store.logEvent("dispatch_short_circuit", row.number, {
|
|
1319
|
+
reason: sc.reason,
|
|
1320
|
+
delta_class: sc.class,
|
|
1321
|
+
head_sha: row.head_sha,
|
|
1322
|
+
last_reviewed_sha: mark.head_sha,
|
|
1323
|
+
standing_verdict: standing,
|
|
1324
|
+
files: sc.files,
|
|
1325
|
+
});
|
|
1326
|
+
applyEvent(
|
|
1327
|
+
store,
|
|
1328
|
+
row.number,
|
|
1329
|
+
{ type: "verdict", verdict: standing },
|
|
1330
|
+
{},
|
|
1331
|
+
{
|
|
1332
|
+
botLogin: cfg.github.bot_login,
|
|
1333
|
+
now,
|
|
1334
|
+
reason: `dispatch-short-circuit:${sc.reason}`,
|
|
1335
|
+
},
|
|
1336
|
+
);
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
markedInProgress = true;
|
|
1342
|
+
const consumed = await consumeTriggers(deps, row.number);
|
|
1343
|
+
|
|
1344
|
+
// The branch this PR targets (base freshness) and its body
|
|
1345
|
+
// (pass-intent's artifact + substance bar) come off one getPr read. A
|
|
1346
|
+
// failed read degrades to "no base facts, no intent lens", never a
|
|
1347
|
+
// failed round.
|
|
1348
|
+
let baseRef = "";
|
|
1349
|
+
let prBody: string | undefined;
|
|
1350
|
+
try {
|
|
1351
|
+
const ref = await deps.adapter.getPr(row.number);
|
|
1352
|
+
baseRef = ref.base_ref;
|
|
1353
|
+
prBody = ref.body;
|
|
1354
|
+
} catch (err) {
|
|
1355
|
+
log(`dispatch: base ref lookup failed on #${row.number}`, err);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// Prepare the tree BEFORE building the order, so the prompt cites the sha
|
|
1359
|
+
// the passes will actually read. `row.head_sha` is a webhook/reconcile
|
|
1360
|
+
// snapshot; a push landing between it and the fetch would otherwise have
|
|
1361
|
+
// passes cite one sha while reading another tree, with anchors computed
|
|
1362
|
+
// against a diff the review is not about.
|
|
1363
|
+
//
|
|
1364
|
+
// And BEFORE the changed-files read: every file-derived fact of the round
|
|
1365
|
+
// — the migration gate, the pass plan and sweep focus, the collision
|
|
1366
|
+
// pre-check, the diffstat — must describe the head the tree holds (read
|
|
1367
|
+
// first, a migration a racing push added would get no gate and no
|
|
1368
|
+
// collision check). The files API is by PR number, so read after the
|
|
1369
|
+
// fetch it lists at least the prepared head.
|
|
1370
|
+
const tree = deps.prepareWorktree
|
|
1371
|
+
? await deps.prepareWorktree(row.number, baseRef, row.head_sha)
|
|
1372
|
+
: undefined;
|
|
1373
|
+
// The head this round reviews — the prepared tree's, else the row's —
|
|
1374
|
+
// decided once, so the round row, the prior findings, the pre-check,
|
|
1375
|
+
// the recovery identity and the order all name the same one.
|
|
1376
|
+
const preparedHead = reviewedHeadOf(tree, row);
|
|
1377
|
+
|
|
1378
|
+
const files = await deps.adapter.listPrFiles(row.number);
|
|
1379
|
+
const gate = eco.migrationGate(files);
|
|
1380
|
+
// The repo's written style guide, if any: the style lens runs only on
|
|
1381
|
+
// one (`[review] style_lens`). Total — an unreadable tree is no guide.
|
|
1382
|
+
// Read off the prepared tree BEFORE the plan, so the plan is built once
|
|
1383
|
+
// and the round row records exactly the order's plan.
|
|
1384
|
+
const styleGuide = lensEnabled(cfg.lenses, STYLE_PASS, cfg.review.style_lens)
|
|
1385
|
+
? findStyleGuide(resolve(sandboxRoot(), worktreeFor(row.number)))
|
|
1386
|
+
: [];
|
|
1387
|
+
// The linters a CI job of the repo runs: lint conformance runs only on
|
|
1388
|
+
// them (`[review] lint_conformance`). Total — an unreadable tree is none.
|
|
1389
|
+
const ciLinters = lensEnabled(cfg.lenses, LINT_CONFORMANCE_PASS, cfg.review.lint_conformance)
|
|
1390
|
+
? findCiLinters(resolve(sandboxRoot(), worktreeFor(row.number)))
|
|
1391
|
+
: [];
|
|
1392
|
+
// The plan is pure and deterministic in `files` + the PR body + the
|
|
1393
|
+
// style guide + the linters: built ONCE here and handed to
|
|
1394
|
+
// buildRoundOrder, so the round row and the order never disagree.
|
|
1395
|
+
const fullPlan = buildPassPlan(
|
|
1396
|
+
cfg,
|
|
1397
|
+
files,
|
|
1398
|
+
{ title: row.title, body: prBody },
|
|
1399
|
+
eco,
|
|
1400
|
+
deps.heuristics,
|
|
1401
|
+
styleGuide,
|
|
1402
|
+
ciLinters,
|
|
1403
|
+
);
|
|
1404
|
+
// Follow-up rounds (docs/follow-up-rounds.md): every round after the
|
|
1405
|
+
// first reviews the PR-owned change since the last reviewed head — or,
|
|
1406
|
+
// on any doubt, runs a full re-review. `files` stays the WHOLE
|
|
1407
|
+
// PR for every per-PR fact (the migration gate, the pre-check, the
|
|
1408
|
+
// directive escalation, the diffstat, dependency sources, memory and
|
|
1409
|
+
// lessons); only the plan and the coverage check read the delta.
|
|
1410
|
+
let followUp: FollowUpDecision | null = null;
|
|
1411
|
+
let fullRound: { reason: FollowUpFullReason; detail?: Record<string, unknown> } | null = null;
|
|
1412
|
+
if (roundType === "re-review") {
|
|
1413
|
+
const decided = await decideFollowUp(
|
|
1414
|
+
{
|
|
1415
|
+
cfg: deps.cfg,
|
|
1416
|
+
history: deps.store,
|
|
1417
|
+
...(deps.prOwnedDelta ? { prOwnedDelta: deps.prOwnedDelta } : {}),
|
|
1418
|
+
...(deps.heuristics ? { heuristics: deps.heuristics } : {}),
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
pr: row.number,
|
|
1422
|
+
markHead: mark?.head_sha,
|
|
1423
|
+
tree,
|
|
1424
|
+
files,
|
|
1425
|
+
fullPlan,
|
|
1426
|
+
standing,
|
|
1427
|
+
// Read before this dispatch spends it: a withheld approval is
|
|
1428
|
+
// one GitHub never saw, so the sweep stays owed behind it.
|
|
1429
|
+
approvalWithheld: withheld !== undefined,
|
|
1430
|
+
ecosystem: eco,
|
|
1431
|
+
styleGuide,
|
|
1432
|
+
ciLinters,
|
|
1433
|
+
},
|
|
1434
|
+
);
|
|
1435
|
+
if (decided.kind === "delta") followUp = decided.decision;
|
|
1436
|
+
else
|
|
1437
|
+
fullRound = {
|
|
1438
|
+
reason: decided.reason,
|
|
1439
|
+
...(decided.detail ? { detail: decided.detail } : {}),
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
let passPolicy = followUp ? followUp.plan : fullPlan;
|
|
1443
|
+
let { plannedPasses, lenses, addablePasses, validateTier } = recordedPlan(
|
|
1444
|
+
cfg,
|
|
1445
|
+
passPolicy,
|
|
1446
|
+
followUp?.optionalPlan,
|
|
1447
|
+
);
|
|
1448
|
+
|
|
1449
|
+
// The PR must still be queued when its round row is written — read in
|
|
1450
|
+
// the same turn as `startRound`. Preparation awaited GitHub and git, and
|
|
1451
|
+
// a verdict settling an older round (a late, orphan-swept one), a close
|
|
1452
|
+
// or the operator may have moved the row meanwhile; then this round is
|
|
1453
|
+
// nobody's work and no row is written. The in-progress label the
|
|
1454
|
+
// trigger consumption put on comes off again (best effort).
|
|
1455
|
+
const live = store.getPr(row.number);
|
|
1456
|
+
if (live?.state !== "queued") {
|
|
1457
|
+
log(
|
|
1458
|
+
`dispatch: #${row.number} left the queue (${live?.state ?? "gone"}) while its round was prepared — no round started`,
|
|
1459
|
+
);
|
|
1460
|
+
store.logEvent("dispatch_abandoned", row.number, {
|
|
1461
|
+
state: live?.state ?? null,
|
|
1462
|
+
head_sha: preparedHead,
|
|
1463
|
+
});
|
|
1464
|
+
try {
|
|
1465
|
+
await deps.adapter.removeLabel(row.number, cfg.github.in_progress_label);
|
|
1466
|
+
} catch (err) {
|
|
1467
|
+
log(`dispatch: #${row.number} in-progress label not removed`, err);
|
|
1468
|
+
}
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
// Diffstat comes free off the files list dispatch already fetched — no
|
|
1472
|
+
// extra GitHub call in the hot path. trigger_ts is copied off pr_state
|
|
1473
|
+
// here because the `dispatched` transition clears it moments later.
|
|
1474
|
+
// validate_tier is recorded so a later audit can tell which tier judged
|
|
1475
|
+
// which finding.
|
|
1476
|
+
const diffstat = diffstatOf(files);
|
|
1477
|
+
const roundId = store.startRound(
|
|
1478
|
+
row.number,
|
|
1479
|
+
roundType,
|
|
1480
|
+
lenses,
|
|
1481
|
+
cfg.daemon.round_deadline_min,
|
|
1482
|
+
{
|
|
1483
|
+
...diffstat,
|
|
1484
|
+
trigger_ts: row.trigger_ts,
|
|
1485
|
+
validate_tier: validateTier,
|
|
1486
|
+
pass_plan: plannedPasses,
|
|
1487
|
+
addable_passes: addablePasses,
|
|
1488
|
+
head_sha: preparedHead,
|
|
1489
|
+
...(tree?.merge_base ? { merge_base: tree.merge_base } : {}),
|
|
1490
|
+
...(roundType === "re-review"
|
|
1491
|
+
? followUp
|
|
1492
|
+
? {
|
|
1493
|
+
scope: "delta" as const,
|
|
1494
|
+
delta_base_sha: followUp.fromSha,
|
|
1495
|
+
delta_lines: followUp.delta.lines,
|
|
1496
|
+
delta_files: followUp.delta.files.length,
|
|
1497
|
+
sweep_owed: followUp.sweepPlan !== undefined,
|
|
1498
|
+
...(followUp.sweepPlan
|
|
1499
|
+
? { sweep_plan: followUp.sweepPlan }
|
|
1500
|
+
: { sweep_status: "not_owed" as const }),
|
|
1501
|
+
}
|
|
1502
|
+
: { scope: "full" as const }
|
|
1503
|
+
: {}),
|
|
1504
|
+
},
|
|
1505
|
+
);
|
|
1506
|
+
provisionalRoundId = roundId;
|
|
1507
|
+
const roundNo = (
|
|
1508
|
+
store.db.prepare("SELECT round_no FROM rounds WHERE id = ?").get(roundId) as {
|
|
1509
|
+
round_no: number;
|
|
1510
|
+
}
|
|
1511
|
+
).round_no;
|
|
1512
|
+
|
|
1513
|
+
// The delta as one patch file, beside the round's other state
|
|
1514
|
+
// files. A write that fails falls the round back to a full re-review —
|
|
1515
|
+
// the follow-up prompt names the file, and a missing one is never a
|
|
1516
|
+
// quieter round.
|
|
1517
|
+
let roundDelta: RoundDelta | undefined;
|
|
1518
|
+
if (followUp) {
|
|
1519
|
+
const patchFile = resolve(
|
|
1520
|
+
sandboxRoot(),
|
|
1521
|
+
"state",
|
|
1522
|
+
`pr-${row.number}-round-${roundNo}-delta.patch`,
|
|
1523
|
+
);
|
|
1524
|
+
try {
|
|
1525
|
+
mkdirSync(resolve(sandboxRoot(), "state"), { recursive: true });
|
|
1526
|
+
writeFileSync(`${patchFile}.tmp`, renderDeltaPatch(followUp.delta.files));
|
|
1527
|
+
renameSync(`${patchFile}.tmp`, patchFile);
|
|
1528
|
+
roundDelta = roundDeltaOf(followUp, patchFile);
|
|
1529
|
+
} catch (err) {
|
|
1530
|
+
log(`dispatch: #${row.number} delta patch not written — full round`, err);
|
|
1531
|
+
fullRound = {
|
|
1532
|
+
reason: "patch-write-failed",
|
|
1533
|
+
detail: { error: err instanceof Error ? err.message : String(err) },
|
|
1534
|
+
};
|
|
1535
|
+
followUp = null;
|
|
1536
|
+
passPolicy = fullPlan;
|
|
1537
|
+
({ plannedPasses, lenses, addablePasses, validateTier } = recordedPlan(cfg, passPolicy));
|
|
1538
|
+
store.setRoundPlan(roundId, lenses, {
|
|
1539
|
+
validate_tier: validateTier,
|
|
1540
|
+
pass_plan: plannedPasses,
|
|
1541
|
+
addable_passes: addablePasses,
|
|
1542
|
+
});
|
|
1543
|
+
store.setRoundMeta(roundId, {
|
|
1544
|
+
scope: "full",
|
|
1545
|
+
delta_base_sha: null,
|
|
1546
|
+
delta_lines: null,
|
|
1547
|
+
delta_files: null,
|
|
1548
|
+
sweep_owed: null,
|
|
1549
|
+
sweep_plan: null,
|
|
1550
|
+
sweep_status: null,
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
if (fullRound) {
|
|
1555
|
+
store.logEvent("followup_full_round", row.number, {
|
|
1556
|
+
round_id: roundId,
|
|
1557
|
+
round_no: roundNo,
|
|
1558
|
+
reason: fullRound.reason,
|
|
1559
|
+
head_sha: preparedHead,
|
|
1560
|
+
last_reviewed_sha: mark?.head_sha ?? null,
|
|
1561
|
+
...(fullRound.detail ?? {}),
|
|
1562
|
+
});
|
|
1563
|
+
log(`dispatch: #${row.number} round ${roundNo} runs in full (${fullRound.reason})`);
|
|
1564
|
+
}
|
|
1565
|
+
if (passPolicy.behaviour_unread)
|
|
1566
|
+
store.logEvent("behaviour_delta_unread", row.number, {
|
|
1567
|
+
round_id: roundId,
|
|
1568
|
+
round_no: roundNo,
|
|
1569
|
+
files: passPolicy.behaviour_unread,
|
|
1570
|
+
});
|
|
1571
|
+
// The prior findings this follow-up round verifies — listed in
|
|
1572
|
+
// prior_finding_checks and written to the round's sidecar. Total: a list
|
|
1573
|
+
// that cannot be assembled renders an explicit "verify from your ledger"
|
|
1574
|
+
// note, never an empty one.
|
|
1575
|
+
const priorFindings = followUp
|
|
1576
|
+
? preparePriorFindings(
|
|
1577
|
+
{
|
|
1578
|
+
store,
|
|
1579
|
+
findingsDir: deps.findingsDir ?? resolve(sandboxRoot(), "state", "findings"),
|
|
1580
|
+
stateDir: resolve(sandboxRoot(), "state"),
|
|
1581
|
+
log,
|
|
1582
|
+
},
|
|
1583
|
+
row.number,
|
|
1584
|
+
roundNo,
|
|
1585
|
+
preparedHead,
|
|
1586
|
+
)
|
|
1587
|
+
: null;
|
|
1588
|
+
const priorFindingsNote = priorFindings?.note ?? "";
|
|
1589
|
+
|
|
1590
|
+
if (tree?.pull_ref_sha) {
|
|
1591
|
+
log(
|
|
1592
|
+
`dispatch: #${row.number} GitHub's refs/pull/${row.number}/head lags (${tree.pull_ref_sha}); reviewing the API head ${tree.head_sha}`,
|
|
1593
|
+
);
|
|
1594
|
+
store.logEvent("pull_ref_lagged", row.number, {
|
|
1595
|
+
round_id: roundId,
|
|
1596
|
+
pull_ref: tree.pull_ref_sha,
|
|
1597
|
+
head: tree.head_sha,
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
if (tree && row.head_sha && tree.head_sha !== row.head_sha) {
|
|
1601
|
+
log(`dispatch: #${row.number} head drift ${row.head_sha} → ${tree.head_sha}`);
|
|
1602
|
+
store.logEvent("head_drift", row.number, {
|
|
1603
|
+
round_id: roundId,
|
|
1604
|
+
expected: row.head_sha,
|
|
1605
|
+
actual: tree.head_sha,
|
|
1606
|
+
source: "worktree_prepare",
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
// The integrity fingerprint — git's config/hook files and this system's
|
|
1611
|
+
// own policy surface under the sandbox root — taken HERE: after the
|
|
1612
|
+
// worktree is materialised (so the tree is the one the round will read)
|
|
1613
|
+
// and before the session hears about the round (so nothing it does is
|
|
1614
|
+
// inside the baseline). `verdicts.measureUsage` re-checks it when the
|
|
1615
|
+
// round ends. Best-effort in both directions: a fingerprint that cannot
|
|
1616
|
+
// be taken leaves the round unchecked — it never fails a round. It hashes
|
|
1617
|
+
// the whole policy surface (dist/, scripts/, …) asynchronously, a loop
|
|
1618
|
+
// turn after every file, and is BOUNDED, because this await sits inside
|
|
1619
|
+
// the one serialized dispatch pump: a read that never returns leaves
|
|
1620
|
+
// this round unchecked (no baseline), never every later PR undispatched.
|
|
1621
|
+
try {
|
|
1622
|
+
const budgetMs = Math.max(0, deps.integrityBudgetMs ?? INTEGRITY_BUDGET_MS);
|
|
1623
|
+
const baseline = await withinBudget(
|
|
1624
|
+
(deps.integrityFingerprint ?? fingerprintRoundIntegrity)(
|
|
1625
|
+
roundIntegrityOpts(githubCloneDir(cfg.github), sandboxRoot(), worktreeFor(row.number)),
|
|
1626
|
+
),
|
|
1627
|
+
budgetMs,
|
|
1628
|
+
);
|
|
1629
|
+
if (baseline.kind === "done") {
|
|
1630
|
+
store.recordRoundGitConfig(roundId, baseline.value);
|
|
1631
|
+
} else {
|
|
1632
|
+
store.logEvent("round_integrity_unchecked", row.number, {
|
|
1633
|
+
round_id: roundId,
|
|
1634
|
+
stage: "baseline",
|
|
1635
|
+
reason: "budget",
|
|
1636
|
+
budget_ms: budgetMs,
|
|
1637
|
+
});
|
|
1638
|
+
log(
|
|
1639
|
+
`dispatch: #${row.number} integrity baseline not taken within ${budgetMs} ms — the round goes unchecked`,
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
} catch (err) {
|
|
1643
|
+
log(`dispatch: #${row.number} git-config fingerprint unavailable`, err);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
// The ecosystem pre-check, before the session hears about the round.
|
|
1647
|
+
// Django delivers collisions as its
|
|
1648
|
+
// own COMMENT, so whatever the review concludes, the renumber never
|
|
1649
|
+
// rides on the verdict; a `none` ecosystem runs nothing at all.
|
|
1650
|
+
const precheck = await ecosystemPreCheck(deps, eco, {
|
|
1651
|
+
round_id: roundId,
|
|
1652
|
+
pr: row.number,
|
|
1653
|
+
files,
|
|
1654
|
+
head_sha: preparedHead,
|
|
1655
|
+
base_ref: tree?.base_ref ?? baseRef,
|
|
1656
|
+
base_sha: tree?.base_sha ?? "",
|
|
1657
|
+
});
|
|
1658
|
+
const migrationCollisions = precheck.collisions;
|
|
1659
|
+
// A degraded pre-check is an anomaly the overseer should see (same
|
|
1660
|
+
// channel verdicts.ts uses — an overseer inject, never a new path). The
|
|
1661
|
+
// round itself still runs: the prompt carries the "check collisions
|
|
1662
|
+
// yourself" note, so the class is covered either way. A configured-off
|
|
1663
|
+
// ecosystem ("none") can never reach this block — off is not degraded,
|
|
1664
|
+
// and the seam's outcome union keeps the two apart structurally.
|
|
1665
|
+
if (precheck.degraded) {
|
|
1666
|
+
// Durable anomaly record + the overseer wake (WAKE class) — one
|
|
1667
|
+
// logAnomaly call, the store's sink writes the inject.
|
|
1668
|
+
store.logAnomaly(
|
|
1669
|
+
"migration_precheck_degraded",
|
|
1670
|
+
row.number,
|
|
1671
|
+
roundNo,
|
|
1672
|
+
"migration pre-check could not run (no base facts, or the base read failed); the round prompt un-muzzles the lens",
|
|
1673
|
+
[
|
|
1674
|
+
`ANOMALY (migration-precheck) — PR #${row.number} round ${roundNo}`,
|
|
1675
|
+
"The migration-number pre-check could not run this round (no base facts, or the base read failed) — " +
|
|
1676
|
+
"see the migration_precheck_skipped/failed event on this PR.",
|
|
1677
|
+
"The round prompt tells the session to check base-branch migration-number collisions itself, so nothing is lost — " +
|
|
1678
|
+
"but a recurring degraded pre-check means the base clone or worktree preparation is broken and needs a look.",
|
|
1679
|
+
].join("\n"),
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
// A withheld collision COMMENT is an anomaly too: the round
|
|
1683
|
+
// still hears the collisions through the gate note, but every later
|
|
1684
|
+
// collision comment on this PR stays withheld until the torn ledger row
|
|
1685
|
+
// is repaired — a human has to know that, once.
|
|
1686
|
+
if (
|
|
1687
|
+
precheck.commentWithheld &&
|
|
1688
|
+
!store.anomalyRaised("migration_collision_comment_withheld", row.number)
|
|
1689
|
+
) {
|
|
1690
|
+
store.logAnomaly(
|
|
1691
|
+
"migration_collision_comment_withheld",
|
|
1692
|
+
row.number,
|
|
1693
|
+
roundNo,
|
|
1694
|
+
"migration collision COMMENT withheld: the event log holds a migration_collision_comment row for this PR that cannot be read, " +
|
|
1695
|
+
"so a duplicate cannot be ruled out; the collisions still ride the round prompt",
|
|
1696
|
+
[
|
|
1697
|
+
`ANOMALY (migration-precheck) — PR #${row.number} round ${roundNo}`,
|
|
1698
|
+
"The migration-number pre-check found collisions but WITHHELD the GitHub comment: the event log holds a " +
|
|
1699
|
+
"migration_collision_comment row for this PR whose payload cannot be read, so posting could duplicate an earlier comment.",
|
|
1700
|
+
"The session still hears the collisions through the round prompt's gate note, so nothing is lost this round — " +
|
|
1701
|
+
"but EVERY collision comment on this PR stays withheld until that row is repaired or deleted " +
|
|
1702
|
+
`(events table, kind = 'migration_collision_comment', pr = ${row.number}; see the migration_collision_comment_withheld events).`,
|
|
1703
|
+
].join("\n"),
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// First-party directive edits: detect them and pick the ONE
|
|
1708
|
+
// teammate the escalation comment @-mentions, deterministically, before
|
|
1709
|
+
// the session hears about the round.
|
|
1710
|
+
const firstPartyDirectives = await directiveEscalationFor(deps, {
|
|
1711
|
+
pr: row.number,
|
|
1712
|
+
files,
|
|
1713
|
+
merge_base: tree?.merge_base ?? "",
|
|
1714
|
+
base_sha: tree?.base_sha ?? "",
|
|
1715
|
+
});
|
|
1716
|
+
|
|
1717
|
+
const rawRecoveryInstruction = store.roundRecoveryInstruction(row.number, roundNo);
|
|
1718
|
+
const recoveryInstruction = store.roundRecoveryInstructionForDispatch(
|
|
1719
|
+
row.number,
|
|
1720
|
+
roundNo,
|
|
1721
|
+
preparedHead,
|
|
1722
|
+
);
|
|
1723
|
+
if (rawRecoveryInstruction && !recoveryInstruction) {
|
|
1724
|
+
store.logEvent("round_recovery_instruction_not_applied", row.number, {
|
|
1725
|
+
target_round_no: roundNo,
|
|
1726
|
+
stored_head_sha: rawRecoveryInstruction.target_head_sha,
|
|
1727
|
+
prepared_head_sha: preparedHead,
|
|
1728
|
+
source_round_no: rawRecoveryInstruction.source_round_no,
|
|
1729
|
+
source_command_id: rawRecoveryInstruction.source_command_id,
|
|
1730
|
+
reason: "exact_successor_identity_mismatch",
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1733
|
+
const recovery: RoundRecoveryContext | undefined = recoveryInstruction
|
|
1734
|
+
? {
|
|
1735
|
+
stage: recoveryInstruction.stage,
|
|
1736
|
+
source_round_no: recoveryInstruction.source_round_no,
|
|
1737
|
+
source_command_id: recoveryInstruction.source_command_id,
|
|
1738
|
+
target_head_sha: recoveryInstruction.target_head_sha,
|
|
1739
|
+
immediate_attempt: recoveryInstruction.immediate_attempt,
|
|
1740
|
+
retry_epoch: recoveryInstruction.retry_epoch,
|
|
1741
|
+
...(recoveryInstruction.source_incident_id
|
|
1742
|
+
? { source_incident_id: recoveryInstruction.source_incident_id }
|
|
1743
|
+
: {}),
|
|
1744
|
+
}
|
|
1745
|
+
: undefined;
|
|
1746
|
+
// Third-party sources for the passes (`[review] dependency_sources`).
|
|
1747
|
+
// Total by contract — a dead registry yields an empty note, never a
|
|
1748
|
+
// failed or delayed-past-budget round.
|
|
1749
|
+
let dependencyNote = "";
|
|
1750
|
+
if (cfg.review.dependency_sources && deps.dependencySources) {
|
|
1751
|
+
try {
|
|
1752
|
+
dependencyNote = await deps.dependencySources(
|
|
1753
|
+
resolve(sandboxRoot(), worktreeFor(row.number)),
|
|
1754
|
+
files,
|
|
1755
|
+
);
|
|
1756
|
+
} catch (err) {
|
|
1757
|
+
log(`dispatch: #${row.number} dependency sources unavailable`, err);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
const built = buildRoundOrder({
|
|
1761
|
+
tree,
|
|
1762
|
+
plan: passPolicy,
|
|
1763
|
+
...(followUp && roundDelta
|
|
1764
|
+
? {
|
|
1765
|
+
followUp: {
|
|
1766
|
+
delta: roundDelta,
|
|
1767
|
+
deltaFiles: followUp.deltaFiles,
|
|
1768
|
+
priorFindingsNote,
|
|
1769
|
+
...(priorFindings?.list ? { priorFindings: priorFindings.list } : {}),
|
|
1770
|
+
...(followUp.optionalPlan ? { optionalPlan: followUp.optionalPlan } : {}),
|
|
1771
|
+
...(followUp.sweepPlan ? { sweepPlan: followUp.sweepPlan } : {}),
|
|
1772
|
+
...(followUp.sweepUnswept ? { sweepUnswept: followUp.sweepUnswept } : {}),
|
|
1773
|
+
...(followUp.sweepBehaviourUnread
|
|
1774
|
+
? { sweepBehaviourUnread: followUp.sweepBehaviourUnread }
|
|
1775
|
+
: {}),
|
|
1776
|
+
},
|
|
1777
|
+
}
|
|
1778
|
+
: {}),
|
|
1779
|
+
ecosystem: eco,
|
|
1780
|
+
...(styleGuide.length ? { styleGuide } : {}),
|
|
1781
|
+
...(ciLinters.length ? { ciLinters } : {}),
|
|
1782
|
+
...(dependencyNote ? { dependencyNote } : {}),
|
|
1783
|
+
...(recovery ? { recovery } : {}),
|
|
1784
|
+
migrationCollisions,
|
|
1785
|
+
...(precheck.commentUndelivered ? { migrationCollisionsUndelivered: true } : {}),
|
|
1786
|
+
migrationPrecheckDegraded: precheck.degraded,
|
|
1787
|
+
...(firstPartyDirectives !== undefined ? { firstPartyDirectives } : {}),
|
|
1788
|
+
cfg,
|
|
1789
|
+
row,
|
|
1790
|
+
files,
|
|
1791
|
+
roundId,
|
|
1792
|
+
roundNo,
|
|
1793
|
+
reviewNo: priorRounds + 1,
|
|
1794
|
+
roundType,
|
|
1795
|
+
worktree: worktreeFor(row.number),
|
|
1796
|
+
...((): { settledDigest: string; ledgerCarry?: LedgerCarry } => {
|
|
1797
|
+
// One read of the ledger mirror per order; an explicit
|
|
1798
|
+
// settledDigest supplier still wins for the digest.
|
|
1799
|
+
const fromLedger = ledgerCarry(row.number);
|
|
1800
|
+
return {
|
|
1801
|
+
settledDigest: deps.settledDigest ? deps.settledDigest(row.number) : fromLedger.settled,
|
|
1802
|
+
...(fromLedger.carry ? { ledgerCarry: fromLedger.carry } : {}),
|
|
1803
|
+
};
|
|
1804
|
+
})(),
|
|
1805
|
+
issuedAt: now().toISOString(),
|
|
1806
|
+
...(prBody !== undefined ? { prBody } : {}),
|
|
1807
|
+
...(deps.heuristics !== undefined ? { heuristics: deps.heuristics } : {}),
|
|
1808
|
+
// The host's lens tools, so the prompt and the task files name the ones its
|
|
1809
|
+
// sessions have (rule 9: `lens` / `lens_wave` on workers).
|
|
1810
|
+
...(deps.hooks.lensRuntime ? { lensRuntime: deps.hooks.lensRuntime } : {}),
|
|
1811
|
+
});
|
|
1812
|
+
store.stampPostingKey(roundId, built.posting_key!);
|
|
1813
|
+
// The round row already holds the order's plan: the style guide and the
|
|
1814
|
+
// linters were read before the plan was built, and the plan was built
|
|
1815
|
+
// once.
|
|
1816
|
+
|
|
1817
|
+
// The ONE memory call this round WAITS for, placed after the
|
|
1818
|
+
// worktree and the order so the context is seeded from the same sha the
|
|
1819
|
+
// passes read. It is bounded and total: the worst case is this round's
|
|
1820
|
+
// prompt arriving memory-less a couple of seconds later.
|
|
1821
|
+
//
|
|
1822
|
+
// Under `[memory] delivery = "on"` the round's LESSON
|
|
1823
|
+
// SELECTION is awaited beside it — the prompt, the task files and the
|
|
1824
|
+
// lessons files are rendered from it. Its budget (≤ 2 s) runs from
|
|
1825
|
+
// here, inside the seed's deadline, so the round waits for the slower
|
|
1826
|
+
// of the two, never for their sum — but no lesson find is SENT before
|
|
1827
|
+
// the round's own context search was: the assembler says when it
|
|
1828
|
+
// issued the search, and a seed that never does (no assembler, a
|
|
1829
|
+
// failure before the search, an assembler that does not say) opens
|
|
1830
|
+
// the way when it settles. So the seed's search never queues behind a
|
|
1831
|
+
// lesson find on the server's one-thread embedder. Under
|
|
1832
|
+
// `shadow`/`off` the selection starts only after the handoff,
|
|
1833
|
+
// unawaited (below).
|
|
1834
|
+
const delivering = lessonsDelivering(deps, log);
|
|
1835
|
+
// No case batch starts while this round reads memory — the
|
|
1836
|
+
// window opens here, before the seed, and closes once the selection
|
|
1837
|
+
// has taken over: under `on` when the awaited selection is in; under
|
|
1838
|
+
// `shadow` once `start` has opened the selector's own window, after the
|
|
1839
|
+
// handoff (a batch started during the handoff would sit in front of the
|
|
1840
|
+
// selection's finds). The `finally` below is the belt.
|
|
1841
|
+
endMemoryWindow = openMemoryWindow(deps, row.number, log);
|
|
1842
|
+
let seeded: RoundMemory;
|
|
1843
|
+
let selection: RoundSelectionResult | null = null;
|
|
1844
|
+
if (delivering) {
|
|
1845
|
+
let searchSent!: () => void;
|
|
1846
|
+
const seedSearchSent = new Promise<void>((resolveSent) => (searchSent = resolveSent));
|
|
1847
|
+
const seeding = seedRoundMemory(deps, built, files, () => searchSent());
|
|
1848
|
+
void seeding.then(
|
|
1849
|
+
() => searchSent(),
|
|
1850
|
+
() => searchSent(),
|
|
1851
|
+
);
|
|
1852
|
+
[seeded, selection] = await Promise.all([
|
|
1853
|
+
seeding,
|
|
1854
|
+
selectRoundLessons(deps, built, files, seedSearchSent),
|
|
1855
|
+
]);
|
|
1856
|
+
endMemoryWindow();
|
|
1857
|
+
} else {
|
|
1858
|
+
seeded = await seedRoundMemory(deps, built, files);
|
|
1859
|
+
}
|
|
1860
|
+
const delivery: RoundLessonDelivery | null = selection
|
|
1861
|
+
? buildRoundDelivery({
|
|
1862
|
+
selections: selection.selections,
|
|
1863
|
+
texts: selection.texts,
|
|
1864
|
+
pr: row.number,
|
|
1865
|
+
round_no: roundNo,
|
|
1866
|
+
head_sha: built.head_sha,
|
|
1867
|
+
repo: cfg.github.repo,
|
|
1868
|
+
rowsInTaskFiles: cfg.review.task_files,
|
|
1869
|
+
})
|
|
1870
|
+
: null;
|
|
1871
|
+
|
|
1872
|
+
// `[review] task_files` (packages/pi-plugin/src/passtasks.ts): each discovery row's
|
|
1873
|
+
// complete lens task, written beside the round's pass outputs so the
|
|
1874
|
+
// session launches lenses with a pointer instead of generating every
|
|
1875
|
+
// task string. Total: a write that fails leaves the order without task
|
|
1876
|
+
// files, and the session writes the strings itself. Under `on` each
|
|
1877
|
+
// row's delivered lessons ride its task file.
|
|
1878
|
+
// `[review] context_pack = "pack-first"`: the round's CONTEXT
|
|
1879
|
+
// PACK, written before the task files that point at it, and only where
|
|
1880
|
+
// children share a cache key (`[engine] lens_cache_group`) — an unshared
|
|
1881
|
+
// pack costs every lens more, not less. Total: a pack that cannot be
|
|
1882
|
+
// written leaves the round exactly as without one; a stale pack from an
|
|
1883
|
+
// earlier attempt of this round number never survives into it.
|
|
1884
|
+
const passesDir = resolve(sandboxRoot(), "state", `pr-${row.number}-round-${roundNo}-passes`);
|
|
1885
|
+
const packPath = join(passesDir, CONTEXT_PACK_FILE);
|
|
1886
|
+
const removeStalePack = (): void => {
|
|
1887
|
+
try {
|
|
1888
|
+
rmSync(packPath, { force: true });
|
|
1889
|
+
} catch (err) {
|
|
1890
|
+
log(`dispatch: #${row.number} stale context pack not removed`, err);
|
|
1891
|
+
}
|
|
1892
|
+
};
|
|
1893
|
+
let packed: RoundOrder = built;
|
|
1894
|
+
if (
|
|
1895
|
+
cfg.review.task_files &&
|
|
1896
|
+
cfg.review.context_pack === "pack-first" &&
|
|
1897
|
+
cfg.engine.lens_cache_group > 0
|
|
1898
|
+
) {
|
|
1899
|
+
try {
|
|
1900
|
+
const pack = renderContextPack({
|
|
1901
|
+
pr: row.number,
|
|
1902
|
+
headSha: built.head_sha,
|
|
1903
|
+
mergeBase: built.merge_base,
|
|
1904
|
+
files,
|
|
1905
|
+
...(followUp ? { deltaPatch: renderDeltaPatch(followUp.delta.files) } : {}),
|
|
1906
|
+
readFile: (p) => readWorktreeFileForPack(resolve(sandboxRoot(), built.worktree), p),
|
|
1907
|
+
maxTokens: cfg.review.context_pack_max_tokens,
|
|
1908
|
+
});
|
|
1909
|
+
mkdirSync(passesDir, { recursive: true });
|
|
1910
|
+
writeFileSync(`${packPath}.tmp`, pack.text);
|
|
1911
|
+
renameSync(`${packPath}.tmp`, packPath);
|
|
1912
|
+
packed = { ...built, context_pack_file: packPath };
|
|
1913
|
+
store.logEvent("context_pack_written", row.number, {
|
|
1914
|
+
round_id: roundId,
|
|
1915
|
+
chars: pack.chars,
|
|
1916
|
+
diff_files: pack.diffFiles.length,
|
|
1917
|
+
diff_omitted: pack.diffOmitted.length,
|
|
1918
|
+
full_files: pack.fullFiles.length,
|
|
1919
|
+
});
|
|
1920
|
+
} catch (err) {
|
|
1921
|
+
log(`dispatch: #${row.number} context pack not written`, err);
|
|
1922
|
+
// A child finds the pack beside its task file, not through the
|
|
1923
|
+
// order — an earlier attempt's pack left here would be read as
|
|
1924
|
+
// this round's.
|
|
1925
|
+
removeStalePack();
|
|
1926
|
+
}
|
|
1927
|
+
} else {
|
|
1928
|
+
removeStalePack();
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
let order = packed;
|
|
1932
|
+
let rowsDelivered = false;
|
|
1933
|
+
if (cfg.review.task_files) {
|
|
1934
|
+
try {
|
|
1935
|
+
const dir = join(passesDir, "tasks");
|
|
1936
|
+
const tasks = writePassTasks(
|
|
1937
|
+
dir,
|
|
1938
|
+
packed,
|
|
1939
|
+
renderGateNotes(packed, eco),
|
|
1940
|
+
delivery
|
|
1941
|
+
? {
|
|
1942
|
+
rows: new Map(delivery.rows.map((r) => [r.row, r.lessons] as const)),
|
|
1943
|
+
repo: delivery.repo,
|
|
1944
|
+
}
|
|
1945
|
+
: undefined,
|
|
1946
|
+
);
|
|
1947
|
+
order = {
|
|
1948
|
+
...packed,
|
|
1949
|
+
pass_task_files: tasks.map((t) => ({ row: t.row, agent: t.agent, path: t.path })),
|
|
1950
|
+
};
|
|
1951
|
+
rowsDelivered = true;
|
|
1952
|
+
} catch (err) {
|
|
1953
|
+
log(`dispatch: #${row.number} lens task files unavailable`, err);
|
|
1954
|
+
}
|
|
1955
|
+
// The owed pre-approval sweep's task files — its own
|
|
1956
|
+
// directory and row index (`row` indexes `sweep_plan`), the whole PR's
|
|
1957
|
+
// full plan rendered exactly as a full round's. No lesson rows ride
|
|
1958
|
+
// them: lesson selection covers `pass_plan` rows only. Total, as above.
|
|
1959
|
+
// With no sweep owed, an earlier attempt's sweep dir is removed, so
|
|
1960
|
+
// nothing stale can be launched from it.
|
|
1961
|
+
const sweepDir = join(passesDir, "tasks-sweep");
|
|
1962
|
+
if (!built.sweep_plan || built.sweep_plan.length === 0) {
|
|
1963
|
+
try {
|
|
1964
|
+
rmSync(sweepDir, { recursive: true, force: true });
|
|
1965
|
+
} catch (err) {
|
|
1966
|
+
log(`dispatch: #${row.number} stale sweep task dir not removed`, err);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
// The optional rows' task files — their own directory and
|
|
1970
|
+
// row index (`row` indexes `optional_plan`), rendered from the
|
|
1971
|
+
// follow-up order itself, so they carry the change since the last
|
|
1972
|
+
// review exactly as the core rows do. Total, as above.
|
|
1973
|
+
const optionalDir = join(passesDir, "tasks-optional");
|
|
1974
|
+
if (!built.optional_plan || built.optional_plan.length === 0) {
|
|
1975
|
+
try {
|
|
1976
|
+
rmSync(optionalDir, { recursive: true, force: true });
|
|
1977
|
+
} catch (err) {
|
|
1978
|
+
log(`dispatch: #${row.number} stale optional task dir not removed`, err);
|
|
1979
|
+
}
|
|
1980
|
+
} else {
|
|
1981
|
+
try {
|
|
1982
|
+
const optionalOrder = optionalOrderOf(packed, built.optional_plan);
|
|
1983
|
+
const optionalTasks = writePassTasks(
|
|
1984
|
+
optionalDir,
|
|
1985
|
+
optionalOrder,
|
|
1986
|
+
renderGateNotes(optionalOrder, eco),
|
|
1987
|
+
);
|
|
1988
|
+
order = {
|
|
1989
|
+
...order,
|
|
1990
|
+
optional_task_files: optionalTasks.map((t) => ({
|
|
1991
|
+
row: t.row,
|
|
1992
|
+
agent: t.agent,
|
|
1993
|
+
path: t.path,
|
|
1994
|
+
})),
|
|
1995
|
+
};
|
|
1996
|
+
} catch (err) {
|
|
1997
|
+
log(`dispatch: #${row.number} optional task files unavailable`, err);
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
if (built.sweep_plan && built.sweep_plan.length > 0) {
|
|
2001
|
+
try {
|
|
2002
|
+
const sweepOrder = sweepOrderOf(packed, built.sweep_plan);
|
|
2003
|
+
const sweepTasks = writePassTasks(
|
|
2004
|
+
sweepDir,
|
|
2005
|
+
sweepOrder,
|
|
2006
|
+
renderGateNotes(sweepOrder, eco),
|
|
2007
|
+
);
|
|
2008
|
+
order = {
|
|
2009
|
+
...order,
|
|
2010
|
+
sweep_task_files: sweepTasks.map((t) => ({
|
|
2011
|
+
row: t.row,
|
|
2012
|
+
agent: t.agent,
|
|
2013
|
+
path: t.path,
|
|
2014
|
+
})),
|
|
2015
|
+
};
|
|
2016
|
+
} catch (err) {
|
|
2017
|
+
log(`dispatch: #${row.number} sweep task files unavailable`, err);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
const memory = deliverRoundLessons(deps, {
|
|
2022
|
+
seeded,
|
|
2023
|
+
// A row's lessons travel only in its task file: none written, none
|
|
2024
|
+
// delivered — the index must not say otherwise.
|
|
2025
|
+
delivery: delivery && !rowsDelivered ? { ...delivery, rows: [] } : delivery,
|
|
2026
|
+
dir: join(passesDir, "lessons"),
|
|
2027
|
+
});
|
|
2028
|
+
|
|
2029
|
+
// A PR closed while its round was prepared gets no session and no
|
|
2030
|
+
// prompt: the provisional round is discarded like any failed
|
|
2031
|
+
// preparation (the close hook already archived the session).
|
|
2032
|
+
if (store.getPr(row.number)?.state === "closed")
|
|
2033
|
+
throw new Error(`#${row.number} closed while its round was prepared — not handed off`);
|
|
2034
|
+
// The round is provisional until the worktree/session/prompt handoff
|
|
2035
|
+
// completes; publishing reviewing/running state any earlier would leave
|
|
2036
|
+
// an orphan round whenever worktree preparation fails.
|
|
2037
|
+
const prompt = render(order, memory);
|
|
2038
|
+
// Rendering and optional preparation may have awaited or invoked callers
|
|
2039
|
+
// that changed the operator hold. Re-admit at the actual handoff seam.
|
|
2040
|
+
const admitted = store.getPr(row.number);
|
|
2041
|
+
if (!admitted || admitted.state === "closed" || admitted.paused)
|
|
2042
|
+
throw new Error(`#${row.number} closed or paused during preparation — not handed off`);
|
|
2043
|
+
await deps.hooks.dispatchRound(order, prompt);
|
|
2044
|
+
// Record the receipt token this round's prompt carries, so the
|
|
2045
|
+
// recovery paths can tell "delivered" from "never arrived" instead of
|
|
2046
|
+
// inferring it. Stamped only AFTER dispatchRound resolved: the marker
|
|
2047
|
+
// asserts that a prompt bearing it was written, and a dispatch that
|
|
2048
|
+
// threw may not have written it (one that persisted its durable
|
|
2049
|
+
// command first is retained by the catch below, unstamped).
|
|
2050
|
+
store.stampPromptMarker(roundId, roundPromptMarker(row.number, roundNo));
|
|
2051
|
+
spendWithholding(roundId);
|
|
2052
|
+
// The head this round reviews is the next follow-up round's
|
|
2053
|
+
// baseline — pin it against gc so a force-push cannot take it away.
|
|
2054
|
+
// Best-effort: an unpinned head is re-fetched, or the round runs in full.
|
|
2055
|
+
if (deps.pinReviewedHead) {
|
|
2056
|
+
try {
|
|
2057
|
+
const pinned = await deps.pinReviewedHead(row.number, order.head_sha);
|
|
2058
|
+
if (!pinned)
|
|
2059
|
+
log(`dispatch: #${row.number} reviewed head ${order.head_sha.slice(0, 7)} not pinned`);
|
|
2060
|
+
} catch (err) {
|
|
2061
|
+
log(`dispatch: #${row.number} reviewed head not pinned`, err);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
if (memory.lessons) logLessonDelivery(store, roundId, memory.lessons);
|
|
2065
|
+
// The round's lesson selection — the shadow ledger's
|
|
2066
|
+
// input. Started only now, with the round handed off, and never
|
|
2067
|
+
// awaited: under `shadow` it changes nothing in this round's prompt,
|
|
2068
|
+
// task files or agents, so nothing is gained by starting it sooner —
|
|
2069
|
+
// and starting it before the seed would queue its finds ahead of the
|
|
2070
|
+
// round's own memory search on the server's one-thread embedder. Under
|
|
2071
|
+
// `on` it already ran, awaited beside the seed (above).
|
|
2072
|
+
if (deps.lessons && !delivering) {
|
|
2073
|
+
try {
|
|
2074
|
+
deps.lessons.start(order, files);
|
|
2075
|
+
} catch (err) {
|
|
2076
|
+
log(`dispatch: #${row.number} lesson selection not started (ignored)`, err);
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
// `start` opened the selector's own window synchronously (or there
|
|
2080
|
+
// is no selection) — the round's own window closes now.
|
|
2081
|
+
endMemoryWindow();
|
|
2082
|
+
|
|
2083
|
+
setPrGates(store, row.number, gate);
|
|
2084
|
+
applyEvent(
|
|
2085
|
+
store,
|
|
2086
|
+
row.number,
|
|
2087
|
+
{ type: "dispatched" },
|
|
2088
|
+
{},
|
|
2089
|
+
{ botLogin: cfg.github.bot_login, now },
|
|
2090
|
+
);
|
|
2091
|
+
store.logEvent("round_dispatched", row.number, {
|
|
2092
|
+
round_id: roundId,
|
|
2093
|
+
round_no: roundNo,
|
|
2094
|
+
round_type: roundType,
|
|
2095
|
+
// What a later round reviewed (absent on a first round).
|
|
2096
|
+
...(roundType === "re-review"
|
|
2097
|
+
? {
|
|
2098
|
+
scope: order.scope === "delta" ? "delta" : "full",
|
|
2099
|
+
...(order.delta
|
|
2100
|
+
? {
|
|
2101
|
+
delta_lines: order.delta.lines,
|
|
2102
|
+
delta_files: order.delta.files.length,
|
|
2103
|
+
sweep_owed: (order.sweep_plan?.length ?? 0) > 0,
|
|
2104
|
+
}
|
|
2105
|
+
: {}),
|
|
2106
|
+
}
|
|
2107
|
+
: {}),
|
|
2108
|
+
lenses: order.lenses,
|
|
2109
|
+
validate_tier: validateTier,
|
|
2110
|
+
migration_gate: order.migration_gate,
|
|
2111
|
+
migration_collisions: migrationCollisions.length,
|
|
2112
|
+
migration_precheck_degraded: precheck.degraded,
|
|
2113
|
+
first_party_directive_files: firstPartyDirectives?.files.length ?? 0,
|
|
2114
|
+
head_sha: order.head_sha,
|
|
2115
|
+
base_ref: order.base_ref,
|
|
2116
|
+
base_sha: order.base_sha,
|
|
2117
|
+
triggers_consumed: consumed,
|
|
2118
|
+
// `row` is the pre-dispatch snapshot; `dispatched` has just
|
|
2119
|
+
// cleared these on the live row, and a late guard-2 settlement needs
|
|
2120
|
+
// to explain which trigger paid for the round.
|
|
2121
|
+
trigger_ts: row.trigger_ts,
|
|
2122
|
+
trigger_provenance: row.trigger_provenance,
|
|
2123
|
+
recovery: order.recovery ?? null,
|
|
2124
|
+
});
|
|
2125
|
+
// Slack: the PR's thread root on the first round we see, a
|
|
2126
|
+
// 🔄 line in-thread on later ones. Fire-and-forget: a slow or
|
|
2127
|
+
// blackholed Slack (bounded retries, capped Retry-After, one shared
|
|
2128
|
+
// post queue) must never stall the pump behind it — the announcer's
|
|
2129
|
+
// per-PR lane is claimed synchronously on the call, so thread order
|
|
2130
|
+
// still holds. The announcer swallows its own failures; the catches
|
|
2131
|
+
// here are belt-and-braces, and the round is recorded above already.
|
|
2132
|
+
if (deps.announce) {
|
|
2133
|
+
const pr = row.number;
|
|
2134
|
+
try {
|
|
2135
|
+
void deps.announce
|
|
2136
|
+
.roundStarted({
|
|
2137
|
+
pr: row.number,
|
|
2138
|
+
title: row.title,
|
|
2139
|
+
author: row.author,
|
|
2140
|
+
head_sha: order.head_sha,
|
|
2141
|
+
round_no: roundNo,
|
|
2142
|
+
round_type: roundType,
|
|
2143
|
+
lenses: order.lenses,
|
|
2144
|
+
additions: diffstat.additions,
|
|
2145
|
+
deletions: diffstat.deletions,
|
|
2146
|
+
changed_files: diffstat.changed_files,
|
|
2147
|
+
})
|
|
2148
|
+
.catch((err: unknown) => log(`dispatch: slack announcement failed on #${pr}`, err));
|
|
2149
|
+
} catch (err) {
|
|
2150
|
+
log(`dispatch: slack announcement failed on #${pr}`, err);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
provisionalRoundId = undefined;
|
|
2154
|
+
dispatched.push(order);
|
|
2155
|
+
started++;
|
|
2156
|
+
} catch (err) {
|
|
2157
|
+
// One bad PR must never stall the pump. Before the session handoff, the
|
|
2158
|
+
// provisional round is disposable. AFTER dispatchRound persisted a
|
|
2159
|
+
// durable command, deleting only the round would orphan a live command
|
|
2160
|
+
// that blocks every successor and that the supervisor cannot correlate.
|
|
2161
|
+
// Keep that round authoritative instead; best-effort publish reviewing
|
|
2162
|
+
// state and let the bounded command supervisor finish/recover it.
|
|
2163
|
+
let retainedCommand = false;
|
|
2164
|
+
if (provisionalRoundId !== undefined) {
|
|
2165
|
+
const provisional = store.runningRounds().find((round) => round.id === provisionalRoundId);
|
|
2166
|
+
const command = provisional
|
|
2167
|
+
? store.sessionCommandForRound(row.number, provisional.round_no)
|
|
2168
|
+
: undefined;
|
|
2169
|
+
if (command) {
|
|
2170
|
+
retainedCommand = true;
|
|
2171
|
+
started++;
|
|
2172
|
+
// The retained round owns the review now: the withholding it
|
|
2173
|
+
// inherited is spent, exactly as on a clean handoff.
|
|
2174
|
+
try {
|
|
2175
|
+
spendWithholding(provisionalRoundId);
|
|
2176
|
+
} catch (spendError) {
|
|
2177
|
+
log(`dispatch: #${row.number} withheld-approval mark not spent`, spendError);
|
|
2178
|
+
}
|
|
2179
|
+
try {
|
|
2180
|
+
applyEvent(
|
|
2181
|
+
store,
|
|
2182
|
+
row.number,
|
|
2183
|
+
{ type: "dispatched" },
|
|
2184
|
+
{},
|
|
2185
|
+
{ botLogin: cfg.github.bot_login, now },
|
|
2186
|
+
);
|
|
2187
|
+
} catch (publishError) {
|
|
2188
|
+
log(
|
|
2189
|
+
`dispatch: #${row.number} retained command but could not publish reviewing state`,
|
|
2190
|
+
publishError,
|
|
2191
|
+
);
|
|
2192
|
+
}
|
|
2193
|
+
try {
|
|
2194
|
+
store.logAnomaly(
|
|
2195
|
+
"dispatch_commit_failed",
|
|
2196
|
+
row.number,
|
|
2197
|
+
provisional!.round_no,
|
|
2198
|
+
`session command ${command.id} was persisted but dispatch bookkeeping failed: ${String(err)}; bounded supervision retains ownership`,
|
|
2199
|
+
);
|
|
2200
|
+
} catch (anomalyError) {
|
|
2201
|
+
log(`dispatch: #${row.number} retained-command anomaly write failed`, anomalyError);
|
|
2202
|
+
}
|
|
2203
|
+
} else {
|
|
2204
|
+
store.discardRunningRound(provisionalRoundId);
|
|
2205
|
+
// Under `on` the round's lesson selection ran — and was
|
|
2206
|
+
// recorded — before the handoff; a discarded round never ran, so
|
|
2207
|
+
// its ledger rows go too (and a selection still in flight records
|
|
2208
|
+
// nothing), or the shadow report would count the attempt.
|
|
2209
|
+
if (deps.lessons?.discard) {
|
|
2210
|
+
try {
|
|
2211
|
+
deps.lessons.discard(provisionalRoundId);
|
|
2212
|
+
} catch (discardError) {
|
|
2213
|
+
log(
|
|
2214
|
+
`dispatch: #${row.number} lesson record of the discarded round not dropped (ignored)`,
|
|
2215
|
+
discardError,
|
|
2216
|
+
);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
log(
|
|
2222
|
+
`dispatch: #${row.number} failed${retainedCommand ? " after durable handoff — round retained" : ""}`,
|
|
2223
|
+
err,
|
|
2224
|
+
);
|
|
2225
|
+
// No round owns the review: the PR stays queued for the next pump,
|
|
2226
|
+
// which paints it again when its round really starts.
|
|
2227
|
+
if (!retainedCommand && markedInProgress) {
|
|
2228
|
+
try {
|
|
2229
|
+
await deps.adapter.removeLabel(row.number, cfg.github.in_progress_label);
|
|
2230
|
+
} catch (labelError) {
|
|
2231
|
+
log(`dispatch: #${row.number} in-progress label not removed`, labelError);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
try {
|
|
2235
|
+
store.logEvent("dispatch_failed", row.number, {
|
|
2236
|
+
round_id: provisionalRoundId ?? null,
|
|
2237
|
+
retained_command: retainedCommand,
|
|
2238
|
+
error: String(err),
|
|
2239
|
+
});
|
|
2240
|
+
} catch (eventError) {
|
|
2241
|
+
log(`dispatch: #${row.number} failure event write failed`, eventError);
|
|
2242
|
+
}
|
|
2243
|
+
} finally {
|
|
2244
|
+
// A round that failed after its seed never leaves the case
|
|
2245
|
+
// writer held (idempotent: a closed window stays closed).
|
|
2246
|
+
endMemoryWindow();
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
return dispatched;
|
|
2250
|
+
}
|