@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/dist/src/cli.js
ADDED
|
@@ -0,0 +1,1916 @@
|
|
|
1
|
+
// qcr — the operator CLI. Read-only against state/ (status.json + state.db)
|
|
2
|
+
// except where a subcommand says otherwise. Nothing here attaches to a tmux
|
|
3
|
+
// session: a PR is read with `live` / `open pr`, entered with `takeover` /
|
|
4
|
+
// `debug-round` (whose own lines say how to attach to their pi).
|
|
5
|
+
import { readFileSync, existsSync, mkdirSync, readdirSync, realpathSync, renameSync, writeFileSync, } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { takeHandoffToken } from "@quickchatai/pi-quickreview/src/ghcred.js";
|
|
10
|
+
import { loadConfig, sandboxRoot } from "./config.js";
|
|
11
|
+
import { daemonLoadLine } from "./eventloop.js";
|
|
12
|
+
import { ManifestUnreadable, parseManifestText, summarizeManifest, } from "./archive/manifest.js";
|
|
13
|
+
import { archiveStatusLine, fmtBytes } from "./archive/status.js";
|
|
14
|
+
import { memoryStatusLine } from "./memory/health.js";
|
|
15
|
+
import { cancelCauseLabel, parseExecutedPasses, roundUsage, StateStore, tallyCancellations, } from "./state.js";
|
|
16
|
+
import { cancellationSummaryLine, computeMetrics, computeTimeline, readOps, renderMetrics, renderTimeline, } from "./metrics.js";
|
|
17
|
+
import { CommandDrainSender, drainPayload } from "./tui/deploy-drain.js";
|
|
18
|
+
import { locateConfig, locateKeysFile, locateSecrets, migrateConfig, migrateProfiles, migrateReport, profileMigrateReport, profileSearchDirs, migrateSecrets, operatorStateDir, repointComposeSecrets, composeRepointReport, homeProfileLookup, secretsMigrateReport, stateHome, strandedCheckoutConfig, strandedCheckoutProfiles, strandedLegacySecrets, } from "./install.js";
|
|
19
|
+
import { sanitizeText } from "./tui/paint.js";
|
|
20
|
+
import { operatorCommandWriteScript } from "./tui/unwedge.js";
|
|
21
|
+
import { shellQuote, transportOf } from "./tui/remote.js";
|
|
22
|
+
import { MutationGate } from "@quickchatai/pi-quickreview/src/gate.js";
|
|
23
|
+
import { remoteSandboxRoot, resolveLayout } from "./tui/layout.js";
|
|
24
|
+
export { drainPayload } from "./tui/deploy-drain.js";
|
|
25
|
+
/** Where the operator verbs keep the state database (src/homes.ts
|
|
26
|
+
* `operatorStateDir`): the state home for a package-manager install, the
|
|
27
|
+
* checkout's or the image's `state/` otherwise. */
|
|
28
|
+
function stateDir() {
|
|
29
|
+
return operatorStateDir(sandboxRoot());
|
|
30
|
+
}
|
|
31
|
+
function openStore() {
|
|
32
|
+
return new StateStore(join(stateDir(), "state.db"));
|
|
33
|
+
}
|
|
34
|
+
export const OUTCOMES_USAGE = "usage: qcr outcomes [--days <n>] [--capture] [--read-github] [--json]";
|
|
35
|
+
/** `qcr outcomes` flags. An unknown token is refused, so a
|
|
36
|
+
* mistyped `--capture` cannot quietly print a stale table. */
|
|
37
|
+
export function parseOutcomesArgs(rest) {
|
|
38
|
+
let days = 30;
|
|
39
|
+
let capture = false;
|
|
40
|
+
let readGithub = false;
|
|
41
|
+
for (let i = 0; i < rest.length; i++) {
|
|
42
|
+
const a = rest[i];
|
|
43
|
+
if (a === "--capture")
|
|
44
|
+
capture = true;
|
|
45
|
+
else if (a === "--read-github")
|
|
46
|
+
readGithub = true;
|
|
47
|
+
else if (a === "--days") {
|
|
48
|
+
const n = Number(rest[++i]);
|
|
49
|
+
if (!Number.isInteger(n) || n <= 0)
|
|
50
|
+
throw new Error(OUTCOMES_USAGE);
|
|
51
|
+
days = n;
|
|
52
|
+
}
|
|
53
|
+
else
|
|
54
|
+
throw new Error(OUTCOMES_USAGE);
|
|
55
|
+
}
|
|
56
|
+
if (readGithub && !capture)
|
|
57
|
+
throw new Error(`${OUTCOMES_USAGE} (--read-github needs --capture)`);
|
|
58
|
+
return { days, capture, readGithub };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The gate behind every read-only CLI command that opens the adapter
|
|
62
|
+
* (`qcr outcomes --read-github`, `why --json`'s enrichment, `backfill`,
|
|
63
|
+
* `history digest`, `regression`): it can only RECORD. Dry-run, so every
|
|
64
|
+
* ordinary kind is written to `dryrunDir` and never executed; and an executor
|
|
65
|
+
* that refuses, so even the one class the gate runs in dry-run
|
|
66
|
+
* (`APP_SELF_CONFIG_KINDS`) cannot reach GitHub. The reads the command makes
|
|
67
|
+
* go direct, as every adapter read does; nothing can write.
|
|
68
|
+
*/
|
|
69
|
+
export function recordOnlyGate(dryrunDir, command = "qcr outcomes") {
|
|
70
|
+
const gate = new MutationGate(true, dryrunDir, async () => {
|
|
71
|
+
throw new Error(`${command} performs no mutations`);
|
|
72
|
+
});
|
|
73
|
+
// The gate's loud line (app-self-config attempts) on stdout.
|
|
74
|
+
gate.setLog((msg, extra) => console.log(`[quickreviewd] ${msg}`, extra ?? ""));
|
|
75
|
+
return gate;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The `--capture` half of `qcr outcomes`: every PR with a posted finding
|
|
79
|
+
* recorded since `since`, oldest number first — with `read`, ONE read of its
|
|
80
|
+
* inline comments first for the link facts (a failed read is reported and the
|
|
81
|
+
* PR is captured from what the state database holds) — then captured.
|
|
82
|
+
* Returns the PRs captured.
|
|
83
|
+
*/
|
|
84
|
+
export async function captureOutcomesWindow(opts) {
|
|
85
|
+
const write = opts.write ?? ((line) => process.stderr.write(`${line}\n`));
|
|
86
|
+
const prs = opts.store.db
|
|
87
|
+
.prepare("SELECT DISTINCT pr FROM round_findings WHERE disposition = 'posted' AND recorded_at >= ? ORDER BY pr")
|
|
88
|
+
.all(opts.since).map((r) => r.pr);
|
|
89
|
+
for (const pr of prs) {
|
|
90
|
+
if (opts.read) {
|
|
91
|
+
try {
|
|
92
|
+
opts.outcomes.observeBotComments(pr, await opts.read.listReviewCommentsFull(pr));
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
write(`outcomes #${pr}: could not read its comments: ${String(err)}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const r = await opts.outcomes.capturePr(pr);
|
|
99
|
+
write(`outcomes #${pr}: ${r.posted} posted, ${r.linked} comment(s) linked, ${r.unmatched} unmatched, ${r.changed} fate(s) changed`);
|
|
100
|
+
}
|
|
101
|
+
return prs;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* `qcr outcomes`: what became of the findings posted in the last
|
|
105
|
+
* `--days` (default 30) — the fate table per lens, how many posted findings
|
|
106
|
+
* are linked to their comment, and the whole-review signals per review.
|
|
107
|
+
* `--capture` first re-captures every PR with a posted finding in the window
|
|
108
|
+
* from state.db and the base clone's git (the daemon does this on its own for
|
|
109
|
+
* open and just-closed PRs; this reaches the older ones). `--read-github` adds
|
|
110
|
+
* ONE read per such PR — its inline comments, for the link facts — through a
|
|
111
|
+
* gate that can only record (`recordOnlyGate`). Nothing is ever written to
|
|
112
|
+
* GitHub.
|
|
113
|
+
*/
|
|
114
|
+
export async function cmdOutcomes(rest, json) {
|
|
115
|
+
const { days, capture, readGithub } = parseOutcomesArgs(rest);
|
|
116
|
+
const cfg = loadConfig();
|
|
117
|
+
const { OutcomeCapture, renderOutcomeSummary } = await import("./outcomes.js");
|
|
118
|
+
const { WorktreeManager } = await import("./worktrees.js");
|
|
119
|
+
const { githubCloneDir } = await import("./config.js");
|
|
120
|
+
const root = sandboxRoot();
|
|
121
|
+
const worktrees = new WorktreeManager({
|
|
122
|
+
root,
|
|
123
|
+
repo: cfg.github.repo,
|
|
124
|
+
cloneDir: githubCloneDir(cfg.github),
|
|
125
|
+
});
|
|
126
|
+
const store = openStore();
|
|
127
|
+
try {
|
|
128
|
+
const outcomes = new OutcomeCapture({
|
|
129
|
+
store,
|
|
130
|
+
botLogin: cfg.github.bot_login,
|
|
131
|
+
...(existsSync(worktrees.basePath) ? { git: worktrees } : {}),
|
|
132
|
+
});
|
|
133
|
+
const since = new Date(Date.now() - days * 86_400_000).toISOString();
|
|
134
|
+
if (capture) {
|
|
135
|
+
let read;
|
|
136
|
+
if (readGithub) {
|
|
137
|
+
const { TokenMinter } = await import("@quickchatai/pi-quickreview/src/githubAuth.js");
|
|
138
|
+
const { GitHubAdapter } = await import("./github.js");
|
|
139
|
+
read = new GitHubAdapter(cfg, new TokenMinter(cfg.github.secrets_dir), recordOnlyGate(join(stateDir(), "dryrun")));
|
|
140
|
+
}
|
|
141
|
+
await captureOutcomesWindow({ store, outcomes, since, ...(read ? { read } : {}) });
|
|
142
|
+
}
|
|
143
|
+
const summary = outcomes.summary(since);
|
|
144
|
+
console.log(json ? JSON.stringify(summary) : renderOutcomeSummary(summary));
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
store.close();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export const LESSONS_USAGE = "usage: qcr lessons [mine] [--json] | qcr lessons shadow-report [--days <n>] [--json] | " +
|
|
152
|
+
"qcr lessons off|on | qcr lessons suppress|unsuppress <experiences/<file>.md | viking://… URI>";
|
|
153
|
+
/** `qcr lessons shadow-report|off|on|suppress|unsuppress …`, or null when
|
|
154
|
+
* `rest` is the candidates table (`[mine]`). An unknown flag or a missing target is
|
|
155
|
+
* refused. */
|
|
156
|
+
export function parseLessonsSubcommand(rest) {
|
|
157
|
+
const [head, ...tail] = rest;
|
|
158
|
+
switch (head) {
|
|
159
|
+
case "shadow-report": {
|
|
160
|
+
let days = 14;
|
|
161
|
+
for (let i = 0; i < tail.length; i++) {
|
|
162
|
+
if (tail[i] === "--days") {
|
|
163
|
+
const n = Number(tail[i + 1]);
|
|
164
|
+
if (!Number.isInteger(n) || n < 1 || n > 365)
|
|
165
|
+
throw new Error(LESSONS_USAGE);
|
|
166
|
+
days = n;
|
|
167
|
+
i += 1;
|
|
168
|
+
}
|
|
169
|
+
else
|
|
170
|
+
throw new Error(LESSONS_USAGE);
|
|
171
|
+
}
|
|
172
|
+
return { kind: "shadow-report", days };
|
|
173
|
+
}
|
|
174
|
+
case "off":
|
|
175
|
+
case "on":
|
|
176
|
+
if (tail.length > 0)
|
|
177
|
+
throw new Error(LESSONS_USAGE);
|
|
178
|
+
return { kind: "switch", command: head === "off" ? "lessons-off" : "lessons-on" };
|
|
179
|
+
case "suppress":
|
|
180
|
+
case "unsuppress":
|
|
181
|
+
if (tail.length !== 1 || !tail[0])
|
|
182
|
+
throw new Error(LESSONS_USAGE);
|
|
183
|
+
return {
|
|
184
|
+
kind: "switch",
|
|
185
|
+
command: head === "suppress" ? "suppress-lesson" : "unsuppress-lesson",
|
|
186
|
+
target: tail[0],
|
|
187
|
+
};
|
|
188
|
+
default:
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** `qcr lessons` arguments: `mine` or nothing. An unknown token
|
|
193
|
+
* is refused, so a mistyped `mine` cannot quietly print yesterday's table. */
|
|
194
|
+
export function parseLessonsArgs(rest) {
|
|
195
|
+
if (rest.length === 0)
|
|
196
|
+
return { mine: false };
|
|
197
|
+
if (rest.length === 1 && rest[0] === "mine")
|
|
198
|
+
return { mine: true };
|
|
199
|
+
throw new Error(LESSONS_USAGE);
|
|
200
|
+
}
|
|
201
|
+
/** What `qcr lessons` prints: the mine's report (with `mine`), the undecided
|
|
202
|
+
* candidates per class, and the latest EOD curation. */
|
|
203
|
+
export function renderLessons(opts) {
|
|
204
|
+
const out = [];
|
|
205
|
+
const r = opts.report;
|
|
206
|
+
if (r) {
|
|
207
|
+
const by = Object.entries(r.added_by_source)
|
|
208
|
+
.filter(([, v]) => (v ?? 0) > 0)
|
|
209
|
+
.map(([k, v]) => `${k} ${v}`)
|
|
210
|
+
.join(", ");
|
|
211
|
+
out.push(`mined: ${r.added} new candidate${r.added === 1 ? "" : "s"}${by ? ` (${by})` : ""}`);
|
|
212
|
+
if (r.recall)
|
|
213
|
+
out.push(`post-merge recall: ${r.recall.scanned} merged PR(s) read, ${r.recall.unknown} unknown`);
|
|
214
|
+
else
|
|
215
|
+
out.push("post-merge recall: not read (no base clone)");
|
|
216
|
+
if (r.digest)
|
|
217
|
+
out.push(`digest backfill: ${r.digest.added} added from ${r.digest.files} digest(s) (${r.digest.absent} absent, ${r.digest.unreadable} unreadable)`);
|
|
218
|
+
const f = r.fates;
|
|
219
|
+
if (f.revised + f.withdrawn + f.reinstated > 0)
|
|
220
|
+
out.push(`fate candidates re-read: ${f.revised} revised, ${f.withdrawn} withdrawn, ${f.reinstated} reinstated`);
|
|
221
|
+
if ((r.relinked ?? 0) > 0)
|
|
222
|
+
out.push(`feedback candidates re-linked to their finding: ${r.relinked}`);
|
|
223
|
+
for (const e of r.errors)
|
|
224
|
+
out.push(`source error: ${oneLine(e)}`);
|
|
225
|
+
out.push("");
|
|
226
|
+
}
|
|
227
|
+
out.push(opts.clusters.length === 0
|
|
228
|
+
? "No undecided lesson candidates."
|
|
229
|
+
: "Undecided lesson candidates by class (offered / waiting for a packet / events in the recurrence window):");
|
|
230
|
+
for (const c of opts.clusters)
|
|
231
|
+
out.push(` ${c.class.padEnd(32)} ${c.polarity.padEnd(10)} ${String(c.open).padStart(4)} ${String(c.queued).padStart(4)} ${String(c.recurrence).padStart(5)}`);
|
|
232
|
+
const s = opts.status;
|
|
233
|
+
out.push("", s.day === null
|
|
234
|
+
? "EOD curation: no outcomes packet yet."
|
|
235
|
+
: `EOD curation ${s.day}: ${s.state.replace("_", " ")} — ${s.offered} offered, ${s.adopted} adopted, ${s.declined} declined, ${s.verifying} verifying, ${s.not_persisted} not persisted, ${s.expired_today} expired${(s.unresolved ?? []).length > 0
|
|
236
|
+
? `\nunresolved: ${(s.unresolved ?? []).map((u) => `${u.day} ${u.state.replace("_", " ")} (${u.count})`).join("; ")}`
|
|
237
|
+
: ""}`, s.balance
|
|
238
|
+
? `lessons in force: look-for:dont-raise ${s.balance.look_for}:${s.balance.dont_raise}${s.balance.ratio === null ? "" : ` (${s.balance.ratio.toFixed(2)})`} as of ${s.balance.as_of}`
|
|
239
|
+
: "lessons in force: the daemon reads memory for the balance — see `qcr status`");
|
|
240
|
+
return out.join("\n");
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* `qcr lessons [mine]`: the learner's candidates per class and
|
|
244
|
+
* the latest EOD curation, from the state database. `mine` first runs the
|
|
245
|
+
* miner on the operator's word — the same deterministic mine the daemon runs
|
|
246
|
+
* before every EOD sweep (state.db, and read-only git in the base clone when
|
|
247
|
+
* there is one). It reads no memory and writes none.
|
|
248
|
+
*/
|
|
249
|
+
export async function cmdLessons(rest, json) {
|
|
250
|
+
const sub = parseLessonsSubcommand(rest);
|
|
251
|
+
if (sub?.kind === "shadow-report")
|
|
252
|
+
return cmdShadowReport(sub.days, json);
|
|
253
|
+
if (sub?.kind === "switch")
|
|
254
|
+
return cmdLessonSwitch(sub);
|
|
255
|
+
const { mine } = parseLessonsArgs(rest);
|
|
256
|
+
const cfg = loadConfig();
|
|
257
|
+
const { Learner, learnerKnobsOf } = await import("./learner/learner.js");
|
|
258
|
+
const { WorktreeManager } = await import("./worktrees.js");
|
|
259
|
+
const { githubCloneDir } = await import("./config.js");
|
|
260
|
+
const root = sandboxRoot();
|
|
261
|
+
const worktrees = new WorktreeManager({
|
|
262
|
+
root,
|
|
263
|
+
repo: cfg.github.repo,
|
|
264
|
+
cloneDir: githubCloneDir(cfg.github),
|
|
265
|
+
});
|
|
266
|
+
const store = openStore();
|
|
267
|
+
try {
|
|
268
|
+
const learner = new Learner({
|
|
269
|
+
store,
|
|
270
|
+
root,
|
|
271
|
+
eodTime: cfg.eod.time,
|
|
272
|
+
timeZone: cfg.eod.timezone,
|
|
273
|
+
botLogin: cfg.github.bot_login,
|
|
274
|
+
baseBranch: cfg.github.base_branch,
|
|
275
|
+
config: learnerKnobsOf(cfg.memory),
|
|
276
|
+
...(existsSync(worktrees.basePath) ? { git: worktrees } : {}),
|
|
277
|
+
});
|
|
278
|
+
const report = mine ? await learner.mine() : null;
|
|
279
|
+
const clusters = learner.clusters();
|
|
280
|
+
const status = learner.status();
|
|
281
|
+
console.log(json
|
|
282
|
+
? JSON.stringify({ report, clusters, status })
|
|
283
|
+
: renderLessons({ report, clusters, status }));
|
|
284
|
+
return report && report.errors.length > 0 ? 1 : 0;
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
store.close();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* `qcr lessons shadow-report [--days N]`: the lesson shadow
|
|
292
|
+
* ledger joined with what humans did — in total, per lesson and per lens, the
|
|
293
|
+
* findings a delivered dont-raise lesson would have touched that humans then
|
|
294
|
+
* FIXED (harm), refuted or set aside (help), or not yet, each split by match
|
|
295
|
+
* tier (`class`, the lesson's class matched too — the would-demote tier,
|
|
296
|
+
* what the validator's license reaches; `lens`, lens and path alone —
|
|
297
|
+
* evidence only); and the look-for lessons whose class later showed a
|
|
298
|
+
* human-raised miss. state.db only (opening it migrates a ledger without
|
|
299
|
+
* match tiers once).
|
|
300
|
+
*/
|
|
301
|
+
async function cmdShadowReport(days, json) {
|
|
302
|
+
const cfg = loadConfig();
|
|
303
|
+
const { LessonLedger } = await import("./lessonledger.js");
|
|
304
|
+
const { renderShadowReport, shadowReport } = await import("./lessonshadow.js");
|
|
305
|
+
const store = openStore();
|
|
306
|
+
try {
|
|
307
|
+
const report = shadowReport(new LessonLedger(store), { days, mode: cfg.memory.delivery });
|
|
308
|
+
console.log(json ? JSON.stringify(report) : renderShadowReport(report));
|
|
309
|
+
return 0;
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
store.close();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* `qcr lessons off|on|suppress|unsuppress`: the operator's kill
|
|
317
|
+
* switch, written as root into the sealed commands/operator/ (the drain's
|
|
318
|
+
* channel) and confirmed by the daemon's own result file.
|
|
319
|
+
*/
|
|
320
|
+
async function cmdLessonSwitch(sub) {
|
|
321
|
+
const cfg = loadConfig();
|
|
322
|
+
const { runnerFor } = await import("./tui/remote.js");
|
|
323
|
+
const { LessonSwitchSender, lessonUriOf } = await import("./tui/lessonswitch.js");
|
|
324
|
+
const cmd = "target" in sub
|
|
325
|
+
? { command: sub.command, uri: lessonUriOf(sub.target, cfg.openviking.user) }
|
|
326
|
+
: { command: sub.command };
|
|
327
|
+
await resolveLayout(cfg.tui, runnerFor(cfg.tui));
|
|
328
|
+
console.log(`${cmd.command}${"uri" in cmd ? ` ${cmd.uri}` : ""} → ${cfg.tui.ssh_host}:${remoteSandboxRoot(cfg.tui)}/commands/operator/`);
|
|
329
|
+
const res = await new LessonSwitchSender(cfg.tui, runnerFor(cfg.tui)).send(cmd);
|
|
330
|
+
if (!res.ok) {
|
|
331
|
+
console.log(`✖ refused: ${res.reason ?? "no reason given"}`);
|
|
332
|
+
if (res.issuer === "session")
|
|
333
|
+
console.log(" the daemon found commands/operator/ UNSEALED (writable by its own uid), so it served the file as a session's — check the directory is root-owned 0755 in the container");
|
|
334
|
+
return 1;
|
|
335
|
+
}
|
|
336
|
+
console.log(`✓ ${res.changed === false ? "already so" : "done"} — lessons ${res.lessons_off ? "OFF (kill switch)" : "on"}; ${res.suppressed ?? "?"} suppressed`);
|
|
337
|
+
return 0;
|
|
338
|
+
}
|
|
339
|
+
/** Foreign free text (a PR title, a session's pause reason) headed for the
|
|
340
|
+
* operator's terminal: one line, no C0/C1 control bytes — the board's
|
|
341
|
+
* sanitizeText does the same, so an ESC/CSI in a title cannot drive the
|
|
342
|
+
* terminal. */
|
|
343
|
+
function oneLine(s) {
|
|
344
|
+
return s.replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ").trim();
|
|
345
|
+
}
|
|
346
|
+
/** A session-written ledger (the archived copy) headed for the operator's
|
|
347
|
+
* terminal: its lines kept, its control bytes gone — the board's LEDGER
|
|
348
|
+
* section reads it the same way. */
|
|
349
|
+
export function archivedLedgerText(raw) {
|
|
350
|
+
return sanitizeText(raw);
|
|
351
|
+
}
|
|
352
|
+
export function renderStatus(snapshot) {
|
|
353
|
+
const lines = [];
|
|
354
|
+
const mode = snapshot.dry_run ? "DRY-RUN" : "LIVE";
|
|
355
|
+
lines.push(`quickreview — ${mode} — snapshot ${snapshot.written_at}`);
|
|
356
|
+
if (snapshot.run_host?.warning)
|
|
357
|
+
lines.push(`WARNING: ${oneLine(snapshot.run_host.warning)}`);
|
|
358
|
+
if (snapshot.digest_headline)
|
|
359
|
+
lines.push(`digest: ${oneLine(snapshot.digest_headline)}`);
|
|
360
|
+
lines.push(`rounds running: ${snapshot.running_rounds} total spend: $${snapshot.total_spend_usd.toFixed(2)}`);
|
|
361
|
+
// Whether reviewer memory works, every time — an absent block
|
|
362
|
+
// (older daemon) and an unmeasured one say so, never "healthy".
|
|
363
|
+
lines.push(oneLine(memoryStatusLine(snapshot.memory, new Date(snapshot.written_at))));
|
|
364
|
+
// The daemon's own event loop and the container's CPU
|
|
365
|
+
// throttling, every time — an absent block (older daemon) and an unmeasured
|
|
366
|
+
// one say so, never "ok".
|
|
367
|
+
lines.push(oneLine(daemonLoadLine(snapshot.daemon)));
|
|
368
|
+
// The closed-PR archiver, every time — an absent block (older
|
|
369
|
+
// daemon) says so, and a backlog nobody planned yet is UNKNOWN, never 0.
|
|
370
|
+
lines.push(oneLine(archiveStatusLine(snapshot.archive)));
|
|
371
|
+
if (snapshot.draining) {
|
|
372
|
+
lines.push(`DRAINING${snapshot.draining_since ? ` since ${snapshot.draining_since}` : ""} — ${drainHolderLine(snapshot)} — ` +
|
|
373
|
+
`webhook deliveries dropped: ${snapshot.drained_deliveries ?? 0}; ` +
|
|
374
|
+
`deploy when rounds running hits 0 (${drainLiftLine(snapshot)})`);
|
|
375
|
+
}
|
|
376
|
+
lines.push("");
|
|
377
|
+
if (snapshot.prs.length === 0) {
|
|
378
|
+
lines.push("no tracked PRs");
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
lines.push("PR state stale head title");
|
|
382
|
+
for (const pr of snapshot.prs) {
|
|
383
|
+
lines.push(`#${String(pr.number).padEnd(5)} ${pr.state.padEnd(17)} ${(pr.stale_recheck ? "yes" : "-").padEnd(6)} ${pr.head_sha.slice(0, 10).padEnd(11)} ${oneLine(pr.title).slice(0, 60)}`);
|
|
384
|
+
}
|
|
385
|
+
// Every held PR says who holds it and when the hold lapses.
|
|
386
|
+
const held = snapshot.prs.filter((pr) => pr.paused);
|
|
387
|
+
if (held.length > 0) {
|
|
388
|
+
lines.push("");
|
|
389
|
+
for (const pr of held) {
|
|
390
|
+
const hold = pr.pause;
|
|
391
|
+
const who = hold
|
|
392
|
+
? hold.issuer === "operator"
|
|
393
|
+
? "paused by the operator until resumed"
|
|
394
|
+
: `paused by a session${hold.expires_at ? `, lapses ${hold.expires_at}` : ""}`
|
|
395
|
+
: "paused (holder unknown — older daemon)";
|
|
396
|
+
// The reason is a SESSION's free text headed for a terminal.
|
|
397
|
+
const reason = hold?.reason ? oneLine(hold.reason) : undefined;
|
|
398
|
+
lines.push(`#${pr.number} ${who}${reason ? ` — ${reason}` : ""}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return lines.join("\n");
|
|
403
|
+
}
|
|
404
|
+
function drainHolderLine(snapshot) {
|
|
405
|
+
if (snapshot.draining_by === undefined)
|
|
406
|
+
return "holder unknown (older daemon)";
|
|
407
|
+
if (snapshot.draining_by === "operator") {
|
|
408
|
+
if (snapshot.draining_hold === true)
|
|
409
|
+
return "HELD by the operator (hold: no boot lifts it)";
|
|
410
|
+
return snapshot.draining_until
|
|
411
|
+
? `held by the operator, lapses ${snapshot.draining_until}`
|
|
412
|
+
: "held by the operator until lifted";
|
|
413
|
+
}
|
|
414
|
+
return `held by a SESSION, lapses ${snapshot.draining_until ?? "(no expiry recorded)"}`;
|
|
415
|
+
}
|
|
416
|
+
/** What lifts the drain that stands. A snapshot with no hold marker comes from
|
|
417
|
+
* a daemon that does not report holds: whether its boot lifts it is unknown. */
|
|
418
|
+
function drainLiftLine(snapshot) {
|
|
419
|
+
if (snapshot.draining_hold === true || snapshot.draining_reason)
|
|
420
|
+
return "only qcr drain off lifts it — no boot does";
|
|
421
|
+
if (snapshot.draining_hold === undefined)
|
|
422
|
+
return "hold not reported (older daemon); qcr drain off lifts it, a healthy boot may";
|
|
423
|
+
return "auto-lifts on the next boot's first healthy reconcile; qcr drain off to lift now";
|
|
424
|
+
}
|
|
425
|
+
function cmdStatus() {
|
|
426
|
+
const path = join(stateDir(), "status.json");
|
|
427
|
+
if (!existsSync(path)) {
|
|
428
|
+
console.log("no status.json yet — is the daemon running?");
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
console.log(renderStatus(JSON.parse(readFileSync(path, "utf8"))));
|
|
432
|
+
}
|
|
433
|
+
function cmdSpend() {
|
|
434
|
+
const store = openStore();
|
|
435
|
+
try {
|
|
436
|
+
const total = store.totalSpend();
|
|
437
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
438
|
+
// est_usd is the one spend field; expired rounds count (`roundUsage`).
|
|
439
|
+
const todayRounds = store.roundsOn(today);
|
|
440
|
+
const day = roundUsage(todayRounds);
|
|
441
|
+
const totals = store.db
|
|
442
|
+
.prepare("SELECT COALESCE(SUM(tokens_in), 0) AS tin, COALESCE(SUM(tokens_out), 0) AS tout FROM rounds")
|
|
443
|
+
.get();
|
|
444
|
+
console.log(`total spend: $${total.toFixed(2)} (est) — ${totals.tin} in / ${totals.tout} out tokens`);
|
|
445
|
+
console.log(`today (${today}): $${day.spend_usd.toFixed(2)} across ${todayRounds.length} rounds — ${day.tokens_in} in / ${day.tokens_out} out tokens`);
|
|
446
|
+
}
|
|
447
|
+
finally {
|
|
448
|
+
store.close();
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
function cmdToday() {
|
|
452
|
+
const store = openStore();
|
|
453
|
+
try {
|
|
454
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
455
|
+
const rounds = store.roundsOn(today);
|
|
456
|
+
console.log(`rounds on ${today}: ${rounds.length}`);
|
|
457
|
+
for (const r of rounds) {
|
|
458
|
+
const tokens = r.tokens_in !== null || r.tokens_out !== null
|
|
459
|
+
? ` tok=${r.tokens_in ?? 0}/${r.tokens_out ?? 0}`
|
|
460
|
+
: "";
|
|
461
|
+
// A cancelled round says why inline — its status alone does not.
|
|
462
|
+
const why = r.status === "cancelled" ? ` cause=${cancelCauseLabel(r.cancel_cause)}` : "";
|
|
463
|
+
console.log(`#${r.pr} round ${r.round_no} (${r.round_type}) [${r.status}]${why} verdict=${r.verdict ?? "—"} spend=$${(r.est_usd ?? 0).toFixed(2)}${tokens} lenses=${r.lenses}`);
|
|
464
|
+
}
|
|
465
|
+
console.log(todayCancellationLine(cancellationsOn(store, today)));
|
|
466
|
+
const events = store.eventsOn(today);
|
|
467
|
+
console.log(`events: ${events.length}`);
|
|
468
|
+
for (const e of events.slice(-40))
|
|
469
|
+
console.log(` ${e.ts} ${e.kind}${e.pr ? ` #${e.pr}` : ""}`);
|
|
470
|
+
}
|
|
471
|
+
finally {
|
|
472
|
+
store.close();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/** The PR's archive manifest in brief, read in this process (synchronous:
|
|
476
|
+
* `qcr why` is a CLI, never the daemon). Undefined when there is none. */
|
|
477
|
+
export function archiveManifestSummary(archiveDir) {
|
|
478
|
+
const path = join(archiveDir, "manifest.json");
|
|
479
|
+
let text;
|
|
480
|
+
try {
|
|
481
|
+
text = readFileSync(path, "utf8");
|
|
482
|
+
}
|
|
483
|
+
catch (err) {
|
|
484
|
+
if (err.code === "ENOENT")
|
|
485
|
+
return undefined;
|
|
486
|
+
return { state: "unreadable", error: err.message };
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
return { state: "read", ...summarizeManifest(parseManifestText(text, path)) };
|
|
490
|
+
}
|
|
491
|
+
catch (err) {
|
|
492
|
+
if (err instanceof ManifestUnreadable)
|
|
493
|
+
return { state: "unreadable", error: err.message };
|
|
494
|
+
throw err;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
/** `qcr why`'s archive manifest line. */
|
|
498
|
+
export function archiveManifestLine(m) {
|
|
499
|
+
if (m.state === "unreadable")
|
|
500
|
+
return `archive manifest: UNREADABLE (${oneLine(m.error)})`;
|
|
501
|
+
return (`archive manifest: ${m.passes} pass(es), last ${m.last_pass_at ?? "unknown"}; ` +
|
|
502
|
+
`${m.transcripts} transcript(s), ${m.files} large file(s), ${m.bundles} bundle(s) of ${m.records} record(s) — ` +
|
|
503
|
+
`${fmtBytes(m.bytes)} → ${fmtBytes(m.gz_bytes)}; ${m.sources_removed} live source(s) removed` +
|
|
504
|
+
(m.duplicates_removed > 0 ? `, ${m.duplicates_removed} legacy duplicate(s) removed` : "") +
|
|
505
|
+
(m.restores > 0 ? `; ${m.restores} restore(s)` : ""));
|
|
506
|
+
}
|
|
507
|
+
/** Best-effort GitHub enrichment for `why --json` (description, labels, last
|
|
508
|
+
* comments). Read-only; any failure leaves the payload un-enriched.
|
|
509
|
+
* `secretsDir` is an injection seam for tests — pointing it at an empty dir
|
|
510
|
+
* makes the credential lookup fail fast instead of hitting the real network
|
|
511
|
+
* when the developer machine carries the app credential. */
|
|
512
|
+
export async function enrichWhy(payload, secretsDir) {
|
|
513
|
+
try {
|
|
514
|
+
const { TokenMinter } = await import("@quickchatai/pi-quickreview/src/githubAuth.js");
|
|
515
|
+
const { GitHubAdapter } = await import("./github.js");
|
|
516
|
+
const cfg = loadConfig();
|
|
517
|
+
const gate = recordOnlyGate(join(stateDir(), "dryrun"), "qcr why");
|
|
518
|
+
const adapter = new GitHubAdapter(cfg, new TokenMinter(secretsDir ?? cfg.github.secrets_dir), gate);
|
|
519
|
+
const convo = await adapter.prConversation(payload.pr, 3);
|
|
520
|
+
return { ...payload, description: convo.body, labels: convo.labels, comments: convo.comments };
|
|
521
|
+
}
|
|
522
|
+
catch {
|
|
523
|
+
return payload;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
/** A kept-not-posted finding (doubted or withheld) as `why --json` carries it. */
|
|
527
|
+
function keptFinding(f) {
|
|
528
|
+
return {
|
|
529
|
+
round_no: f.round_no,
|
|
530
|
+
lens: f.lens,
|
|
531
|
+
path: f.path,
|
|
532
|
+
line: f.line,
|
|
533
|
+
severity: f.severity,
|
|
534
|
+
word: f.word,
|
|
535
|
+
reason: f.reason,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
export function whyJson(store, n, archiveDir) {
|
|
539
|
+
const pr = store.getPr(n);
|
|
540
|
+
const rounds = store.roundsFor(n);
|
|
541
|
+
const threadTotal = store.db
|
|
542
|
+
.prepare("SELECT COUNT(*) AS c FROM bot_comments WHERE pr = ?")
|
|
543
|
+
.get(n);
|
|
544
|
+
const payload = {
|
|
545
|
+
pr: n,
|
|
546
|
+
known: pr !== undefined || rounds.length > 0,
|
|
547
|
+
// Every round's cost, expired ones included.
|
|
548
|
+
spend_usd: roundUsage(rounds).spend_usd,
|
|
549
|
+
rounds: rounds.map((r) => ({
|
|
550
|
+
round_no: r.round_no,
|
|
551
|
+
round_type: r.round_type,
|
|
552
|
+
status: r.status,
|
|
553
|
+
verdict: r.verdict ?? null,
|
|
554
|
+
started_at: r.started_at,
|
|
555
|
+
deadline_at: r.deadline_at,
|
|
556
|
+
finished_at: r.finished_at ?? null,
|
|
557
|
+
spend_usd: r.spend_usd ?? null,
|
|
558
|
+
est_usd: r.est_usd ?? null,
|
|
559
|
+
tokens_in: r.tokens_in ?? null,
|
|
560
|
+
tokens_out: r.tokens_out ?? null,
|
|
561
|
+
lenses: r.lenses,
|
|
562
|
+
validate_tier: r.validate_tier ?? null,
|
|
563
|
+
executed_passes: parseExecutedPasses(r.executed_passes),
|
|
564
|
+
summary: r.summary ?? null,
|
|
565
|
+
// Why a cancelled round was cancelled — null on every other
|
|
566
|
+
// status. The TUI's detail pane reads this beside the prose summary.
|
|
567
|
+
cancel_cause: r.cancel_cause ?? null,
|
|
568
|
+
})),
|
|
569
|
+
// The registry knows every bot thread; "open" needs GitHub truth the
|
|
570
|
+
// daemon doesn't mirror, so it is UNKNOWN (null) — never the total.
|
|
571
|
+
threads: { open: null, total: threadTotal.c },
|
|
572
|
+
doubted: store.doubtedFindings(n).map(keptFinding),
|
|
573
|
+
withheld: store.withheldFindings(n).map(keptFinding),
|
|
574
|
+
};
|
|
575
|
+
if (pr) {
|
|
576
|
+
payload.state = pr.state;
|
|
577
|
+
payload.title = pr.title;
|
|
578
|
+
payload.head_sha = pr.head_sha;
|
|
579
|
+
payload.author = pr.author;
|
|
580
|
+
payload.trigger_ts = pr.trigger_ts;
|
|
581
|
+
payload.stale_recheck = Boolean(pr.stale_recheck);
|
|
582
|
+
payload.updated_at = pr.updated_at;
|
|
583
|
+
}
|
|
584
|
+
if (existsSync(archiveDir)) {
|
|
585
|
+
payload.archive = readdirSync(archiveDir);
|
|
586
|
+
const ledger = join(archiveDir, "ledger.md");
|
|
587
|
+
if (existsSync(ledger))
|
|
588
|
+
payload.ledger = readFileSync(ledger, "utf8");
|
|
589
|
+
const manifest = archiveManifestSummary(archiveDir);
|
|
590
|
+
if (manifest)
|
|
591
|
+
payload.archive_manifest = manifest;
|
|
592
|
+
}
|
|
593
|
+
return payload;
|
|
594
|
+
}
|
|
595
|
+
export function todayJson(store, today) {
|
|
596
|
+
const rounds = store.roundsOn(today);
|
|
597
|
+
return {
|
|
598
|
+
date: today,
|
|
599
|
+
rounds,
|
|
600
|
+
...roundUsage(rounds),
|
|
601
|
+
events: store.eventsOn(today),
|
|
602
|
+
cancellations: cancellationsOn(store, today),
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
/** Today's cancellations grouped by cause — the same tally the
|
|
606
|
+
* metrics payload carries, scoped to one timezone-agnostic ISO date so `qcr
|
|
607
|
+
* today` can print it beside the rounds it explains. */
|
|
608
|
+
export function cancellationsOn(store, date) {
|
|
609
|
+
return tallyCancellations(store.roundsOn(date));
|
|
610
|
+
}
|
|
611
|
+
/** `qcr today`'s one-line cancellation summary. Never silent: "none" is an
|
|
612
|
+
* answer. */
|
|
613
|
+
export function todayCancellationLine(tallies) {
|
|
614
|
+
return cancellationSummaryLine("cancelled", tallies);
|
|
615
|
+
}
|
|
616
|
+
/** `qcr why`'s first line; the title is the author's text, so one line. */
|
|
617
|
+
export function whyHeaderLine(n, pr) {
|
|
618
|
+
return `#${n} state=${pr.state} head=${pr.head_sha.slice(0, 10)} stale=${pr.stale_recheck} "${oneLine(pr.title)}"`;
|
|
619
|
+
}
|
|
620
|
+
function cmdWhy(n) {
|
|
621
|
+
const archiveDir = join(sandboxRoot(), "archive", `pr-${n}`);
|
|
622
|
+
const store = openStore();
|
|
623
|
+
try {
|
|
624
|
+
const pr = store.getPr(n);
|
|
625
|
+
if (pr)
|
|
626
|
+
console.log(whyHeaderLine(n, pr));
|
|
627
|
+
const rounds = store.roundsFor(n);
|
|
628
|
+
for (const r of rounds)
|
|
629
|
+
console.log(`round ${r.round_no} (${r.round_type}) [${r.status}] verdict=${r.verdict ?? "—"} started=${r.started_at}`);
|
|
630
|
+
if (existsSync(archiveDir)) {
|
|
631
|
+
console.log(`archive: ${archiveDir}`);
|
|
632
|
+
for (const f of readdirSync(archiveDir))
|
|
633
|
+
console.log(` ${f}`);
|
|
634
|
+
const manifest = archiveManifestSummary(archiveDir);
|
|
635
|
+
if (manifest)
|
|
636
|
+
console.log(archiveManifestLine(manifest));
|
|
637
|
+
const ledger = join(archiveDir, "ledger.md");
|
|
638
|
+
if (existsSync(ledger)) {
|
|
639
|
+
console.log("--- archived ledger ---");
|
|
640
|
+
console.log(archivedLedgerText(readFileSync(ledger, "utf8")));
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
else if (!pr && rounds.length === 0) {
|
|
644
|
+
console.log(`nothing known about #${n}`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
finally {
|
|
648
|
+
store.close();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/** `qcr metrics [--pr <n>] [--json]` — the aggregation engine over the live
|
|
652
|
+
* sqlite (read-only, container-safe: same store/config opening as `why`). */
|
|
653
|
+
async function cmdMetrics(pr, json) {
|
|
654
|
+
const store = openStore();
|
|
655
|
+
try {
|
|
656
|
+
if (pr !== null) {
|
|
657
|
+
const payload = computeTimeline(store, pr);
|
|
658
|
+
console.log(json ? JSON.stringify(payload) : renderTimeline(payload));
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
// Day buckets follow the ops timezone. `qcr metrics` must stay
|
|
662
|
+
// usable beside a bare state.db, so an unreadable config degrades to UTC
|
|
663
|
+
// rather than refusing.
|
|
664
|
+
let timeZone = "UTC";
|
|
665
|
+
try {
|
|
666
|
+
timeZone = loadConfig().ops.timezone;
|
|
667
|
+
}
|
|
668
|
+
catch {
|
|
669
|
+
/* config genuinely unavailable — UTC day buckets */
|
|
670
|
+
}
|
|
671
|
+
const payload = computeMetrics(store, { timeZone });
|
|
672
|
+
// The operational half is file-derived: status.json (heartbeat/dry-run/
|
|
673
|
+
// concurrency) + the BUILD_SHA marker deploy.sh writes into the sandbox
|
|
674
|
+
// root. parseBuildSha is deploytab's — the marker format has one parser.
|
|
675
|
+
const { parseBuildSha } = await import("./tui/deploytab.js");
|
|
676
|
+
payload.ops = readOps({
|
|
677
|
+
statusPath: join(stateDir(), "status.json"),
|
|
678
|
+
buildShaPath: join(sandboxRoot(), "BUILD_SHA"),
|
|
679
|
+
}, { readFile: (p) => readFileSync(p, "utf8"), parseBuildSha }, payload.ops);
|
|
680
|
+
console.log(json ? JSON.stringify(payload) : renderMetrics(payload));
|
|
681
|
+
}
|
|
682
|
+
finally {
|
|
683
|
+
store.close();
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
const BACKFILL_USAGE = "usage: qcr backfill [--dry-run] [--limit <n>] [--json]";
|
|
687
|
+
/** `qcr backfill`'s arguments (`--json` is taken off before). An unknown or
|
|
688
|
+
* repeated token is refused: a mistyped `--dry-run` must not run the import. */
|
|
689
|
+
export function parseBackfillArgs(rest) {
|
|
690
|
+
let dryRun = false;
|
|
691
|
+
let limit;
|
|
692
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
693
|
+
const a = rest[i];
|
|
694
|
+
if (a === "--dry-run" && !dryRun)
|
|
695
|
+
dryRun = true;
|
|
696
|
+
else if (a === "--limit" && limit === undefined && rest[i + 1] !== undefined) {
|
|
697
|
+
limit = Number(rest[i + 1]);
|
|
698
|
+
i += 1;
|
|
699
|
+
if (!Number.isInteger(limit) || limit < 0)
|
|
700
|
+
throw new Error(BACKFILL_USAGE);
|
|
701
|
+
}
|
|
702
|
+
else
|
|
703
|
+
throw new Error(BACKFILL_USAGE);
|
|
704
|
+
}
|
|
705
|
+
return { dryRun, limit };
|
|
706
|
+
}
|
|
707
|
+
/** `qcr backfill [--dry-run] [--limit N]` — the one-shot pre-daemon history
|
|
708
|
+
* import. Read-only against GitHub: it opens the adapter purely for
|
|
709
|
+
* its search/GraphQL reads and never reaches a mutation surface. */
|
|
710
|
+
export async function cmdBackfill(dryRun, limit, json) {
|
|
711
|
+
const { TokenMinter } = await import("@quickchatai/pi-quickreview/src/githubAuth.js");
|
|
712
|
+
const { GitHubAdapter } = await import("./github.js");
|
|
713
|
+
const { runBackfill } = await import("./backfill.js");
|
|
714
|
+
const cfg = loadConfig();
|
|
715
|
+
// A gate that can only ever record: the importer performs no mutations, and
|
|
716
|
+
// this keeps that structural rather than prompted.
|
|
717
|
+
const gate = recordOnlyGate(join(stateDir(), "dryrun"), "qcr backfill");
|
|
718
|
+
const adapter = new GitHubAdapter(cfg, new TokenMinter(cfg.github.secrets_dir), gate);
|
|
719
|
+
const store = openStore();
|
|
720
|
+
try {
|
|
721
|
+
const stats = await runBackfill(store, adapter, cfg, {
|
|
722
|
+
dryRun,
|
|
723
|
+
...(limit !== undefined ? { limit } : {}),
|
|
724
|
+
log: (line) => process.stderr.write(`${line}\n`),
|
|
725
|
+
});
|
|
726
|
+
if (json)
|
|
727
|
+
console.log(JSON.stringify(stats));
|
|
728
|
+
}
|
|
729
|
+
finally {
|
|
730
|
+
store.close();
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
/** `--months N` / `--limit N`, or undefined; a malformed value is an error. */
|
|
734
|
+
function intFlag(rest, flag, usage) {
|
|
735
|
+
const i = rest.indexOf(flag);
|
|
736
|
+
if (i < 0)
|
|
737
|
+
return undefined;
|
|
738
|
+
const n = Number(rest[i + 1]);
|
|
739
|
+
if (!Number.isInteger(n) || n < 0)
|
|
740
|
+
throw new Error(usage);
|
|
741
|
+
return n;
|
|
742
|
+
}
|
|
743
|
+
const HISTORY_USAGE = "usage: qcr history <digest [--months <n>] [--limit <n>] | pull [--months <n>]>";
|
|
744
|
+
/**
|
|
745
|
+
* `qcr history` — the review history digest setup reads.
|
|
746
|
+
*
|
|
747
|
+
* `digest` runs WHERE THE APP CREDENTIAL IS (in the VM container) and prints
|
|
748
|
+
* the digest to stdout, writing nothing: read-only against GitHub, built with
|
|
749
|
+
* the same record-only gate `qcr backfill` builds, so no mutation is reachable
|
|
750
|
+
* from it even by mistake. `pull` runs on the PC: it runs `digest` over the
|
|
751
|
+
* TUI's route and writes the result into THIS install's state home — the only
|
|
752
|
+
* place the raw comments ever land.
|
|
753
|
+
*/
|
|
754
|
+
export async function cmdHistory(rest) {
|
|
755
|
+
const sub = rest[0];
|
|
756
|
+
const months = intFlag(rest, "--months", HISTORY_USAGE);
|
|
757
|
+
if (sub === "digest") {
|
|
758
|
+
const limit = intFlag(rest, "--limit", HISTORY_USAGE);
|
|
759
|
+
const { TokenMinter } = await import("@quickchatai/pi-quickreview/src/githubAuth.js");
|
|
760
|
+
const { GitHubAdapter } = await import("./github.js");
|
|
761
|
+
const { collectReviewHistory } = await import("./reviewhistory.js");
|
|
762
|
+
const cfg = loadConfig();
|
|
763
|
+
const gate = recordOnlyGate(join(stateDir(), "dryrun"), "qcr history");
|
|
764
|
+
const adapter = new GitHubAdapter(cfg, new TokenMinter(cfg.github.secrets_dir), gate);
|
|
765
|
+
const store = openStore();
|
|
766
|
+
try {
|
|
767
|
+
const digest = await collectReviewHistory(adapter, store, cfg, {
|
|
768
|
+
...(months !== undefined ? { months } : {}),
|
|
769
|
+
...(limit !== undefined ? { limit } : {}),
|
|
770
|
+
log: (line) => process.stderr.write(`${line}\n`),
|
|
771
|
+
});
|
|
772
|
+
process.stdout.write(`${JSON.stringify(digest)}\n`);
|
|
773
|
+
return 0;
|
|
774
|
+
}
|
|
775
|
+
finally {
|
|
776
|
+
store.close();
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (sub === "pull") {
|
|
780
|
+
const { runnerFor } = await import("./tui/remote.js");
|
|
781
|
+
const { pullReviewHistory } = await import("./tui/historypull.js");
|
|
782
|
+
const { stateHome } = await import("./install.js");
|
|
783
|
+
const cfg = loadConfig();
|
|
784
|
+
const r = await pullReviewHistory({
|
|
785
|
+
tui: cfg.tui,
|
|
786
|
+
ssh: runnerFor(cfg.tui),
|
|
787
|
+
stateHome: stateHome(),
|
|
788
|
+
repo: cfg.github.repo,
|
|
789
|
+
...(months !== undefined ? { months } : {}),
|
|
790
|
+
log: (line) => console.log(line),
|
|
791
|
+
});
|
|
792
|
+
if (!r.ok) {
|
|
793
|
+
console.log(`✖ ${r.reason}`);
|
|
794
|
+
return 1;
|
|
795
|
+
}
|
|
796
|
+
return 0;
|
|
797
|
+
}
|
|
798
|
+
throw new Error(HISTORY_USAGE);
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* `qcr regression` — run the human-review case set against the
|
|
802
|
+
* reviewer and write the investigation.
|
|
803
|
+
*
|
|
804
|
+
* The gate is built HERE, exactly as `cmdBackfill` builds its own: a dry-run
|
|
805
|
+
* gate whose executor throws. That is not a claim copied out of `config.toml`
|
|
806
|
+
* — `runRegression` refuses to start unless the object it is handed reports
|
|
807
|
+
* dry-run, and this is the only gate in the process. `src/replay.ts` holds no
|
|
808
|
+
* adapter and no mutation surface at all, so there is nothing for a gate to
|
|
809
|
+
* front even if one were live.
|
|
810
|
+
*/
|
|
811
|
+
export async function cmdRegression(rest, json) {
|
|
812
|
+
const { nodeReplayIo, parseRegressionArgs, readCaseSet, runRegressionCli, ScratchReplay } = await import("./replay.js");
|
|
813
|
+
const root = sandboxRoot();
|
|
814
|
+
// The replay plans rounds with `buildPassPlan`, so it must see the
|
|
815
|
+
// profile's overlays exactly as the daemon does (wire.ts buildParts, the
|
|
816
|
+
// same function) — otherwise a lens the profile switched off would be
|
|
817
|
+
// replayed.
|
|
818
|
+
const { applyProfileOverlays } = await import("@quickchatai/pi-quickreview/src/profiles.js");
|
|
819
|
+
const cfg = applyProfileOverlays(root, loadConfig(), homeProfileLookup());
|
|
820
|
+
const args = parseRegressionArgs(rest, json);
|
|
821
|
+
const gate = recordOnlyGate(join(stateDir(), "dryrun"), "qcr regression");
|
|
822
|
+
const scratchRoot = join(stateDir(), "regression");
|
|
823
|
+
return runRegressionCli(args, {
|
|
824
|
+
loadCaseSet: (p) => readCaseSet(p ?? join(root, "tests", "fixtures", "regression-cases.json")),
|
|
825
|
+
gate,
|
|
826
|
+
makeReplay: async (g) => {
|
|
827
|
+
const io = await nodeReplayIo({ cfg, sandboxRoot: root, scratchRoot });
|
|
828
|
+
return new ScratchReplay({
|
|
829
|
+
gate: g,
|
|
830
|
+
cfg,
|
|
831
|
+
sandboxRoot: root,
|
|
832
|
+
scratchRoot,
|
|
833
|
+
io,
|
|
834
|
+
...(args.clonePath !== undefined ? { baseClone: args.clonePath } : {}),
|
|
835
|
+
log: (l) => console.log(l),
|
|
836
|
+
}).replay;
|
|
837
|
+
},
|
|
838
|
+
reportPath: (ranAt) => join(root, "reports", `regression-${ranAt.replace(/[:.]/g, "-")}.md`),
|
|
839
|
+
writeReport: (p, body) => {
|
|
840
|
+
mkdirSync(join(p, ".."), { recursive: true });
|
|
841
|
+
writeFileSync(p, body);
|
|
842
|
+
},
|
|
843
|
+
log: (line) => console.log(line),
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
// ---- deploy-window drain -----------------------------------------------------
|
|
847
|
+
//
|
|
848
|
+
// `qcr drain on|off` works as the TUI's unwedge does: ONE validated command
|
|
849
|
+
// file written as root into the daemon's sealed commands/operator/ dir
|
|
850
|
+
// (docker exec over ssh, payload on stdin, tmp+mv), then the daemon's own
|
|
851
|
+
// result file read back. src/commands.ts re-validates against the same
|
|
852
|
+
// whitelist and the same tier gate; nothing here touches GitHub or goes near
|
|
853
|
+
// the MutationGate. `--local` writes the file into THIS machine's sandbox
|
|
854
|
+
// commands/operator/ dir instead, as the current user (for running inside the
|
|
855
|
+
// container or against a local dev daemon) — sealed only if that dir is
|
|
856
|
+
// root-owned; otherwise the daemon bounds it like a session's drain.
|
|
857
|
+
/** The documented manual fallback: the exact one-liner that writes the same
|
|
858
|
+
* command file by hand when the runner path fails — over ssh, or straight
|
|
859
|
+
* into local docker when `[tui] target = "local"`, the transport the runner
|
|
860
|
+
* path itself used. */
|
|
861
|
+
export function drainManualCommand(tui, on, now, hold = false) {
|
|
862
|
+
// Same root-owned operator directory the runner path writes: a
|
|
863
|
+
// fallback that wrote into the session dir would be a bounded session drain.
|
|
864
|
+
const docker = `${tui.sudo_docker ? "sudo " : ""}docker exec -i -u 0 ${tui.container}`;
|
|
865
|
+
// A fresh name each time the fallback is printed, so the `off` after an
|
|
866
|
+
// `on` (or the next deploy's `on`) never lands on a result file that already
|
|
867
|
+
// holds an answer. The daemon's handled-ledger is keyed by file VERSION, so
|
|
868
|
+
// a re-used name (an operator editing an old line) still executes — the
|
|
869
|
+
// fresh name is what keeps its result readable.
|
|
870
|
+
const name = `${now.toISOString().replace(/[:.]/g, "-")}-manual-drain.json`;
|
|
871
|
+
// drainPayload's own object, compacted onto the one line.
|
|
872
|
+
const payload = `printf '%s' '${JSON.stringify(JSON.parse(drainPayload(on, hold)))}' | `;
|
|
873
|
+
// The script as the container's single `sh -c` argument.
|
|
874
|
+
const script = shellQuote(operatorCommandWriteScript(tui, name));
|
|
875
|
+
if (transportOf(tui) === "local")
|
|
876
|
+
return `${payload}${docker} sh -c ${script}`;
|
|
877
|
+
// Over ssh, also backslash, dollar, double quote and backtick for the LOCAL
|
|
878
|
+
// double-quoted ssh argument, which would otherwise expand them on the
|
|
879
|
+
// operator's machine.
|
|
880
|
+
const remote = script.replace(/[\\$"`]/g, "\\$&");
|
|
881
|
+
return `${payload}ssh ${tui.ssh_host} "${docker} sh -c ${remote}"`;
|
|
882
|
+
}
|
|
883
|
+
/** Write the drain command through the configured transport and wait for the
|
|
884
|
+
* daemon's verdict. Returns a process exit code; every failure path prints
|
|
885
|
+
* the manual fallback so the operator is never stuck behind this wrapper. */
|
|
886
|
+
export async function runDrain(on, deps, hold = false) {
|
|
887
|
+
const tui = deps.cfg.tui;
|
|
888
|
+
const where = transportOf(tui) === "local" ? `local docker ${tui.container}` : tui.ssh_host;
|
|
889
|
+
// Say the directory the command will really be written to: the root follows the container's layout
|
|
890
|
+
// label (the cutover recreates the container under a running operator), and an unresolved root is the
|
|
891
|
+
// legacy one.
|
|
892
|
+
await resolveLayout(tui, deps.ssh);
|
|
893
|
+
deps.log(`drain ${on ? (hold ? "on --hold" : "on") : "off"} → ${where}:${remoteSandboxRoot(tui)}/commands/`);
|
|
894
|
+
const sender = new CommandDrainSender(tui, deps.ssh, deps.now, deps.sleep);
|
|
895
|
+
const result = await sender.set(on, hold);
|
|
896
|
+
if (result.ok && on && hold && result.hold !== true) {
|
|
897
|
+
// The daemon took a drain but did not say it is a hold: a boot may lift it.
|
|
898
|
+
deps.log("✖ the drain is ON but the daemon did not confirm the HOLD — the next healthy boot may lift it; qcr drain off, then check the daemon's version");
|
|
899
|
+
return 1;
|
|
900
|
+
}
|
|
901
|
+
if (result.ok) {
|
|
902
|
+
if (on) {
|
|
903
|
+
deps.log(`✓ draining — ${result.running_rounds ?? 0} round(s) still finishing; deploy when \`qcr status\` shows rounds running: 0`);
|
|
904
|
+
if (result.issuer === "session") {
|
|
905
|
+
// The daemon did not see the operator seal on commands/operator/ (the
|
|
906
|
+
// directory was writable by its own uid), so it bounded this drain
|
|
907
|
+
// like a session's. Saying so beats a drain that lifts mid-deploy.
|
|
908
|
+
deps.log(` ⚠ NOT an operator drain: the daemon found commands/operator/ unsealed and bounded it — lapses ${result.expires_at ?? "(unknown)"}`);
|
|
909
|
+
deps.log(" check the directory is root-owned and 0755 in the container before deploying");
|
|
910
|
+
}
|
|
911
|
+
deps.log(hold
|
|
912
|
+
? " HOLD: no boot lifts it — only `qcr drain off` does"
|
|
913
|
+
: " (auto-lifts on the new boot's first healthy reconcile — re-drain before each step of a multi-deploy window)");
|
|
914
|
+
}
|
|
915
|
+
else {
|
|
916
|
+
deps.log(`✓ drain lifted — ${result.dropped_deliveries ?? 0} webhook delivery(ies) were dropped; the reconcile poll recovers them`);
|
|
917
|
+
}
|
|
918
|
+
return 0;
|
|
919
|
+
}
|
|
920
|
+
deps.log(`✖ drain refused: ${result.reason ?? "no reason given"}`);
|
|
921
|
+
deps.log("manual fallback — run:");
|
|
922
|
+
deps.log(` ${drainManualCommand(tui, on, (deps.now ?? (() => new Date()))(), hold)}`);
|
|
923
|
+
return 1;
|
|
924
|
+
}
|
|
925
|
+
/** `--local`: same command file, written straight into this machine's
|
|
926
|
+
* sandbox commands/operator/ dir as the current user (in-container use; no
|
|
927
|
+
* ssh). Fire-and-forget — the
|
|
928
|
+
* local watcher's result lands in commands/results/. */
|
|
929
|
+
function runDrainLocal(on, hold) {
|
|
930
|
+
const dir = join(sandboxRoot(), "commands", "operator");
|
|
931
|
+
mkdirSync(dir, { recursive: true });
|
|
932
|
+
const name = `${new Date().toISOString().replace(/[:.]/g, "-")}-cli-drain-${randomUUID()}.json`;
|
|
933
|
+
writeFileSync(join(dir, `.${name}.tmp`), drainPayload(on, hold));
|
|
934
|
+
renameSync(join(dir, `.${name}.tmp`), join(dir, name));
|
|
935
|
+
console.log(`drain ${on ? (hold ? "on --hold" : "on") : "off"} command written to ${join(dir, name)} — result lands in commands/results/`);
|
|
936
|
+
console.log("note: written as THIS user, so unless commands/operator/ is root-owned the daemon sees no operator seal and bounds the drain like a session's");
|
|
937
|
+
return 0;
|
|
938
|
+
}
|
|
939
|
+
const DRAIN_USAGE = "usage: qcr drain <on [--hold]|off> [--local]";
|
|
940
|
+
/** `qcr drain`'s arguments: exactly one of `on`/`off`, optionally `--local`,
|
|
941
|
+
* and `--hold` with `on` only (the operator's no-auto-lift drain).
|
|
942
|
+
* Anything else is refused before a transport is picked or a command file
|
|
943
|
+
* written — a mistyped `--local` must not send the drain over ssh instead. */
|
|
944
|
+
export function parseDrainArgs(rest) {
|
|
945
|
+
let sub;
|
|
946
|
+
let local = false;
|
|
947
|
+
let hold = false;
|
|
948
|
+
for (const a of rest) {
|
|
949
|
+
if ((a === "on" || a === "off") && sub === undefined)
|
|
950
|
+
sub = a;
|
|
951
|
+
else if (a === "--local" && !local)
|
|
952
|
+
local = true;
|
|
953
|
+
else if (a === "--hold" && !hold)
|
|
954
|
+
hold = true;
|
|
955
|
+
else
|
|
956
|
+
throw new Error(DRAIN_USAGE);
|
|
957
|
+
}
|
|
958
|
+
if (sub === undefined)
|
|
959
|
+
throw new Error(DRAIN_USAGE);
|
|
960
|
+
if (hold && sub !== "on")
|
|
961
|
+
throw new Error(DRAIN_USAGE);
|
|
962
|
+
return { on: sub === "on", local, hold };
|
|
963
|
+
}
|
|
964
|
+
async function cmdDrain(rest) {
|
|
965
|
+
const { on, local, hold } = parseDrainArgs(rest);
|
|
966
|
+
if (local)
|
|
967
|
+
return runDrainLocal(on, hold);
|
|
968
|
+
// Honour [tui] target — on the laptop topology the same command
|
|
969
|
+
// file write runs against local docker instead of ssh.
|
|
970
|
+
const { runnerFor } = await import("./tui/remote.js");
|
|
971
|
+
const cfg = loadConfig();
|
|
972
|
+
return runDrain(on, { cfg, ssh: runnerFor(cfg.tui), log: (l) => console.log(l) }, hold);
|
|
973
|
+
}
|
|
974
|
+
// ---- the operator's per-PR pause ---------------------------------------------------
|
|
975
|
+
//
|
|
976
|
+
// `qcr pause-pr <n>` / `qcr resume-pr <n>`: the operator's `pause-pr` /
|
|
977
|
+
// `resume-pr`, written as root into the sealed commands/operator/ (the drain's
|
|
978
|
+
// channel) and confirmed by the daemon's own result file
|
|
979
|
+
// (src/tui/prpause.ts). From that channel a pause carries no expiry: it stands
|
|
980
|
+
// until `qcr resume-pr`, and no session lifts it. The cutover wizard's stage 9
|
|
981
|
+
// pauses every tracked PR but one with it before the drain is lifted.
|
|
982
|
+
export const PR_LEVER_USAGE = "usage: qcr pause-pr <pr> [--reason <text>] | qcr resume-pr <pr>";
|
|
983
|
+
/** `qcr pause-pr|resume-pr`'s arguments: one PR number, and for a pause an
|
|
984
|
+
* optional `--reason <text>` (the reason the board and `qcr status` show). */
|
|
985
|
+
export function parsePrLeverArgs(verb, rest) {
|
|
986
|
+
let pr;
|
|
987
|
+
let reason;
|
|
988
|
+
for (let i = 0; i < rest.length; i++) {
|
|
989
|
+
const a = rest[i];
|
|
990
|
+
if (a === "--reason" && verb === "pause-pr" && reason === undefined) {
|
|
991
|
+
const v = rest[++i];
|
|
992
|
+
if (v === undefined || v.trim() === "")
|
|
993
|
+
throw new Error(PR_LEVER_USAGE);
|
|
994
|
+
reason = v;
|
|
995
|
+
}
|
|
996
|
+
else if (/^\d+$/.test(a) && Number(a) > 0 && pr === undefined)
|
|
997
|
+
pr = Number(a);
|
|
998
|
+
else
|
|
999
|
+
throw new Error(PR_LEVER_USAGE);
|
|
1000
|
+
}
|
|
1001
|
+
if (pr === undefined)
|
|
1002
|
+
throw new Error(PR_LEVER_USAGE);
|
|
1003
|
+
if (verb === "resume-pr")
|
|
1004
|
+
return { command: "resume-pr", pr };
|
|
1005
|
+
return { command: "pause-pr", pr, reason: reason ?? "paused by the operator (qcr pause-pr)" };
|
|
1006
|
+
}
|
|
1007
|
+
async function cmdPrLever(verb, rest) {
|
|
1008
|
+
const cmd = parsePrLeverArgs(verb, rest);
|
|
1009
|
+
const cfg = loadConfig();
|
|
1010
|
+
const { runnerFor } = await import("./tui/remote.js");
|
|
1011
|
+
const { PrLeverSender, renderPrLever } = await import("./tui/prpause.js");
|
|
1012
|
+
const ssh = runnerFor(cfg.tui);
|
|
1013
|
+
await resolveLayout(cfg.tui, ssh);
|
|
1014
|
+
const where = transportOf(cfg.tui) === "local" ? `local docker ${cfg.tui.container}` : cfg.tui.ssh_host;
|
|
1015
|
+
console.log(`${verb} #${cmd.pr} → ${where}:${remoteSandboxRoot(cfg.tui)}/commands/operator/`);
|
|
1016
|
+
const { lines, code } = renderPrLever(cmd, await new PrLeverSender(cfg.tui, ssh).send(cmd));
|
|
1017
|
+
for (const l of lines)
|
|
1018
|
+
console.log(l);
|
|
1019
|
+
return code;
|
|
1020
|
+
}
|
|
1021
|
+
// ---- takeover -----------------------------------------------------
|
|
1022
|
+
//
|
|
1023
|
+
// `qcr takeover <pr>`, `qcr debug-round <pr>` and `qcr release <pr>` are three
|
|
1024
|
+
// of the operator's control commands (src/commands.ts CONTROL_COMMANDS), sent down the
|
|
1025
|
+
// same root-sealed operator route the TUI's input line uses (src/tui/livecontrol.ts).
|
|
1026
|
+
// The daemon grants the lease and starts interactive pi on the PR's real session in tmux
|
|
1027
|
+
// `takeover-pr-<n>` (src/takeover.ts), or arms the PR's next round to run in tmux
|
|
1028
|
+
// `debug-pr-<n>` (src/debuground.ts); this verb only asks, and says how to attach.
|
|
1029
|
+
export const TAKEOVER_USAGE = "usage: qcr <takeover|debug-round|release> <pr-number>";
|
|
1030
|
+
/** `qcr takeover|release`'s arguments: exactly one PR number. */
|
|
1031
|
+
export function parseTakeoverArgs(rest) {
|
|
1032
|
+
if (rest.length !== 1 || !/^\d+$/.test(rest[0]) || Number(rest[0]) <= 0)
|
|
1033
|
+
throw new Error(TAKEOVER_USAGE);
|
|
1034
|
+
return Number(rest[0]);
|
|
1035
|
+
}
|
|
1036
|
+
/** The container name the attach hints print when no configured one is passed. */
|
|
1037
|
+
const DEFAULT_ATTACH_CONTAINER = "quickreview-sandbox";
|
|
1038
|
+
/** The lines that follow a granted debug round: the lease is armed for the
|
|
1039
|
+
* PR's next round, which a worker would otherwise take. */
|
|
1040
|
+
export function debugRoundAttachLines(pr, container = DEFAULT_ATTACH_CONTAINER) {
|
|
1041
|
+
const session = `debug-pr-${pr}`;
|
|
1042
|
+
return [
|
|
1043
|
+
`the PR's next round runs in interactive pi: tmux attach -t ${session}`,
|
|
1044
|
+
`(on the VM: docker exec -it ${container} tmux attach -t ${session})`,
|
|
1045
|
+
`it posts like any round, through the gate with the install's dry_run. \`qcr release ${pr}\` ends it; it ends with the verdict commit, and the round after goes back to a worker.`,
|
|
1046
|
+
];
|
|
1047
|
+
}
|
|
1048
|
+
/** The lines that follow a granted takeover: how to reach the pi. */
|
|
1049
|
+
export function takeoverAttachLines(pr, container = DEFAULT_ATTACH_CONTAINER) {
|
|
1050
|
+
const session = `takeover-pr-${pr}`;
|
|
1051
|
+
return [
|
|
1052
|
+
`attach: tmux attach -t ${session}`,
|
|
1053
|
+
`(on the VM: docker exec -it ${container} tmux attach -t ${session})`,
|
|
1054
|
+
`the TUI's PR pane shows it too. \`qcr release ${pr}\` ends it; it also ends after 30 minutes idle.`,
|
|
1055
|
+
];
|
|
1056
|
+
}
|
|
1057
|
+
async function cmdTakeover(kind, rest) {
|
|
1058
|
+
const pr = parseTakeoverArgs(rest);
|
|
1059
|
+
const cfg = loadConfig();
|
|
1060
|
+
const { runnerFor } = await import("./tui/remote.js");
|
|
1061
|
+
const { LiveControlSender } = await import("./tui/livecontrol.js");
|
|
1062
|
+
const outcome = await new LiveControlSender(cfg.tui, runnerFor(cfg.tui)).send({ kind, pr }, (state, label) => {
|
|
1063
|
+
if (state !== "pending")
|
|
1064
|
+
console.log(label);
|
|
1065
|
+
});
|
|
1066
|
+
const ok = outcome.state === "handled" || outcome.state === "queued";
|
|
1067
|
+
if (ok && kind === "takeover")
|
|
1068
|
+
for (const line of takeoverAttachLines(pr, cfg.tui.container))
|
|
1069
|
+
console.log(line);
|
|
1070
|
+
if (ok && kind === "debug-round")
|
|
1071
|
+
for (const line of debugRoundAttachLines(pr, cfg.tui.container))
|
|
1072
|
+
console.log(line);
|
|
1073
|
+
return ok ? 0 : 1;
|
|
1074
|
+
}
|
|
1075
|
+
// ---- the operator's message to a PR ------------------------------------
|
|
1076
|
+
const MESSAGE_USAGE = "usage: qcr message <pr> [--may-post] <text...>";
|
|
1077
|
+
/** `qcr message`'s arguments: the PR number, then the words of the message, with
|
|
1078
|
+
* `--may-post` anywhere before them. `may_post` is set ONLY by that flag: a message
|
|
1079
|
+
* posts nothing unless the operator says so here (or presses Alt-p in the viewer). */
|
|
1080
|
+
export function parseMessageArgs(rest) {
|
|
1081
|
+
let mayPost = false;
|
|
1082
|
+
const words = [];
|
|
1083
|
+
for (const a of rest) {
|
|
1084
|
+
if (a === "--may-post" && !mayPost && words.length <= 1)
|
|
1085
|
+
mayPost = true;
|
|
1086
|
+
else
|
|
1087
|
+
words.push(a);
|
|
1088
|
+
}
|
|
1089
|
+
const [prArg, ...text] = words;
|
|
1090
|
+
const pr = /^#?\d+$/.test(prArg ?? "") ? Number(prArg.replace("#", "")) : NaN;
|
|
1091
|
+
const body = text.join(" ").trim();
|
|
1092
|
+
if (!Number.isSafeInteger(pr) || pr <= 0 || body === "")
|
|
1093
|
+
throw new Error(MESSAGE_USAGE);
|
|
1094
|
+
return { pr, text: body, mayPost };
|
|
1095
|
+
}
|
|
1096
|
+
/** `qcr message`: send the operator's message to a PR's session through the same
|
|
1097
|
+
* operator channel the viewer's input line uses, and say how the daemon took it. */
|
|
1098
|
+
export async function cmdMessage(rest, deps = {}) {
|
|
1099
|
+
const { pr, text, mayPost } = parseMessageArgs(rest);
|
|
1100
|
+
const log = deps.log ?? ((l) => console.log(l));
|
|
1101
|
+
let sender = deps.sender;
|
|
1102
|
+
if (!sender) {
|
|
1103
|
+
const { runnerFor } = await import("./tui/remote.js");
|
|
1104
|
+
const { LiveControlSender } = await import("./tui/livecontrol.js");
|
|
1105
|
+
const cfg = loadConfig();
|
|
1106
|
+
sender = new LiveControlSender(cfg.tui, runnerFor(cfg.tui));
|
|
1107
|
+
}
|
|
1108
|
+
const out = await sender.send({ kind: "message", pr, text, ...(mayPost ? { may_post: true } : {}) }, (_state, label) => log(label));
|
|
1109
|
+
return out.state === "queued" || out.state === "handled" ? 0 : 1;
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* `qcr install <where|migrate>`. `where` prints what the install
|
|
1113
|
+
* locator resolves; `migrate` moves the checkout's old config.toml into the
|
|
1114
|
+
* install's config home, and every install profile out of the
|
|
1115
|
+
* checkout's `profiles/` — idempotent, and it never overwrites a differing
|
|
1116
|
+
* config or profile. Deliberately NOT loadConfig(): a config that does not
|
|
1117
|
+
* parse must still be movable. Both verbs cover the secrets too —
|
|
1118
|
+
* `where` names the secrets dir and the companion key file the locator picks
|
|
1119
|
+
* (without the configured `[github] secrets_dir`, which only a parsed config
|
|
1120
|
+
* knows), and `migrate` moves `~/.config/quickreview-bot/` into
|
|
1121
|
+
* `<config home>/secrets/` — and repoints a compose `.env` whose
|
|
1122
|
+
* `QCR_SECRETS_DIR` still names it. Nothing else reads those old
|
|
1123
|
+
* places, so `where` lists whatever is still STRANDED there, and
|
|
1124
|
+
* `migrate` is the one command that opens them.
|
|
1125
|
+
*/
|
|
1126
|
+
export function cmdInstall(rest, deps = {}) {
|
|
1127
|
+
if (rest.length !== 1)
|
|
1128
|
+
throw new Error("usage: qcr install <where|migrate>");
|
|
1129
|
+
const root = deps.root ?? sandboxRoot();
|
|
1130
|
+
const env = deps.env ?? process.env;
|
|
1131
|
+
const log = deps.log ?? ((line) => console.log(line));
|
|
1132
|
+
switch (rest[0]) {
|
|
1133
|
+
case "where": {
|
|
1134
|
+
const located = locateConfig({ env });
|
|
1135
|
+
log(`config ${located.path} (${located.source}${existsSync(located.path) ? "" : ", missing"})`);
|
|
1136
|
+
log(`config home ${located.home}`);
|
|
1137
|
+
log(`state home ${stateHome(env)}`);
|
|
1138
|
+
log(`state db ${join(operatorStateDir(root, env), "state.db")} (the operator verbs)`);
|
|
1139
|
+
log(`profiles ${profileSearchDirs({ root, env }).join(" → ")} (the checkout: generic examples only)`);
|
|
1140
|
+
const secrets = locateSecrets({ env });
|
|
1141
|
+
const keys = locateKeysFile({ env });
|
|
1142
|
+
log(`secrets ${secrets.path} (${secrets.source}; a set [github] secrets_dir overrides it)`);
|
|
1143
|
+
log(`memory keys ${keys.path} (${keys.source})`);
|
|
1144
|
+
// Nothing reads the old places, so an install that
|
|
1145
|
+
// has not migrated is told what it is stranding, and the way out.
|
|
1146
|
+
const stranded = [
|
|
1147
|
+
strandedCheckoutConfig({ root, env }),
|
|
1148
|
+
...strandedCheckoutProfiles({ root, env }),
|
|
1149
|
+
strandedLegacySecrets(env),
|
|
1150
|
+
].filter((p) => p !== null);
|
|
1151
|
+
for (const path of stranded)
|
|
1152
|
+
log(`stranded ${path} — no longer read; \`qcr install migrate\` moves it into the homes`);
|
|
1153
|
+
return 0;
|
|
1154
|
+
}
|
|
1155
|
+
case "migrate": {
|
|
1156
|
+
// The config, every install profile still in the checkout,
|
|
1157
|
+
// then the secrets — independent moves, so one refusal never
|
|
1158
|
+
// holds another back, and the exit code fails if any of them refused.
|
|
1159
|
+
const config = migrateReport(migrateConfig({ root, env }));
|
|
1160
|
+
const profiles = profileMigrateReport(migrateProfiles({ root, env }));
|
|
1161
|
+
const secrets = secretsMigrateReport(migrateSecrets({ env }));
|
|
1162
|
+
const compose = composeRepointReport(repointComposeSecrets({ root, env }));
|
|
1163
|
+
for (const line of [...config.lines, ...profiles.lines, ...secrets.lines, ...compose])
|
|
1164
|
+
log(line);
|
|
1165
|
+
return Math.max(config.code, profiles.code, secrets.code);
|
|
1166
|
+
}
|
|
1167
|
+
default:
|
|
1168
|
+
throw new Error("usage: qcr install <where|migrate>");
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
function cmdEvents(tail, json) {
|
|
1172
|
+
const store = openStore();
|
|
1173
|
+
try {
|
|
1174
|
+
const events = store.recentEvents(tail);
|
|
1175
|
+
if (json) {
|
|
1176
|
+
console.log(JSON.stringify({ events }));
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
for (const e of events)
|
|
1180
|
+
console.log(`${e.ts} ${e.kind}${e.pr ? ` #${e.pr}` : ""}`);
|
|
1181
|
+
}
|
|
1182
|
+
finally {
|
|
1183
|
+
store.close();
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
/** Start the companion the one way `up` and `rebuild` both do: the token and
|
|
1187
|
+
* config mirror refreshed before the container starts, so it never mounts a
|
|
1188
|
+
* stale mirror or an expired token — then the container, then pi. */
|
|
1189
|
+
async function startCompanion(lc, refreshInputs, log) {
|
|
1190
|
+
await refreshInputs();
|
|
1191
|
+
const outcome = await lc.ensureUp(log);
|
|
1192
|
+
return { outcome, born: await lc.spawnPi() };
|
|
1193
|
+
}
|
|
1194
|
+
/** `qcr companion rebuild`: replace the container and image, then start the
|
|
1195
|
+
* new one exactly as `up` does. The pihome volume is untouched. To rebuild
|
|
1196
|
+
* the scan sandbox's image without touching the companion: `qcr scan
|
|
1197
|
+
* rebuild`. */
|
|
1198
|
+
export async function rebuildCompanion(lc, name, refreshInputs, log) {
|
|
1199
|
+
// Refused before the running container goes: a rebuild that cannot build
|
|
1200
|
+
// must leave the companion as it was.
|
|
1201
|
+
lc.checkBuildable();
|
|
1202
|
+
await lc.removeContainer();
|
|
1203
|
+
await lc.buildImage(log);
|
|
1204
|
+
const { outcome, born } = await startCompanion(lc, refreshInputs, log);
|
|
1205
|
+
log(`companion ${name}: image rebuilt, ${outcome}; pi session ${born ? "spawned" : "already running"}`);
|
|
1206
|
+
}
|
|
1207
|
+
/** `qcr companion up|down|status|rebuild|plan` — thin over CompanionLifecycle. */
|
|
1208
|
+
export async function cmdCompanion(sub) {
|
|
1209
|
+
if (sub === "plan") {
|
|
1210
|
+
// For the host wrapper: the docker run it makes for the containerised TUI's
|
|
1211
|
+
// companion. Needs no config and runs no docker.
|
|
1212
|
+
const { companionPlan, renderPlan } = await import("./tui/companionplan.js");
|
|
1213
|
+
process.stdout.write(renderPlan(companionPlan(process.env)));
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
const { CompanionLifecycle, companionStateWord, realDockerRunner } = await import("./tui/companion.js");
|
|
1217
|
+
const cfg = loadConfig().companion;
|
|
1218
|
+
const lc = new CompanionLifecycle(cfg, realDockerRunner);
|
|
1219
|
+
// What every start publishes before pi spawns: the read-only GitHub token
|
|
1220
|
+
// and the config mirror. Both are one-shot — nothing refreshes them after
|
|
1221
|
+
// this command exits (the TUI does, while it runs).
|
|
1222
|
+
const refreshInputs = async () => {
|
|
1223
|
+
const { CompanionGithubToken, tokenOutcomeLine } = await import("./tui/ghread.js");
|
|
1224
|
+
const { companionGithubDir } = await import("./install.js");
|
|
1225
|
+
const full = loadConfig();
|
|
1226
|
+
const gh = await new CompanionGithubToken({
|
|
1227
|
+
dir: companionGithubDir(),
|
|
1228
|
+
repo: full.github.repo,
|
|
1229
|
+
secretsDir: full.github.secrets_dir,
|
|
1230
|
+
}).refresh();
|
|
1231
|
+
console.log(`companion ${cfg.container_name}: ${tokenOutcomeLine(gh)}`);
|
|
1232
|
+
const mirror = lc.syncConfigMirror();
|
|
1233
|
+
console.log(`companion ${cfg.container_name}: config mirror ${typeof mirror === "object" ? `NOT refreshed — ${mirror.failed}` : mirror}`);
|
|
1234
|
+
};
|
|
1235
|
+
switch (sub) {
|
|
1236
|
+
case "up": {
|
|
1237
|
+
const { outcome, born } = await startCompanion(lc, refreshInputs);
|
|
1238
|
+
console.log(`companion ${cfg.container_name}: ${outcome}; pi session ${born ? "spawned" : "already running"}`);
|
|
1239
|
+
console.log(`attach: docker ${lc.attachArgv().join(" ")}`);
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
case "down":
|
|
1243
|
+
await lc.gracefulStop();
|
|
1244
|
+
console.log(`companion ${cfg.container_name}: stopped (volumes and conversation kept)`);
|
|
1245
|
+
return;
|
|
1246
|
+
case "status": {
|
|
1247
|
+
const st = await lc.status();
|
|
1248
|
+
const pi = st.running ? await lc.piRunning() : false;
|
|
1249
|
+
console.log(`companion ${cfg.container_name}: ${companionStateWord(st)}` +
|
|
1250
|
+
`${st.running ? ` · pi session ${pi ? "live" : "down"}` : ""}`);
|
|
1251
|
+
// Unknown is not an answer: a script reading the exit code must not
|
|
1252
|
+
// take "docker could not be asked" for "no container".
|
|
1253
|
+
if (st.unknown !== undefined)
|
|
1254
|
+
process.exitCode = 1;
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
case "rebuild": {
|
|
1258
|
+
// The image is otherwise built ONLY when it is missing, so an
|
|
1259
|
+
// entrypoint or Dockerfile change never reaches a machine that already
|
|
1260
|
+
// has one — it just keeps starting yesterday's image. This is the
|
|
1261
|
+
// explicit "apply what I changed" path. The pihome volume is untouched,
|
|
1262
|
+
// so OAuth and the conversation survive; only the container and image
|
|
1263
|
+
// are replaced.
|
|
1264
|
+
await rebuildCompanion(lc, cfg.container_name, refreshInputs, (line) => console.log(line));
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
default:
|
|
1268
|
+
throw new Error("usage: qcr companion <up|down|status|rebuild|plan>");
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* `qcr memory reembed [--user <name>[,<name>…]]` — the operator's
|
|
1273
|
+
* half of the embedder migration, run from the PC that holds the keys.
|
|
1274
|
+
*
|
|
1275
|
+
* The daemon re-embeds ITS OWN namespace (the reviewer's) by itself once the
|
|
1276
|
+
* server is ready; it holds no other key and never will (the companion
|
|
1277
|
+
* boundary), so the companion's namespace — and any other provisioned user's — is
|
|
1278
|
+
* rebuilt from here, over the memory forward, with the keys in the operator's
|
|
1279
|
+
* keys file. Default: every provisioned user EXCEPT the daemon's own. The
|
|
1280
|
+
* reindex is an idempotent upsert, so re-running it (or naming the reviewer
|
|
1281
|
+
* too) is safe, only slow. Nothing but the request leaves this machine: no
|
|
1282
|
+
* key ever reaches an argv or the output.
|
|
1283
|
+
*/
|
|
1284
|
+
export async function cmdMemory(rest) {
|
|
1285
|
+
const sub = rest[0];
|
|
1286
|
+
if (sub === "relayout")
|
|
1287
|
+
return cmdMemoryRelayout(rest.slice(1));
|
|
1288
|
+
if (sub !== "reembed")
|
|
1289
|
+
throw new Error(MEMORY_USAGE);
|
|
1290
|
+
const named = parseReembedArgs(rest.slice(1));
|
|
1291
|
+
const cfg = loadConfig();
|
|
1292
|
+
const own = cfg.openviking.user;
|
|
1293
|
+
const users = named ?? cfg.openviking.provision_users.filter((u) => u !== own);
|
|
1294
|
+
if (users.length === 0)
|
|
1295
|
+
throw new Error("usage: qcr memory reembed [--user <name>[,<name>…]] — no namespace to re-embed");
|
|
1296
|
+
const [{ loadReembedModule, reembedNamespaces }, { USER_KEY_ENV, keysFileReader, tuiMemoryBaseUrl }, { keysFilePath },] = await Promise.all([
|
|
1297
|
+
import("./openviking/reembed.js"),
|
|
1298
|
+
import("./tui/memory.js"),
|
|
1299
|
+
import("./tui/companion.js"),
|
|
1300
|
+
]);
|
|
1301
|
+
const keysFile = keysFilePath(process.env);
|
|
1302
|
+
const readKey = keysFileReader(keysFile);
|
|
1303
|
+
const base = tuiMemoryBaseUrl(process.env);
|
|
1304
|
+
console.log(`re-embedding ${users.join(", ")} through ${base} with the keys in ${keysFile} (this can take minutes per namespace)`);
|
|
1305
|
+
const { ok, results } = await reembedNamespaces({
|
|
1306
|
+
module: await loadReembedModule(sandboxRoot()),
|
|
1307
|
+
base,
|
|
1308
|
+
users,
|
|
1309
|
+
keyFor: (user) => namespaceKey(user, USER_KEY_ENV, readKey).key,
|
|
1310
|
+
log: (line) => console.log(line),
|
|
1311
|
+
});
|
|
1312
|
+
for (const r of results)
|
|
1313
|
+
console.log(` ${r.user}: ${r.status} — ${r.detail}`);
|
|
1314
|
+
if (ok) {
|
|
1315
|
+
// The journal lives with the store: in the daemon container, or in the
|
|
1316
|
+
// memory sidecar (whose app root is /opt/qcr) under `endpoint = "remote"`.
|
|
1317
|
+
const { tuiMemoryContainer } = await import("./tui/memforward.js");
|
|
1318
|
+
const memoryContainer = tuiMemoryContainer(cfg.tui);
|
|
1319
|
+
// The daemon container's app root follows its `qcr.layout` label.
|
|
1320
|
+
const { runnerFor } = await import("./tui/remote.js");
|
|
1321
|
+
await resolveLayout(cfg.tui, runnerFor(cfg.tui));
|
|
1322
|
+
const script = reembedScriptPath(memoryContainer === cfg.tui.container, remoteSandboxRoot(cfg.tui));
|
|
1323
|
+
console.log("done. The daemon's journal still lists these namespaces as awaiting you; receipt them in the container so the heartbeat stops naming them:\n" +
|
|
1324
|
+
users
|
|
1325
|
+
.map((u) => ` docker exec ${memoryContainer} node ${script} settle --home ${cfg.openviking.home} --user ${u}`)
|
|
1326
|
+
.join("\n"));
|
|
1327
|
+
}
|
|
1328
|
+
return ok ? 0 : 1;
|
|
1329
|
+
}
|
|
1330
|
+
/** Where the re-embed script lives in the container the journal is in: the
|
|
1331
|
+
* daemon container's own app root, or the memory sidecar's /opt/qcr. */
|
|
1332
|
+
export function reembedScriptPath(inDaemonContainer, daemonRoot) {
|
|
1333
|
+
return `${inDaemonContainer ? daemonRoot : "/opt/qcr"}/scripts/openviking-reembed.mjs`;
|
|
1334
|
+
}
|
|
1335
|
+
const MEMORY_USAGE = "usage: qcr memory reembed [--user <name>[,<name>…]] | qcr memory relayout [--plan|--apply] [--user <name>]";
|
|
1336
|
+
/** A plain memory namespace name — the shape `memory relayout` holds its
|
|
1337
|
+
* `--user` (and its default) to. `memory reembed` keeps its names lenient. */
|
|
1338
|
+
const NAMESPACE_RE = /^[a-z][a-z0-9_-]*$/;
|
|
1339
|
+
/**
|
|
1340
|
+
* The flags of `qcr memory reembed` (after the verb): the namespaces `--user`
|
|
1341
|
+
* names, or null when it is absent (the caller's default set). An unknown
|
|
1342
|
+
* token or a repeated or empty `--user` is refused rather than ignored — a
|
|
1343
|
+
* mistyped flag must not quietly re-embed the default namespaces instead.
|
|
1344
|
+
*/
|
|
1345
|
+
export function parseReembedArgs(rest) {
|
|
1346
|
+
let users = null;
|
|
1347
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
1348
|
+
if (rest[i] !== "--user" || users !== null)
|
|
1349
|
+
throw new Error(MEMORY_USAGE);
|
|
1350
|
+
users = String(rest[i + 1] ?? "")
|
|
1351
|
+
.split(/[,\s]+/)
|
|
1352
|
+
.filter(Boolean);
|
|
1353
|
+
i += 1;
|
|
1354
|
+
if (users.length === 0)
|
|
1355
|
+
throw new Error(MEMORY_USAGE);
|
|
1356
|
+
}
|
|
1357
|
+
return users;
|
|
1358
|
+
}
|
|
1359
|
+
/** The keys-file names that may hold `user`'s key — a known user's own
|
|
1360
|
+
* list, else `OPENVIKING_<USER>_KEY` — and the first one present. */
|
|
1361
|
+
function namespaceKey(user, known, readKey) {
|
|
1362
|
+
const names = Object.hasOwn(known, user)
|
|
1363
|
+
? known[user]
|
|
1364
|
+
: [`OPENVIKING_${user.toUpperCase()}_KEY`];
|
|
1365
|
+
return { names, key: names.map((n) => readKey(n)).find((v) => v) ?? null };
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* The flags of `qcr memory relayout`, or the usage error. `--plan` is the
|
|
1369
|
+
* default and writes nothing; only an explicit `--apply` writes, and the two
|
|
1370
|
+
* together are refused. `--user` must name a plain namespace; without it the
|
|
1371
|
+
* default (the daemon's `[openviking] user`) is asked for, lazily, so a bad
|
|
1372
|
+
* flag is reported before any config is read. An unknown token is refused
|
|
1373
|
+
* rather than ignored — a mistyped `--apply` must not quietly plan instead.
|
|
1374
|
+
*/
|
|
1375
|
+
export function parseRelayoutArgs(rest, defaultUser) {
|
|
1376
|
+
let apply = false;
|
|
1377
|
+
let plan = false;
|
|
1378
|
+
let user = null;
|
|
1379
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
1380
|
+
const arg = rest[i];
|
|
1381
|
+
if (arg === "--apply")
|
|
1382
|
+
apply = true;
|
|
1383
|
+
else if (arg === "--plan")
|
|
1384
|
+
plan = true;
|
|
1385
|
+
else if (arg === "--user" && user === null) {
|
|
1386
|
+
user = String(rest[i + 1] ?? "").trim();
|
|
1387
|
+
i += 1;
|
|
1388
|
+
}
|
|
1389
|
+
else
|
|
1390
|
+
throw new Error(MEMORY_USAGE);
|
|
1391
|
+
}
|
|
1392
|
+
if (apply && plan)
|
|
1393
|
+
throw new Error(MEMORY_USAGE);
|
|
1394
|
+
const name = user ?? defaultUser();
|
|
1395
|
+
if (!NAMESPACE_RE.test(name))
|
|
1396
|
+
throw new Error(MEMORY_USAGE);
|
|
1397
|
+
return { apply, user: name };
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* `qcr memory relayout [--plan|--apply] [--user <name>]` — sort
|
|
1401
|
+
* one memory namespace (default: the daemon's, `[openviking] user`) into the
|
|
1402
|
+
* memory layout (src/memory/layout.ts). `--plan` (the default) prints where every entry goes;
|
|
1403
|
+
* `--apply` carries the plan out and reads the store again to show what is
|
|
1404
|
+
* left — idempotent, so a second `--apply` plans nothing. Run from the PC
|
|
1405
|
+
* that holds the keys, over the memory forward, like `reembed`; the daemon
|
|
1406
|
+
* never runs it. Nothing is deleted: a move leaves a retired stub, a
|
|
1407
|
+
* retirement is a tag.
|
|
1408
|
+
*/
|
|
1409
|
+
export async function cmdMemoryRelayout(rest) {
|
|
1410
|
+
const { apply, user } = parseRelayoutArgs(rest, () => loadConfig().openviking.user);
|
|
1411
|
+
const [{ USER_KEY_ENV, keysFileReader, tuiMemoryBaseUrl }, { keysFilePath }, { OpenVikingClient }, { OpenVikingStore }, { runRelayout },] = await Promise.all([
|
|
1412
|
+
import("./tui/memory.js"),
|
|
1413
|
+
import("./tui/companion.js"),
|
|
1414
|
+
import("./openviking/client.js"),
|
|
1415
|
+
import("./openviking/store.js"),
|
|
1416
|
+
import("./openviking/relayout.js"),
|
|
1417
|
+
]);
|
|
1418
|
+
const keysFile = keysFilePath(process.env);
|
|
1419
|
+
const readKey = keysFileReader(keysFile);
|
|
1420
|
+
const { names, key } = namespaceKey(user, USER_KEY_ENV, readKey);
|
|
1421
|
+
if (!key) {
|
|
1422
|
+
console.log(`no key for the ${user} namespace in ${keysFile} (${names.join(" or ")})`);
|
|
1423
|
+
return 1;
|
|
1424
|
+
}
|
|
1425
|
+
const base = tuiMemoryBaseUrl(process.env);
|
|
1426
|
+
const client = new OpenVikingClient({ baseUrl: base, apiKey: key, user, timeoutMs: 30_000 });
|
|
1427
|
+
// Every read is a fresh, full one: the plan must describe the store as it
|
|
1428
|
+
// is now, not a cached sweep.
|
|
1429
|
+
const store = new OpenVikingStore({ client, cacheTtlMs: 0, fullRefreshMs: 0 });
|
|
1430
|
+
console.log(`${apply ? "applying" : "planning"} the relayout of ${user} memory through ${base}`);
|
|
1431
|
+
const out = await runRelayout(store, { apply, log: (line) => console.log(line) });
|
|
1432
|
+
return out.ok ? 0 : 1;
|
|
1433
|
+
}
|
|
1434
|
+
const SETUP_USAGE = "usage: qcr setup [validate [<profile>] | commit]";
|
|
1435
|
+
/**
|
|
1436
|
+
* `qcr setup` — set up the reviewed repo with the companion.
|
|
1437
|
+
* qcr setup ask the companion to run its setup skill (an
|
|
1438
|
+
* inject into its column; open `qcr tui`)
|
|
1439
|
+
* qcr setup validate [profile] the daemon's own check of a repo profile as
|
|
1440
|
+
* it is on disk — for a direct edit
|
|
1441
|
+
* qcr setup commit carry out the profile commits setup asked
|
|
1442
|
+
* for (the TUI does this on its own timer),
|
|
1443
|
+
* retrying any whose git step failed
|
|
1444
|
+
*/
|
|
1445
|
+
export async function cmdSetup(rest) {
|
|
1446
|
+
const sub = rest[0];
|
|
1447
|
+
if (sub === "validate") {
|
|
1448
|
+
const { checkProfile, describeCheck, readProfileTarget } = await import("./setup/profile.js");
|
|
1449
|
+
const home = { root: sandboxRoot(), env: process.env };
|
|
1450
|
+
let name = rest[1]?.trim() ?? "";
|
|
1451
|
+
if (name === "") {
|
|
1452
|
+
const t = readProfileTarget(home);
|
|
1453
|
+
if (t.state !== "ok") {
|
|
1454
|
+
console.log(`✖ ${t.error}`);
|
|
1455
|
+
return 1;
|
|
1456
|
+
}
|
|
1457
|
+
name = t.named;
|
|
1458
|
+
}
|
|
1459
|
+
if (name === "") {
|
|
1460
|
+
console.log("the live config names no repo profile — name one: qcr setup validate <profile>");
|
|
1461
|
+
return 1;
|
|
1462
|
+
}
|
|
1463
|
+
const check = checkProfile(home, name);
|
|
1464
|
+
console.log(describeCheck(name, check));
|
|
1465
|
+
return check.ok ? 0 : 1;
|
|
1466
|
+
}
|
|
1467
|
+
if (sub === "commit") {
|
|
1468
|
+
const { commitRequestedProfiles, commitLine } = await import("./tui/setupentry.js");
|
|
1469
|
+
// The operator's own run: a commit whose git step failed is tried again.
|
|
1470
|
+
const results = commitRequestedProfiles(process.env, { retryFailed: true });
|
|
1471
|
+
if (results.length === 0)
|
|
1472
|
+
console.log("no profile commits waiting");
|
|
1473
|
+
for (const r of results)
|
|
1474
|
+
console.log(commitLine(r));
|
|
1475
|
+
return results.some((r) => r.state === "failed" || r.state === "refused") ? 1 : 0;
|
|
1476
|
+
}
|
|
1477
|
+
if (sub !== undefined)
|
|
1478
|
+
throw new Error(SETUP_USAGE);
|
|
1479
|
+
const { requestSetup, commitRequestedProfiles, commitLine } = await import("./tui/setupentry.js");
|
|
1480
|
+
const { companionInjectDir } = await import("./tui/companion.js");
|
|
1481
|
+
const cfg = loadConfig();
|
|
1482
|
+
for (const r of commitRequestedProfiles())
|
|
1483
|
+
console.log(commitLine(r));
|
|
1484
|
+
const file = requestSetup({ injectDir: companionInjectDir(cfg.companion) });
|
|
1485
|
+
if (!cfg.companion.enabled) {
|
|
1486
|
+
console.log(`the request is queued (${file}), but [companion] enabled = false — the companion never starts, so nothing ` +
|
|
1487
|
+
"picks it up until you turn it on. Setup runs in the companion.");
|
|
1488
|
+
return 1;
|
|
1489
|
+
}
|
|
1490
|
+
console.log(`asked the companion to set up ${cfg.github.repo} (${file}).\n` +
|
|
1491
|
+
"Open `qcr tui` — the companion column picks it up (at its next start, if it is not running) and walks you through " +
|
|
1492
|
+
"the survey and the interview. Nothing reaches PR reviews until you deploy the profile it writes.");
|
|
1493
|
+
return 0;
|
|
1494
|
+
}
|
|
1495
|
+
/** `qcr onboard` — the guided setup wizard: pi at the pinned
|
|
1496
|
+
* version via npx (works from a bare clone; pi stays a non-dependency), the
|
|
1497
|
+
* onboarding prompt + extension + a restricted tool set, then the TUI handoff
|
|
1498
|
+
* when the checklist verifies complete. Resumable: run it again after a crash
|
|
1499
|
+
* and completed steps are re-verified, not repeated. */
|
|
1500
|
+
export async function cmdOnboard() {
|
|
1501
|
+
const { piPinFromPackageJson, onboardingSessionDir, runOnboard } = await import("./onboard/launch.js");
|
|
1502
|
+
const { checklistComplete } = await import("./onboard/engine.js");
|
|
1503
|
+
const { spawn } = await import("node:child_process");
|
|
1504
|
+
const root = sandboxRoot();
|
|
1505
|
+
// Pin source: config.toml [pi] when it loads; package.json's "qcrPi" block on a
|
|
1506
|
+
// bare clone (config.toml is written later, BY onboarding). Same pinned
|
|
1507
|
+
// version by the rule-7 mirror discipline.
|
|
1508
|
+
let pin = null;
|
|
1509
|
+
try {
|
|
1510
|
+
const { package: pkg, version } = loadConfig().pi;
|
|
1511
|
+
pin = { package: pkg, version };
|
|
1512
|
+
}
|
|
1513
|
+
catch {
|
|
1514
|
+
pin = piPinFromPackageJson(root);
|
|
1515
|
+
}
|
|
1516
|
+
if (!pin)
|
|
1517
|
+
throw new Error('onboard: pi pin not found (need config.toml [pi] or the package.json "qcrPi" block)');
|
|
1518
|
+
mkdirSync(onboardingSessionDir(root), { recursive: true });
|
|
1519
|
+
const run = (cmd, args) => new Promise((resolvePromise) => {
|
|
1520
|
+
const child = spawn(cmd, args, {
|
|
1521
|
+
stdio: "inherit",
|
|
1522
|
+
cwd: root,
|
|
1523
|
+
env: { ...process.env, QCR_ROOT: root },
|
|
1524
|
+
});
|
|
1525
|
+
child.on("error", (err) => {
|
|
1526
|
+
console.error(`onboard: ${cmd} failed to start: ${err.message}`);
|
|
1527
|
+
resolvePromise(1);
|
|
1528
|
+
});
|
|
1529
|
+
child.on("close", (code) => resolvePromise(code ?? 1));
|
|
1530
|
+
});
|
|
1531
|
+
return runOnboard({
|
|
1532
|
+
pin,
|
|
1533
|
+
root,
|
|
1534
|
+
spawn: run,
|
|
1535
|
+
isComplete: () => checklistComplete(join(root, "state")),
|
|
1536
|
+
log: (line) => console.log(line),
|
|
1537
|
+
cliPath: join(root, "dist", "src", "cli.js"),
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
export const ARCHIVE_USAGE = "usage: qcr archive status [--plan] [--json] | qcr archive run | qcr archive restore <pr> | " +
|
|
1541
|
+
"qcr archive extract <pr> --to <dir>";
|
|
1542
|
+
export function parseArchiveArgs(rest) {
|
|
1543
|
+
const prOf = (v) => {
|
|
1544
|
+
const n = Number(v);
|
|
1545
|
+
if (v === undefined || !/^[1-9]\d*$/.test(v) || !Number.isSafeInteger(n))
|
|
1546
|
+
throw new Error(ARCHIVE_USAGE);
|
|
1547
|
+
return n;
|
|
1548
|
+
};
|
|
1549
|
+
switch (rest[0]) {
|
|
1550
|
+
case "status": {
|
|
1551
|
+
let plan = false;
|
|
1552
|
+
for (const a of rest.slice(1)) {
|
|
1553
|
+
if (a === "--plan")
|
|
1554
|
+
plan = true;
|
|
1555
|
+
else
|
|
1556
|
+
throw new Error(ARCHIVE_USAGE);
|
|
1557
|
+
}
|
|
1558
|
+
return { verb: "status", plan };
|
|
1559
|
+
}
|
|
1560
|
+
case "run":
|
|
1561
|
+
if (rest.length !== 1)
|
|
1562
|
+
throw new Error(ARCHIVE_USAGE);
|
|
1563
|
+
return { verb: "run" };
|
|
1564
|
+
case "restore":
|
|
1565
|
+
if (rest.length !== 2)
|
|
1566
|
+
throw new Error(ARCHIVE_USAGE);
|
|
1567
|
+
return { verb: "restore", pr: prOf(rest[1]) };
|
|
1568
|
+
case "extract": {
|
|
1569
|
+
const pr = prOf(rest[1]);
|
|
1570
|
+
if (rest.length !== 4 || rest[2] !== "--to" || !rest[3] || rest[3].startsWith("-"))
|
|
1571
|
+
throw new Error(ARCHIVE_USAGE);
|
|
1572
|
+
return { verb: "extract", pr, to: rest[3] };
|
|
1573
|
+
}
|
|
1574
|
+
default:
|
|
1575
|
+
throw new Error(ARCHIVE_USAGE);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* `qcr archive …`:
|
|
1580
|
+
* - `status [--plan] [--json]` — read-only, in this process. The daemon's
|
|
1581
|
+
* status.json `archive` block, the archive ledger (`archive_prs`) — and
|
|
1582
|
+
* with `--plan`, a plan computed HERE, now (worker leases asked in this container;
|
|
1583
|
+
* the daemon's in-flight births are not visible from a CLI, and the daemon
|
|
1584
|
+
* re-checks them before anything moves). Moves nothing.
|
|
1585
|
+
* - `run`, `restore <pr>` — the operator's two verbs: ONE command file
|
|
1586
|
+
* written as root into the sealed `commands/operator/` (the kill switch's
|
|
1587
|
+
* channel), confirmed by the daemon's own result file. The daemon runs
|
|
1588
|
+
* them under the archiver's strict mutex.
|
|
1589
|
+
* - `extract <pr> --to <dir>` — read-only over the archive: the PR's
|
|
1590
|
+
* archive decompressed into a scratch dir, every byte sha-verified, never
|
|
1591
|
+
* into a live tree. Runs where the archive is (the container).
|
|
1592
|
+
*/
|
|
1593
|
+
async function cmdArchive(rest, json) {
|
|
1594
|
+
const args = parseArchiveArgs(rest);
|
|
1595
|
+
const cfg = loadConfig();
|
|
1596
|
+
if (args.verb === "run" || args.verb === "restore") {
|
|
1597
|
+
const { runnerFor } = await import("./tui/remote.js");
|
|
1598
|
+
const { ArchiveOpSender, renderArchiveOp } = await import("./tui/archiveops.js");
|
|
1599
|
+
const cmd = args.verb === "run"
|
|
1600
|
+
? { command: "archive-run" }
|
|
1601
|
+
: { command: "archive-restore", pr: args.pr };
|
|
1602
|
+
if (!json) {
|
|
1603
|
+
await resolveLayout(cfg.tui, runnerFor(cfg.tui));
|
|
1604
|
+
console.log(`${cmd.command}${"pr" in cmd ? ` ${cmd.pr}` : ""} → ${cfg.tui.ssh_host}:${remoteSandboxRoot(cfg.tui)}/commands/operator/`);
|
|
1605
|
+
}
|
|
1606
|
+
const res = await new ArchiveOpSender(cfg.tui, runnerFor(cfg.tui)).send(cmd);
|
|
1607
|
+
if (json)
|
|
1608
|
+
console.log(JSON.stringify(res));
|
|
1609
|
+
else
|
|
1610
|
+
for (const line of renderArchiveOp(cmd, res))
|
|
1611
|
+
console.log(line);
|
|
1612
|
+
return res.ok ? 0 : 1;
|
|
1613
|
+
}
|
|
1614
|
+
const root = sandboxRoot();
|
|
1615
|
+
const { archivePolicy } = await import("./archive/archiver.js");
|
|
1616
|
+
const policy = archivePolicy(cfg.archive);
|
|
1617
|
+
if (args.verb === "extract") {
|
|
1618
|
+
const { extractPr } = await import("./archive/extract.js");
|
|
1619
|
+
const res = await extractPr({
|
|
1620
|
+
root,
|
|
1621
|
+
sessionsRoot: join(root, cfg.pi.session_dir),
|
|
1622
|
+
pr: args.pr,
|
|
1623
|
+
to: args.to,
|
|
1624
|
+
bytesPerS: policy.maxBytesPerS,
|
|
1625
|
+
});
|
|
1626
|
+
if (json)
|
|
1627
|
+
console.log(JSON.stringify(res));
|
|
1628
|
+
else if (!res.ok)
|
|
1629
|
+
console.log(`✖ refused: ${res.reason}`);
|
|
1630
|
+
else {
|
|
1631
|
+
const written = res.files.filter((f) => f.outcome === "written").length;
|
|
1632
|
+
console.log(`✓ pr-${args.pr} extracted to ${res.dir}: ${res.files.length} file(s) verified by sha256 ` +
|
|
1633
|
+
`(${written} written, ${res.files.length - written} already there)` +
|
|
1634
|
+
(res.copied.length > 0 ? `; copied as they are: ${res.copied.join(", ")}` : ""));
|
|
1635
|
+
for (const e of res.errors)
|
|
1636
|
+
console.log(`✖ ${e.path}: ${e.error}`);
|
|
1637
|
+
}
|
|
1638
|
+
return res.ok && res.errors.length === 0 ? 0 : 1;
|
|
1639
|
+
}
|
|
1640
|
+
const statusPath = join(stateDir(), "status.json");
|
|
1641
|
+
const { archiveLedgerSummary, archiveStatusJson, readArchiveBlockFile, renderArchiveReport } = await import("./archive/report.js");
|
|
1642
|
+
const { block, statusNote, archiveRead } = readArchiveBlockFile(statusPath);
|
|
1643
|
+
const store = openStore();
|
|
1644
|
+
try {
|
|
1645
|
+
const ledger = archiveLedgerSummary(store);
|
|
1646
|
+
let plan = null;
|
|
1647
|
+
if (args.plan) {
|
|
1648
|
+
const { computePlan } = await import("./archive/plan.js");
|
|
1649
|
+
const { createWorkerLiveness } = await import("./workerliveness.js");
|
|
1650
|
+
const { runDirOf } = await import("./ipc/capability.js");
|
|
1651
|
+
const workers = createWorkerLiveness({ runDir: runDirOf(join(root, "state")) });
|
|
1652
|
+
const { piGlobalSessionsDir } = await import("./archive/piglobal.js");
|
|
1653
|
+
plan = await computePlan({
|
|
1654
|
+
store,
|
|
1655
|
+
root,
|
|
1656
|
+
sessionsRoot: join(root, cfg.pi.session_dir),
|
|
1657
|
+
now: () => new Date(),
|
|
1658
|
+
afterDays: cfg.archive.after_days,
|
|
1659
|
+
eventsAfterDays: cfg.archive.events_after_days,
|
|
1660
|
+
liveSessions: async () => workers.liveSessions(),
|
|
1661
|
+
// This process's home: run in the container, the pihome volume.
|
|
1662
|
+
piGlobalDir: piGlobalSessionsDir(),
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
if (json)
|
|
1666
|
+
console.log(JSON.stringify(archiveStatusJson({ block, archiveRead, statusNote, ledger, policy, plan })));
|
|
1667
|
+
else
|
|
1668
|
+
console.log(renderArchiveReport({ block, statusNote, ledger, policy, plan }));
|
|
1669
|
+
return 0;
|
|
1670
|
+
}
|
|
1671
|
+
finally {
|
|
1672
|
+
store.close();
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
/** Takes the global `--json` flag out of `argv`. `qcr message` carries free
|
|
1676
|
+
* text, so a `--json` among its words stays words and the flag is not read. */
|
|
1677
|
+
export function splitJsonFlag(argv) {
|
|
1678
|
+
if (argv[0] === "message")
|
|
1679
|
+
return { json: false, argv };
|
|
1680
|
+
return { json: argv.includes("--json"), argv: argv.filter((a) => a !== "--json") };
|
|
1681
|
+
}
|
|
1682
|
+
export async function main(rawArgv) {
|
|
1683
|
+
const { json, argv } = splitJsonFlag(rawArgv);
|
|
1684
|
+
// A GitHub token handed over as a file (packages/pi-plugin/src/ghcred.ts) is read and
|
|
1685
|
+
// unlinked here, before any verb can start a pi or bash child. A file that
|
|
1686
|
+
// is named but cannot be taken is a one-line failure, never a stack trace.
|
|
1687
|
+
try {
|
|
1688
|
+
takeHandoffToken();
|
|
1689
|
+
}
|
|
1690
|
+
catch (err) {
|
|
1691
|
+
const error = `the GitHub token handoff failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
1692
|
+
if (json)
|
|
1693
|
+
console.log(JSON.stringify({ error }));
|
|
1694
|
+
else
|
|
1695
|
+
console.error(`qcr: ${error}`);
|
|
1696
|
+
process.exitCode = 1;
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
const [cmd, ...rest] = argv;
|
|
1700
|
+
switch (cmd) {
|
|
1701
|
+
case "checklist": {
|
|
1702
|
+
const { cmdChecklist } = await import("./cliops.js");
|
|
1703
|
+
process.exitCode = await cmdChecklist();
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
case "compare": {
|
|
1707
|
+
const prs = rest.map(Number);
|
|
1708
|
+
if (prs.length === 0 || prs.some((n) => !Number.isInteger(n)))
|
|
1709
|
+
throw new Error("usage: qcr compare <pr-number...>");
|
|
1710
|
+
const { cmdCompare } = await import("./cliops.js");
|
|
1711
|
+
process.exitCode = await cmdCompare(prs);
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
case "metrics": {
|
|
1715
|
+
const prIdx = rest.indexOf("--pr");
|
|
1716
|
+
const pr = prIdx >= 0 ? Number(rest[prIdx + 1]) : null;
|
|
1717
|
+
if (pr !== null && !Number.isInteger(pr))
|
|
1718
|
+
throw new Error("usage: qcr metrics [--pr <pr-number>] [--json]");
|
|
1719
|
+
return cmdMetrics(pr, json);
|
|
1720
|
+
}
|
|
1721
|
+
case "backfill": {
|
|
1722
|
+
const { dryRun, limit } = parseBackfillArgs(rest);
|
|
1723
|
+
return cmdBackfill(dryRun, limit, json);
|
|
1724
|
+
}
|
|
1725
|
+
case "history": {
|
|
1726
|
+
process.exitCode = await cmdHistory(rest);
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
case "outcomes": {
|
|
1730
|
+
process.exitCode = await cmdOutcomes(rest, json);
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1733
|
+
case "lessons": {
|
|
1734
|
+
process.exitCode = await cmdLessons(rest, json);
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
case "regression": {
|
|
1738
|
+
process.exitCode = await cmdRegression(rest, json);
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
case "status":
|
|
1742
|
+
return cmdStatus();
|
|
1743
|
+
case "spend":
|
|
1744
|
+
return cmdSpend();
|
|
1745
|
+
case "today": {
|
|
1746
|
+
if (json) {
|
|
1747
|
+
const store = openStore();
|
|
1748
|
+
try {
|
|
1749
|
+
console.log(JSON.stringify(todayJson(store, new Date().toISOString().slice(0, 10))));
|
|
1750
|
+
}
|
|
1751
|
+
finally {
|
|
1752
|
+
store.close();
|
|
1753
|
+
}
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
return cmdToday();
|
|
1757
|
+
}
|
|
1758
|
+
case "why": {
|
|
1759
|
+
const n = Number(rest[0]);
|
|
1760
|
+
if (!Number.isInteger(n))
|
|
1761
|
+
throw new Error("usage: qcr why <pr-number> [--json]");
|
|
1762
|
+
if (json) {
|
|
1763
|
+
const store = openStore();
|
|
1764
|
+
try {
|
|
1765
|
+
const payload = whyJson(store, n, join(sandboxRoot(), "archive", `pr-${n}`));
|
|
1766
|
+
console.log(JSON.stringify(await enrichWhy(payload)));
|
|
1767
|
+
}
|
|
1768
|
+
finally {
|
|
1769
|
+
store.close();
|
|
1770
|
+
}
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
return cmdWhy(n);
|
|
1774
|
+
}
|
|
1775
|
+
case "events": {
|
|
1776
|
+
const tailIdx = rest.indexOf("--tail");
|
|
1777
|
+
const tail = tailIdx >= 0 ? Number(rest[tailIdx + 1]) : 40;
|
|
1778
|
+
if (!Number.isInteger(tail) || tail <= 0)
|
|
1779
|
+
throw new Error("usage: qcr events [--tail <k>] [--json]");
|
|
1780
|
+
return cmdEvents(tail, json);
|
|
1781
|
+
}
|
|
1782
|
+
case "tui": {
|
|
1783
|
+
const { runTui } = await import("./tui/index.js");
|
|
1784
|
+
return runTui(rest);
|
|
1785
|
+
}
|
|
1786
|
+
case "companion":
|
|
1787
|
+
if (rest.length !== 1)
|
|
1788
|
+
throw new Error("usage: qcr companion <up|down|status|rebuild|plan>");
|
|
1789
|
+
return cmdCompanion(rest[0]);
|
|
1790
|
+
case "memory": {
|
|
1791
|
+
process.exitCode = await cmdMemory(rest);
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
case "onboard": {
|
|
1795
|
+
process.exitCode = await cmdOnboard();
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
case "setup": {
|
|
1799
|
+
process.exitCode = await cmdSetup(rest);
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
case "drain": {
|
|
1803
|
+
process.exitCode = await cmdDrain(rest);
|
|
1804
|
+
return;
|
|
1805
|
+
}
|
|
1806
|
+
case "pause-pr":
|
|
1807
|
+
case "resume-pr": {
|
|
1808
|
+
process.exitCode = await cmdPrLever(cmd, rest);
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
case "message": {
|
|
1812
|
+
process.exitCode = await cmdMessage(rest);
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
case "archive": {
|
|
1816
|
+
process.exitCode = await cmdArchive(rest, json);
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
case "live": {
|
|
1820
|
+
// A review's LiveEvent log as a transcript (src/livecli.ts). Usage
|
|
1821
|
+
// errors are its own exit code 2.
|
|
1822
|
+
const { runLive } = await import("./livecli.js");
|
|
1823
|
+
const { liveContainersFromEnv } = await import("./reviewdetach.js");
|
|
1824
|
+
const { stateHome } = await import("./install.js");
|
|
1825
|
+
process.exitCode = await runLive(rest, json, {
|
|
1826
|
+
home: join(stateHome(), "review", "detached"),
|
|
1827
|
+
liveContainers: () => liveContainersFromEnv(process.env),
|
|
1828
|
+
out: (l) => console.log(l),
|
|
1829
|
+
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
1830
|
+
alive: (pid) => {
|
|
1831
|
+
try {
|
|
1832
|
+
process.kill(pid, 0);
|
|
1833
|
+
return true;
|
|
1834
|
+
}
|
|
1835
|
+
catch {
|
|
1836
|
+
return false;
|
|
1837
|
+
}
|
|
1838
|
+
},
|
|
1839
|
+
});
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
case "scan": {
|
|
1843
|
+
// Scan stage 1 and 2, the companion's scan tools as a scriptable verb
|
|
1844
|
+
// (src/scancli.ts). Usage errors are its own exit code 2.
|
|
1845
|
+
const { cmdScan } = await import("./scancli.js");
|
|
1846
|
+
process.exitCode = await cmdScan(rest, json);
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
case "review": {
|
|
1850
|
+
// A daemonless review in the terminal (src/reviewcli.ts). Usage errors
|
|
1851
|
+
// are its own exit code 2.
|
|
1852
|
+
const { cmdReview } = await import("./reviewcli.js");
|
|
1853
|
+
const { openReviewMemoryPortAt } = await import("./reviewmemory.js");
|
|
1854
|
+
process.exitCode = await cmdReview(rest, json, { memory: openReviewMemoryPortAt });
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
case "open": {
|
|
1858
|
+
// A finished review session in pi's own UI (src/opencli.ts). Usage
|
|
1859
|
+
// errors are its own exit code 2.
|
|
1860
|
+
const { cmdOpen } = await import("./opencli.js");
|
|
1861
|
+
process.exitCode = await cmdOpen(rest, json);
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
case "version": {
|
|
1865
|
+
// The install's identity for a plugin: image version, JSON schema
|
|
1866
|
+
// versions, pi pin (src/version.ts). Needs no config and no state.
|
|
1867
|
+
const { cmdVersion } = await import("./version.js");
|
|
1868
|
+
return cmdVersion(json);
|
|
1869
|
+
}
|
|
1870
|
+
case "install": {
|
|
1871
|
+
process.exitCode = cmdInstall(rest);
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
case "config": {
|
|
1875
|
+
// Load a config home the way the daemon boots, print every error
|
|
1876
|
+
// (src/configcheck.ts); the VM updater runs it on the staged home.
|
|
1877
|
+
const { cmdConfig } = await import("./configcheck.js");
|
|
1878
|
+
process.exitCode = cmdConfig(rest, json, { defaultHome: () => locateConfig().home });
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
case "takeover":
|
|
1882
|
+
case "debug-round":
|
|
1883
|
+
case "release": {
|
|
1884
|
+
process.exitCode = await cmdTakeover(cmd, rest);
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
default:
|
|
1888
|
+
console.log("usage: qcr <version|config check [--config-home <dir>]|onboard|setup [validate [<profile>]|commit]|status|spend|today|why <n>|metrics [--pr <n>]|events|takeover <n>|debug-round <n>|release <n>|tui [pr|overseer]|drain <on [--hold]|off>|pause-pr <pr> [--reason <text>]|resume-pr <pr>|message <pr> [--may-post] <text...>|archive <status [--plan]|run|restore <pr>|extract <pr> --to <dir>>|install <where|migrate>|" +
|
|
1889
|
+
"companion <up|down|status|rebuild>|review [start|status|stop|branch|--uncommitted|--diff FILE|-|<PR number|URL>] [--repo OWNER/NAME] [--base REF] [--preview] [--key K] [--limit USD] [--json]|live [RUN_ID|DIR] [--lens ID] [--round N] [--follow] [--json]|open [RUN_ID|DIR|FILE.jsonl|scan[:RUN]|pr N [--round R]] [--lens ID] [--write] [--print]|scan <preview|start|status|stop|findings|matches|match|suggestions|stage2 propose|stage2 start|dismiss|units show|units propose|units adjust|units save|login|doctor|rebuild>|memory reembed [--user <name>]|memory relayout [--plan|--apply]|backfill [--dry-run] [--limit <n>]|history <digest|pull>|outcomes [--days <n>] [--capture [--read-github]]|lessons [mine|shadow-report [--days <n>]|off|on|suppress <id>|unsuppress <id>]|checklist|compare <pr...>|" +
|
|
1890
|
+
"regression [--set <path>] [--clone <path>] [--change <name>] [--limit <n>] [--out <path>]>");
|
|
1891
|
+
// A bare `qcr` (or an explicit help) is a usage request; anything else
|
|
1892
|
+
// here is an unknown command, which must not look like success.
|
|
1893
|
+
if (cmd !== undefined && !["help", "--help", "-h"].includes(cmd))
|
|
1894
|
+
process.exitCode = 1;
|
|
1895
|
+
loadConfig(); // surface config errors early even on bad usage
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
/** Whether `entry` (the process's argv[1]) is this very module, links
|
|
1899
|
+
* resolved: `node dist/src/cli.js`, the image's `qcr`. The npm bin
|
|
1900
|
+
* (bin/qcr.js, reached through a package manager's link whose name is never
|
|
1901
|
+
* cli.js) imports this module and calls `main` itself, and another script
|
|
1902
|
+
* that merely shares the name is not this one. */
|
|
1903
|
+
export function isEntryModule(entry, self = fileURLToPath(import.meta.url)) {
|
|
1904
|
+
if (!entry)
|
|
1905
|
+
return false;
|
|
1906
|
+
try {
|
|
1907
|
+
return realpathSync(entry) === realpathSync(self);
|
|
1908
|
+
}
|
|
1909
|
+
catch {
|
|
1910
|
+
return false;
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
if (isEntryModule(process.argv[1])) {
|
|
1914
|
+
void main(process.argv.slice(2));
|
|
1915
|
+
}
|
|
1916
|
+
//# sourceMappingURL=cli.js.map
|