@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.
Files changed (1443) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +385 -2
  3. package/THIRD-PARTY-NOTICES.md +134 -0
  4. package/agents/pass-abstraction.md +64 -0
  5. package/agents/pass-ask-human.md +134 -0
  6. package/agents/pass-backcompat.md +67 -0
  7. package/agents/pass-baseline.md +61 -0
  8. package/agents/pass-behaviour-delta.md +64 -0
  9. package/agents/pass-bounds.md +36 -0
  10. package/agents/pass-claims.md +31 -0
  11. package/agents/pass-concurrency.md +168 -0
  12. package/agents/pass-consistency.md +57 -0
  13. package/agents/pass-contracts.md +30 -0
  14. package/agents/pass-data-safety.md +54 -0
  15. package/agents/pass-deferred.md +35 -0
  16. package/agents/pass-delivery.md +37 -0
  17. package/agents/pass-duplication.md +60 -0
  18. package/agents/pass-intent.md +63 -0
  19. package/agents/pass-lint-conformance.md +46 -0
  20. package/agents/pass-maintainability.md +67 -0
  21. package/agents/pass-placement.md +68 -0
  22. package/agents/pass-prod-risk.md +24 -0
  23. package/agents/pass-rollout.md +32 -0
  24. package/agents/pass-roundtrip.md +46 -0
  25. package/agents/pass-security.md +31 -0
  26. package/agents/pass-simplicity.md +68 -0
  27. package/agents/pass-statelessness.md +27 -0
  28. package/agents/pass-style.md +37 -0
  29. package/agents/pass-sweep.md +57 -0
  30. package/agents/pass-tests.md +58 -0
  31. package/agents/pass-ui.md +23 -0
  32. package/agents/pass-validate.md +50 -0
  33. package/agents/pass-widened.md +46 -0
  34. package/agents/scan/pass-scan.md +86 -0
  35. package/bin/qcr.js +11 -0
  36. package/config.example.toml +1067 -0
  37. package/deploy/ansible/README.md +356 -0
  38. package/deploy/ansible/group_vars/all.yml +212 -0
  39. package/deploy/ansible/inventory.example.ini +25 -0
  40. package/deploy/ansible/playbook.yml +360 -0
  41. package/deploy/collect-secrets.sh +289 -0
  42. package/deploy/collect-slack-secrets.mjs +1190 -0
  43. package/deploy/github-app-manifest.json +31 -0
  44. package/deploy/manifest.schema.json +75 -0
  45. package/deploy/qcr-update.sh +803 -0
  46. package/deploy/reviewer/AGENTS.override.md +12 -0
  47. package/dist/extensions/cli/bash-guard-ambient.js +42 -0
  48. package/dist/extensions/cli/bash-guard-ambient.js.map +1 -0
  49. package/dist/extensions/cli/bash-guard.js +5 -0
  50. package/dist/extensions/cli/bash-guard.js.map +1 -0
  51. package/dist/extensions/cli/companion-guard.js +5 -0
  52. package/dist/extensions/cli/companion-guard.js.map +1 -0
  53. package/dist/extensions/cli/debug-round.js +8 -0
  54. package/dist/extensions/cli/debug-round.js.map +1 -0
  55. package/dist/extensions/cli/inject-bridge.js +5 -0
  56. package/dist/extensions/cli/inject-bridge.js.map +1 -0
  57. package/dist/extensions/cli/ledger-pin.js +5 -0
  58. package/dist/extensions/cli/ledger-pin.js.map +1 -0
  59. package/dist/extensions/cli/lens.js +13 -0
  60. package/dist/extensions/cli/lens.js.map +1 -0
  61. package/dist/extensions/cli/muted-theme.js +5 -0
  62. package/dist/extensions/cli/muted-theme.js.map +1 -0
  63. package/dist/extensions/cli/onboarding.js +5 -0
  64. package/dist/extensions/cli/onboarding.js.map +1 -0
  65. package/dist/extensions/cli/setup.js +5 -0
  66. package/dist/extensions/cli/setup.js.map +1 -0
  67. package/dist/extensions/cli/tui-view.js +5 -0
  68. package/dist/extensions/cli/tui-view.js.map +1 -0
  69. package/dist/extensions/companion-guard.js +361 -0
  70. package/dist/extensions/companion-guard.js.map +1 -0
  71. package/dist/extensions/debug-round.js +204 -0
  72. package/dist/extensions/debug-round.js.map +1 -0
  73. package/dist/extensions/inject-bridge.js +603 -0
  74. package/dist/extensions/inject-bridge.js.map +1 -0
  75. package/dist/extensions/muted-theme.js +12 -0
  76. package/dist/extensions/muted-theme.js.map +1 -0
  77. package/dist/extensions/onboarding.js +175 -0
  78. package/dist/extensions/onboarding.js.map +1 -0
  79. package/dist/extensions/setup-replay.js +269 -0
  80. package/dist/extensions/setup-replay.js.map +1 -0
  81. package/dist/extensions/setup-scan.js +998 -0
  82. package/dist/extensions/setup-scan.js.map +1 -0
  83. package/dist/extensions/setup-triage.js +175 -0
  84. package/dist/extensions/setup-triage.js.map +1 -0
  85. package/dist/extensions/setup.js +1160 -0
  86. package/dist/extensions/setup.js.map +1 -0
  87. package/dist/extensions/tui-view.js +389 -0
  88. package/dist/extensions/tui-view.js.map +1 -0
  89. package/dist/extensions/vendor/openviking-pi/client.js +376 -0
  90. package/dist/extensions/vendor/openviking-pi/client.js.map +1 -0
  91. package/dist/extensions/vendor/openviking-pi/config.js +189 -0
  92. package/dist/extensions/vendor/openviking-pi/config.js.map +1 -0
  93. package/dist/extensions/vendor/openviking-pi/index.js +421 -0
  94. package/dist/extensions/vendor/openviking-pi/index.js.map +1 -0
  95. package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +86 -0
  96. package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs.map +1 -0
  97. package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs +408 -0
  98. package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs.map +1 -0
  99. package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +41 -0
  100. package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs.map +1 -0
  101. package/dist/extensions/vendor/openviking-pi/qcr-hint.js +290 -0
  102. package/dist/extensions/vendor/openviking-pi/qcr-hint.js.map +1 -0
  103. package/dist/extensions/vendor/openviking-pi/qcr-honest.js +148 -0
  104. package/dist/extensions/vendor/openviking-pi/qcr-honest.js.map +1 -0
  105. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js +68 -0
  106. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js.map +1 -0
  107. package/dist/extensions/vendor/openviking-pi/qcr-leaf.js +77 -0
  108. package/dist/extensions/vendor/openviking-pi/qcr-leaf.js.map +1 -0
  109. package/dist/extensions/vendor/openviking-pi/qcr-lesson.js +216 -0
  110. package/dist/extensions/vendor/openviking-pi/qcr-lesson.js.map +1 -0
  111. package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js +154 -0
  112. package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js.map +1 -0
  113. package/dist/extensions/vendor/openviking-pi/qcr-recall.js +254 -0
  114. package/dist/extensions/vendor/openviking-pi/qcr-recall.js.map +1 -0
  115. package/dist/extensions/vendor/openviking-pi/qcr-shim.js +94 -0
  116. package/dist/extensions/vendor/openviking-pi/qcr-shim.js.map +1 -0
  117. package/dist/extensions/vendor/openviking-pi/qcr-write.js +333 -0
  118. package/dist/extensions/vendor/openviking-pi/qcr-write.js.map +1 -0
  119. package/dist/extensions/vendor/openviking-pi/recall.js +108 -0
  120. package/dist/extensions/vendor/openviking-pi/recall.js.map +1 -0
  121. package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs +529 -0
  122. package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs.map +1 -0
  123. package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs +205 -0
  124. package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs.map +1 -0
  125. package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs +442 -0
  126. package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs.map +1 -0
  127. package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs +286 -0
  128. package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs.map +1 -0
  129. package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +189 -0
  130. package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs.map +1 -0
  131. package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs +580 -0
  132. package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs.map +1 -0
  133. package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs +11 -0
  134. package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs.map +1 -0
  135. package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs +63 -0
  136. package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs.map +1 -0
  137. package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs +58 -0
  138. package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs.map +1 -0
  139. package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
  140. package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs.map +1 -0
  141. package/dist/extensions/vendor/openviking-pi/sync.js +127 -0
  142. package/dist/extensions/vendor/openviking-pi/sync.js.map +1 -0
  143. package/dist/extensions/vendor/openviking-pi/takeover.js +25 -0
  144. package/dist/extensions/vendor/openviking-pi/takeover.js.map +1 -0
  145. package/dist/extensions/vendor/openviking-pi/tools.js +417 -0
  146. package/dist/extensions/vendor/openviking-pi/tools.js.map +1 -0
  147. package/dist/src/anomalies.js +483 -0
  148. package/dist/src/anomalies.js.map +1 -0
  149. package/dist/src/answerworker.js +180 -0
  150. package/dist/src/answerworker.js.map +1 -0
  151. package/dist/src/appstamp.js +68 -0
  152. package/dist/src/appstamp.js.map +1 -0
  153. package/dist/src/archive/admission.js +3 -0
  154. package/dist/src/archive/admission.js.map +1 -0
  155. package/dist/src/archive/archivedb.js +474 -0
  156. package/dist/src/archive/archivedb.js.map +1 -0
  157. package/dist/src/archive/archiver.js +1770 -0
  158. package/dist/src/archive/archiver.js.map +1 -0
  159. package/dist/src/archive/archivewrite.js +198 -0
  160. package/dist/src/archive/archivewrite.js.map +1 -0
  161. package/dist/src/archive/bundle.js +266 -0
  162. package/dist/src/archive/bundle.js.map +1 -0
  163. package/dist/src/archive/dbmove.js +638 -0
  164. package/dist/src/archive/dbmove.js.map +1 -0
  165. package/dist/src/archive/duplicatecounts.js +80 -0
  166. package/dist/src/archive/duplicatecounts.js.map +1 -0
  167. package/dist/src/archive/eligibility.js +113 -0
  168. package/dist/src/archive/eligibility.js.map +1 -0
  169. package/dist/src/archive/extract.js +288 -0
  170. package/dist/src/archive/extract.js.map +1 -0
  171. package/dist/src/archive/fsutil.js +330 -0
  172. package/dist/src/archive/fsutil.js.map +1 -0
  173. package/dist/src/archive/gzfile.js +277 -0
  174. package/dist/src/archive/gzfile.js.map +1 -0
  175. package/dist/src/archive/kinds.js +22 -0
  176. package/dist/src/archive/kinds.js.map +1 -0
  177. package/dist/src/archive/livecheckpoint.js +178 -0
  178. package/dist/src/archive/livecheckpoint.js.map +1 -0
  179. package/dist/src/archive/liveretention.js +145 -0
  180. package/dist/src/archive/liveretention.js.map +1 -0
  181. package/dist/src/archive/manifest.js +284 -0
  182. package/dist/src/archive/manifest.js.map +1 -0
  183. package/dist/src/archive/overseer.js +283 -0
  184. package/dist/src/archive/overseer.js.map +1 -0
  185. package/dist/src/archive/piglobal.js +129 -0
  186. package/dist/src/archive/piglobal.js.map +1 -0
  187. package/dist/src/archive/plan.js +398 -0
  188. package/dist/src/archive/plan.js.map +1 -0
  189. package/dist/src/archive/prarchive.js +831 -0
  190. package/dist/src/archive/prarchive.js.map +1 -0
  191. package/dist/src/archive/prfiles.js +303 -0
  192. package/dist/src/archive/prfiles.js.map +1 -0
  193. package/dist/src/archive/report.js +169 -0
  194. package/dist/src/archive/report.js.map +1 -0
  195. package/dist/src/archive/restore.js +276 -0
  196. package/dist/src/archive/restore.js.map +1 -0
  197. package/dist/src/archive/rows.js +106 -0
  198. package/dist/src/archive/rows.js.map +1 -0
  199. package/dist/src/archive/seal.js +480 -0
  200. package/dist/src/archive/seal.js.map +1 -0
  201. package/dist/src/archive/status.js +528 -0
  202. package/dist/src/archive/status.js.map +1 -0
  203. package/dist/src/archive/storepass.js +409 -0
  204. package/dist/src/archive/storepass.js.map +1 -0
  205. package/dist/src/backfill.js +217 -0
  206. package/dist/src/backfill.js.map +1 -0
  207. package/dist/src/bench/cost.js +87 -0
  208. package/dist/src/bench/exec.js +76 -0
  209. package/dist/src/bench/values.js +14 -0
  210. package/dist/src/botcontrol.js +127 -0
  211. package/dist/src/botcontrol.js.map +1 -0
  212. package/dist/src/botworker.js +805 -0
  213. package/dist/src/botworker.js.map +1 -0
  214. package/dist/src/capture.js +234 -0
  215. package/dist/src/capture.js.map +1 -0
  216. package/dist/src/cases.js +1491 -0
  217. package/dist/src/cases.js.map +1 -0
  218. package/dist/src/channelfs.js +27 -0
  219. package/dist/src/channelfs.js.map +1 -0
  220. package/dist/src/checklist.js +312 -0
  221. package/dist/src/checklist.js.map +1 -0
  222. package/dist/src/ciretry.js +91 -0
  223. package/dist/src/ciretry.js.map +1 -0
  224. package/dist/src/cli.js +1916 -0
  225. package/dist/src/cli.js.map +1 -0
  226. package/dist/src/cliops.js +333 -0
  227. package/dist/src/cliops.js.map +1 -0
  228. package/dist/src/clonedir.js +15 -0
  229. package/dist/src/clonedir.js.map +1 -0
  230. package/dist/src/commandeffects.js +43 -0
  231. package/dist/src/commandeffects.js.map +1 -0
  232. package/dist/src/commands.js +1312 -0
  233. package/dist/src/commands.js.map +1 -0
  234. package/dist/src/companioninbox.js +203 -0
  235. package/dist/src/companioninbox.js.map +1 -0
  236. package/dist/src/compare.js +673 -0
  237. package/dist/src/compare.js.map +1 -0
  238. package/dist/src/config.js +1027 -0
  239. package/dist/src/config.js.map +1 -0
  240. package/dist/src/configcheck.js +111 -0
  241. package/dist/src/configcheck.js.map +1 -0
  242. package/dist/src/coreext.js +43 -0
  243. package/dist/src/coreext.js.map +1 -0
  244. package/dist/src/daemon.js +929 -0
  245. package/dist/src/daemon.js.map +1 -0
  246. package/dist/src/debuground.js +731 -0
  247. package/dist/src/debuground.js.map +1 -0
  248. package/dist/src/debugroundpi.js +219 -0
  249. package/dist/src/debugroundpi.js.map +1 -0
  250. package/dist/src/diagnostics.js +14 -0
  251. package/dist/src/diagnostics.js.map +1 -0
  252. package/dist/src/digest.js +139 -0
  253. package/dist/src/digest.js.map +1 -0
  254. package/dist/src/dispatch.js +1726 -0
  255. package/dist/src/dispatch.js.map +1 -0
  256. package/dist/src/engine/answerguard.js +18 -0
  257. package/dist/src/engine/answerguard.js.map +1 -0
  258. package/dist/src/engine/botsdk.js +262 -0
  259. package/dist/src/engine/botsdk.js.map +1 -0
  260. package/dist/src/engine/caserunner-sdk.js +332 -0
  261. package/dist/src/engine/caserunner-sdk.js.map +1 -0
  262. package/dist/src/engine/debugpi.js +194 -0
  263. package/dist/src/engine/debugpi.js.map +1 -0
  264. package/dist/src/eod.js +473 -0
  265. package/dist/src/eod.js.map +1 -0
  266. package/dist/src/eodpinwatch.js +391 -0
  267. package/dist/src/eodpinwatch.js.map +1 -0
  268. package/dist/src/escalation.js +1009 -0
  269. package/dist/src/escalation.js.map +1 -0
  270. package/dist/src/eventloop.js +779 -0
  271. package/dist/src/eventloop.js.map +1 -0
  272. package/dist/src/execboundary.js +430 -0
  273. package/dist/src/execboundary.js.map +1 -0
  274. package/dist/src/feedback.js +915 -0
  275. package/dist/src/feedback.js.map +1 -0
  276. package/dist/src/findingscheck.js +200 -0
  277. package/dist/src/findingscheck.js.map +1 -0
  278. package/dist/src/fsdenied.js +26 -0
  279. package/dist/src/fsdenied.js.map +1 -0
  280. package/dist/src/ghread.js +181 -0
  281. package/dist/src/ghread.js.map +1 -0
  282. package/dist/src/github.js +1063 -0
  283. package/dist/src/github.js.map +1 -0
  284. package/dist/src/homes.js +103 -0
  285. package/dist/src/homes.js.map +1 -0
  286. package/dist/src/hostroute.js +24 -0
  287. package/dist/src/hostroute.js.map +1 -0
  288. package/dist/src/improvements.js +625 -0
  289. package/dist/src/improvements.js.map +1 -0
  290. package/dist/src/install.js +946 -0
  291. package/dist/src/install.js.map +1 -0
  292. package/dist/src/installkind.js +40 -0
  293. package/dist/src/installkind.js.map +1 -0
  294. package/dist/src/ipc/capability.js +286 -0
  295. package/dist/src/ipc/capability.js.map +1 -0
  296. package/dist/src/ipc/client.js +226 -0
  297. package/dist/src/ipc/client.js.map +1 -0
  298. package/dist/src/ipc/control.js +70 -0
  299. package/dist/src/ipc/control.js.map +1 -0
  300. package/dist/src/ipc/ghreadshim.js +77 -0
  301. package/dist/src/ipc/ghreadshim.js.map +1 -0
  302. package/dist/src/ipc/launcher.js +65 -0
  303. package/dist/src/ipc/launcher.js.map +1 -0
  304. package/dist/src/ipc/permits.js +293 -0
  305. package/dist/src/ipc/permits.js.map +1 -0
  306. package/dist/src/ipc/postshim.js +137 -0
  307. package/dist/src/ipc/postshim.js.map +1 -0
  308. package/dist/src/ipc/protocol.js +239 -0
  309. package/dist/src/ipc/protocol.js.map +1 -0
  310. package/dist/src/ipc/server.js +312 -0
  311. package/dist/src/ipc/server.js.map +1 -0
  312. package/dist/src/ipc/workerspec.js +148 -0
  313. package/dist/src/ipc/workerspec.js.map +1 -0
  314. package/dist/src/learner/learner.js +1272 -0
  315. package/dist/src/learner/learner.js.map +1 -0
  316. package/dist/src/learner/miner.js +948 -0
  317. package/dist/src/learner/miner.js.map +1 -0
  318. package/dist/src/learner/packet.js +216 -0
  319. package/dist/src/learner/packet.js.map +1 -0
  320. package/dist/src/learner/receipt.js +135 -0
  321. package/dist/src/learner/receipt.js.map +1 -0
  322. package/dist/src/learner/status.js +38 -0
  323. package/dist/src/learner/status.js.map +1 -0
  324. package/dist/src/ledgercheck.js +120 -0
  325. package/dist/src/ledgercheck.js.map +1 -0
  326. package/dist/src/lessonledger.js +451 -0
  327. package/dist/src/lessonledger.js.map +1 -0
  328. package/dist/src/lessonselect.js +639 -0
  329. package/dist/src/lessonselect.js.map +1 -0
  330. package/dist/src/lessonshadow.js +540 -0
  331. package/dist/src/lessonshadow.js.map +1 -0
  332. package/dist/src/levers.js +302 -0
  333. package/dist/src/levers.js.map +1 -0
  334. package/dist/src/live/projection.js +450 -0
  335. package/dist/src/live/projection.js.map +1 -0
  336. package/dist/src/live/publish.js +83 -0
  337. package/dist/src/live/publish.js.map +1 -0
  338. package/dist/src/live/read.js +60 -0
  339. package/dist/src/live/read.js.map +1 -0
  340. package/dist/src/livecli.js +288 -0
  341. package/dist/src/livecli.js.map +1 -0
  342. package/dist/src/loginhint.js +42 -0
  343. package/dist/src/loginhint.js.map +1 -0
  344. package/dist/src/loginrefresh/atomic.js +66 -0
  345. package/dist/src/loginrefresh/atomic.js.map +1 -0
  346. package/dist/src/loginrefresh/lock.js +93 -0
  347. package/dist/src/loginrefresh/lock.js.map +1 -0
  348. package/dist/src/loginrefresh/main.js +58 -0
  349. package/dist/src/loginrefresh/main.js.map +1 -0
  350. package/dist/src/loginrefresh/pirefresh.js +49 -0
  351. package/dist/src/loginrefresh/pirefresh.js.map +1 -0
  352. package/dist/src/loginrefresh/refresher.js +286 -0
  353. package/dist/src/loginrefresh/refresher.js.map +1 -0
  354. package/dist/src/loginrefresh/supervisor.js +253 -0
  355. package/dist/src/loginrefresh/supervisor.js.map +1 -0
  356. package/dist/src/loginrefresh/types.js +4 -0
  357. package/dist/src/loginrefresh/types.js.map +1 -0
  358. package/dist/src/memory/context.js +251 -0
  359. package/dist/src/memory/context.js.map +1 -0
  360. package/dist/src/memory/datafence.js +5 -0
  361. package/dist/src/memory/datafence.js.map +1 -0
  362. package/dist/src/memory/delivery.js +131 -0
  363. package/dist/src/memory/delivery.js.map +1 -0
  364. package/dist/src/memory/eventtags.js +81 -0
  365. package/dist/src/memory/eventtags.js.map +1 -0
  366. package/dist/src/memory/health.js +713 -0
  367. package/dist/src/memory/health.js.map +1 -0
  368. package/dist/src/memory/import.js +504 -0
  369. package/dist/src/memory/import.js.map +1 -0
  370. package/dist/src/memory/layout.js +252 -0
  371. package/dist/src/memory/layout.js.map +1 -0
  372. package/dist/src/memory/legacy-file.js +100 -0
  373. package/dist/src/memory/legacy-file.js.map +1 -0
  374. package/dist/src/memory/lessons.js +370 -0
  375. package/dist/src/memory/lessons.js.map +1 -0
  376. package/dist/src/memory/ovsession.js +449 -0
  377. package/dist/src/memory/ovsession.js.map +1 -0
  378. package/dist/src/memory/relayout.js +259 -0
  379. package/dist/src/memory/relayout.js.map +1 -0
  380. package/dist/src/memory/roundactivity.js +180 -0
  381. package/dist/src/memory/roundactivity.js.map +1 -0
  382. package/dist/src/memory/selection.js +690 -0
  383. package/dist/src/memory/selection.js.map +1 -0
  384. package/dist/src/memory/shadowstatus.js +69 -0
  385. package/dist/src/memory/shadowstatus.js.map +1 -0
  386. package/dist/src/memory/store.js +83 -0
  387. package/dist/src/memory/store.js.map +1 -0
  388. package/dist/src/memory/textidentity.js +8 -0
  389. package/dist/src/memory/textidentity.js.map +1 -0
  390. package/dist/src/metrics.js +773 -0
  391. package/dist/src/metrics.js.map +1 -0
  392. package/dist/src/missingconfig.js +8 -0
  393. package/dist/src/missingconfig.js.map +1 -0
  394. package/dist/src/npmverbs.js +226 -0
  395. package/dist/src/npmverbs.js.map +1 -0
  396. package/dist/src/onboard/choices.js +191 -0
  397. package/dist/src/onboard/choices.js.map +1 -0
  398. package/dist/src/onboard/engine.js +344 -0
  399. package/dist/src/onboard/engine.js.map +1 -0
  400. package/dist/src/onboard/launch.js +83 -0
  401. package/dist/src/onboard/launch.js.map +1 -0
  402. package/dist/src/onboard/steps/appslug.js +211 -0
  403. package/dist/src/onboard/steps/appslug.js.map +1 -0
  404. package/dist/src/onboard/steps/bringup.js +408 -0
  405. package/dist/src/onboard/steps/bringup.js.map +1 -0
  406. package/dist/src/onboard/steps/compose.js +343 -0
  407. package/dist/src/onboard/steps/compose.js.map +1 -0
  408. package/dist/src/onboard/steps/configdelivery.js +50 -0
  409. package/dist/src/onboard/steps/configdelivery.js.map +1 -0
  410. package/dist/src/onboard/steps/configfill.js +1148 -0
  411. package/dist/src/onboard/steps/configfill.js.map +1 -0
  412. package/dist/src/onboard/steps/deploysh.js +80 -0
  413. package/dist/src/onboard/steps/deploysh.js.map +1 -0
  414. package/dist/src/onboard/steps/embeddings.js +472 -0
  415. package/dist/src/onboard/steps/embeddings.js.map +1 -0
  416. package/dist/src/onboard/steps/escalation.js +571 -0
  417. package/dist/src/onboard/steps/escalation.js.map +1 -0
  418. package/dist/src/onboard/steps/githubapp.js +1092 -0
  419. package/dist/src/onboard/steps/githubapp.js.map +1 -0
  420. package/dist/src/onboard/steps/golive.js +506 -0
  421. package/dist/src/onboard/steps/golive.js.map +1 -0
  422. package/dist/src/onboard/steps/index.js +67 -0
  423. package/dist/src/onboard/steps/index.js.map +1 -0
  424. package/dist/src/onboard/steps/ingress.js +762 -0
  425. package/dist/src/onboard/steps/ingress.js.map +1 -0
  426. package/dist/src/onboard/steps/inventory.js +420 -0
  427. package/dist/src/onboard/steps/inventory.js.map +1 -0
  428. package/dist/src/onboard/steps/llmauth.js +893 -0
  429. package/dist/src/onboard/steps/llmauth.js.map +1 -0
  430. package/dist/src/onboard/steps/memory.js +1060 -0
  431. package/dist/src/onboard/steps/memory.js.map +1 -0
  432. package/dist/src/onboard/steps/memorykeys.js +43 -0
  433. package/dist/src/onboard/steps/memorykeys.js.map +1 -0
  434. package/dist/src/onboard/steps/preflight.js +172 -0
  435. package/dist/src/onboard/steps/preflight.js.map +1 -0
  436. package/dist/src/onboard/steps/proof.js +502 -0
  437. package/dist/src/onboard/steps/proof.js.map +1 -0
  438. package/dist/src/onboard/steps/ssh.js +300 -0
  439. package/dist/src/onboard/steps/ssh.js.map +1 -0
  440. package/dist/src/onboard/steps/topology.js +291 -0
  441. package/dist/src/onboard/steps/topology.js.map +1 -0
  442. package/dist/src/onboard/steps/transport.js +511 -0
  443. package/dist/src/onboard/steps/transport.js.map +1 -0
  444. package/dist/src/onboard/steps/verifyup.js +362 -0
  445. package/dist/src/onboard/steps/verifyup.js.map +1 -0
  446. package/dist/src/onboard/steps/vmdeploy.js +368 -0
  447. package/dist/src/onboard/steps/vmdeploy.js.map +1 -0
  448. package/dist/src/onboard/toml.js +53 -0
  449. package/dist/src/onboard/toml.js.map +1 -0
  450. package/dist/src/opencli.js +508 -0
  451. package/dist/src/opencli.js.map +1 -0
  452. package/dist/src/openpr.js +457 -0
  453. package/dist/src/openpr.js.map +1 -0
  454. package/dist/src/openviking/client.js +821 -0
  455. package/dist/src/openviking/client.js.map +1 -0
  456. package/dist/src/openviking/context.js +218 -0
  457. package/dist/src/openviking/context.js.map +1 -0
  458. package/dist/src/openviking/embedder.js +457 -0
  459. package/dist/src/openviking/embedder.js.map +1 -0
  460. package/dist/src/openviking/gapsink.js +38 -0
  461. package/dist/src/openviking/gapsink.js.map +1 -0
  462. package/dist/src/openviking/health.js +69 -0
  463. package/dist/src/openviking/health.js.map +1 -0
  464. package/dist/src/openviking/index.js +24 -0
  465. package/dist/src/openviking/index.js.map +1 -0
  466. package/dist/src/openviking/lessonfind.js +54 -0
  467. package/dist/src/openviking/lessonfind.js.map +1 -0
  468. package/dist/src/openviking/probe.js +89 -0
  469. package/dist/src/openviking/probe.js.map +1 -0
  470. package/dist/src/openviking/proc.js +95 -0
  471. package/dist/src/openviking/proc.js.map +1 -0
  472. package/dist/src/openviking/provisionreport.js +144 -0
  473. package/dist/src/openviking/provisionreport.js.map +1 -0
  474. package/dist/src/openviking/reembed.js +317 -0
  475. package/dist/src/openviking/reembed.js.map +1 -0
  476. package/dist/src/openviking/relayout.js +153 -0
  477. package/dist/src/openviking/relayout.js.map +1 -0
  478. package/dist/src/openviking/sidecar-main.js +39 -0
  479. package/dist/src/openviking/sidecar-main.js.map +1 -0
  480. package/dist/src/openviking/sidecar.js +79 -0
  481. package/dist/src/openviking/sidecar.js.map +1 -0
  482. package/dist/src/openviking/store.js +1758 -0
  483. package/dist/src/openviking/store.js.map +1 -0
  484. package/dist/src/openviking/supervisor.js +422 -0
  485. package/dist/src/openviking/supervisor.js.map +1 -0
  486. package/dist/src/openviking/types.js +5 -0
  487. package/dist/src/openviking/types.js.map +1 -0
  488. package/dist/src/operator-recovery.js +172 -0
  489. package/dist/src/operator-recovery.js.map +1 -0
  490. package/dist/src/operatormessage.js +44 -0
  491. package/dist/src/operatormessage.js.map +1 -0
  492. package/dist/src/outcomes.js +1361 -0
  493. package/dist/src/outcomes.js.map +1 -0
  494. package/dist/src/piauthcopy.js +5 -0
  495. package/dist/src/piauthcopy.js.map +1 -0
  496. package/dist/src/picli.js +45 -0
  497. package/dist/src/picli.js.map +1 -0
  498. package/dist/src/picwd.js +25 -0
  499. package/dist/src/picwd.js.map +1 -0
  500. package/dist/src/pilogin.js +35 -0
  501. package/dist/src/pilogin.js.map +1 -0
  502. package/dist/src/pingpong.js +1028 -0
  503. package/dist/src/pingpong.js.map +1 -0
  504. package/dist/src/posting-journal.js +441 -0
  505. package/dist/src/posting-journal.js.map +1 -0
  506. package/dist/src/posting-outbox.js +662 -0
  507. package/dist/src/posting-outbox.js.map +1 -0
  508. package/dist/src/posting-receipt.js +203 -0
  509. package/dist/src/posting-receipt.js.map +1 -0
  510. package/dist/src/posting.js +348 -0
  511. package/dist/src/posting.js.map +1 -0
  512. package/dist/src/postops.js +637 -0
  513. package/dist/src/postops.js.map +1 -0
  514. package/dist/src/pr-activity.js +11 -0
  515. package/dist/src/pr-activity.js.map +1 -0
  516. package/dist/src/pr-author.js +5 -0
  517. package/dist/src/pr-author.js.map +1 -0
  518. package/dist/src/pr-conversation.js +355 -0
  519. package/dist/src/pr-conversation.js.map +1 -0
  520. package/dist/src/pr-session-lifecycle.js +542 -0
  521. package/dist/src/pr-session-lifecycle.js.map +1 -0
  522. package/dist/src/preflight.js +341 -0
  523. package/dist/src/preflight.js.map +1 -0
  524. package/dist/src/priorresolve.js +275 -0
  525. package/dist/src/priorresolve.js.map +1 -0
  526. package/dist/src/qcrbin.js +180 -0
  527. package/dist/src/qcrbin.js.map +1 -0
  528. package/dist/src/recheck.js +1398 -0
  529. package/dist/src/recheck.js.map +1 -0
  530. package/dist/src/reconcile.js +820 -0
  531. package/dist/src/reconcile.js.map +1 -0
  532. package/dist/src/recordedfindings.js +15 -0
  533. package/dist/src/recordedfindings.js.map +1 -0
  534. package/dist/src/regression.js +546 -0
  535. package/dist/src/regression.js.map +1 -0
  536. package/dist/src/replay.js +598 -0
  537. package/dist/src/replay.js.map +1 -0
  538. package/dist/src/requiredpasses.js +113 -0
  539. package/dist/src/requiredpasses.js.map +1 -0
  540. package/dist/src/resumecheck.js +168 -0
  541. package/dist/src/resumecheck.js.map +1 -0
  542. package/dist/src/reviewcli.js +671 -0
  543. package/dist/src/reviewcli.js.map +1 -0
  544. package/dist/src/reviewcontext.js +22 -0
  545. package/dist/src/reviewcontext.js.map +1 -0
  546. package/dist/src/reviewdetach.js +574 -0
  547. package/dist/src/reviewdetach.js.map +1 -0
  548. package/dist/src/reviewhistory.js +284 -0
  549. package/dist/src/reviewhistory.js.map +1 -0
  550. package/dist/src/reviewhome.js +26 -0
  551. package/dist/src/reviewhome.js.map +1 -0
  552. package/dist/src/reviewmemory.js +110 -0
  553. package/dist/src/reviewmemory.js.map +1 -0
  554. package/dist/src/reviewrendercli.js +104 -0
  555. package/dist/src/reviewrendercli.js.map +1 -0
  556. package/dist/src/round-work.js +20 -0
  557. package/dist/src/round-work.js.map +1 -0
  558. package/dist/src/runhost.js +72 -0
  559. package/dist/src/runhost.js.map +1 -0
  560. package/dist/src/scanauth.js +25 -0
  561. package/dist/src/scanauth.js.map +1 -0
  562. package/dist/src/scanbox.js +846 -0
  563. package/dist/src/scanbox.js.map +1 -0
  564. package/dist/src/scancli.js +1074 -0
  565. package/dist/src/scancli.js.map +1 -0
  566. package/dist/src/scandriverlog.js +30 -0
  567. package/dist/src/scandriverlog.js.map +1 -0
  568. package/dist/src/session-bridge.js +75 -0
  569. package/dist/src/session-bridge.js.map +1 -0
  570. package/dist/src/session-command-payload.js +29 -0
  571. package/dist/src/session-command-payload.js.map +1 -0
  572. package/dist/src/session-command-supervisor.js +939 -0
  573. package/dist/src/session-command-supervisor.js.map +1 -0
  574. package/dist/src/sessionjsonl.js +85 -0
  575. package/dist/src/sessionjsonl.js.map +1 -0
  576. package/dist/src/sessions.js +541 -0
  577. package/dist/src/sessions.js.map +1 -0
  578. package/dist/src/setup/adapters.js +330 -0
  579. package/dist/src/setup/adapters.js.map +1 -0
  580. package/dist/src/setup/baselines.js +64 -0
  581. package/dist/src/setup/baselines.js.map +1 -0
  582. package/dist/src/setup/caserunner.js +554 -0
  583. package/dist/src/setup/caserunner.js.map +1 -0
  584. package/dist/src/setup/configcommit.js +358 -0
  585. package/dist/src/setup/configcommit.js.map +1 -0
  586. package/dist/src/setup/engine.js +672 -0
  587. package/dist/src/setup/engine.js.map +1 -0
  588. package/dist/src/setup/facts.js +593 -0
  589. package/dist/src/setup/facts.js.map +1 -0
  590. package/dist/src/setup/findings.js +948 -0
  591. package/dist/src/setup/findings.js.map +1 -0
  592. package/dist/src/setup/history.js +338 -0
  593. package/dist/src/setup/history.js.map +1 -0
  594. package/dist/src/setup/interview.js +185 -0
  595. package/dist/src/setup/interview.js.map +1 -0
  596. package/dist/src/setup/ledger.js +207 -0
  597. package/dist/src/setup/ledger.js.map +1 -0
  598. package/dist/src/setup/pins.js +43 -0
  599. package/dist/src/setup/pins.js.map +1 -0
  600. package/dist/src/setup/portrait.js +432 -0
  601. package/dist/src/setup/portrait.js.map +1 -0
  602. package/dist/src/setup/presentation.js +9 -0
  603. package/dist/src/setup/presentation.js.map +1 -0
  604. package/dist/src/setup/profile.js +417 -0
  605. package/dist/src/setup/profile.js.map +1 -0
  606. package/dist/src/setup/replay.js +240 -0
  607. package/dist/src/setup/replay.js.map +1 -0
  608. package/dist/src/setup/replaybuild.js +311 -0
  609. package/dist/src/setup/replaybuild.js.map +1 -0
  610. package/dist/src/setup/replayrun.js +806 -0
  611. package/dist/src/setup/replayrun.js.map +1 -0
  612. package/dist/src/setup/replaysource.js +169 -0
  613. package/dist/src/setup/replaysource.js.map +1 -0
  614. package/dist/src/setup/retune.js +333 -0
  615. package/dist/src/setup/retune.js.map +1 -0
  616. package/dist/src/setup/retunelog.js +51 -0
  617. package/dist/src/setup/retunelog.js.map +1 -0
  618. package/dist/src/setup/scan.js +725 -0
  619. package/dist/src/setup/scan.js.map +1 -0
  620. package/dist/src/setup/scanbuild.js +133 -0
  621. package/dist/src/setup/scanbuild.js.map +1 -0
  622. package/dist/src/setup/scanfacts.js +96 -0
  623. package/dist/src/setup/scanfacts.js.map +1 -0
  624. package/dist/src/setup/scanlease.js +251 -0
  625. package/dist/src/setup/scanlease.js.map +1 -0
  626. package/dist/src/setup/scanreport.js +289 -0
  627. package/dist/src/setup/scanreport.js.map +1 -0
  628. package/dist/src/setup/scanrun.js +1733 -0
  629. package/dist/src/setup/scanrun.js.map +1 -0
  630. package/dist/src/setup/scantree.js +257 -0
  631. package/dist/src/setup/scantree.js.map +1 -0
  632. package/dist/src/setup/stage2.js +617 -0
  633. package/dist/src/setup/stage2.js.map +1 -0
  634. package/dist/src/setup/stage2request.js +259 -0
  635. package/dist/src/setup/stage2request.js.map +1 -0
  636. package/dist/src/setup/survey.js +307 -0
  637. package/dist/src/setup/survey.js.map +1 -0
  638. package/dist/src/setup/surveyrun.js +549 -0
  639. package/dist/src/setup/surveyrun.js.map +1 -0
  640. package/dist/src/setup/textsniff.js +89 -0
  641. package/dist/src/setup/textsniff.js.map +1 -0
  642. package/dist/src/setup/trackersink.js +395 -0
  643. package/dist/src/setup/trackersink.js.map +1 -0
  644. package/dist/src/setup/triage.js +636 -0
  645. package/dist/src/setup/triage.js.map +1 -0
  646. package/dist/src/setup/tuning.js +585 -0
  647. package/dist/src/setup/tuning.js.map +1 -0
  648. package/dist/src/setup/unitcut.js +437 -0
  649. package/dist/src/setup/unitcut.js.map +1 -0
  650. package/dist/src/singleflight.js +72 -0
  651. package/dist/src/singleflight.js.map +1 -0
  652. package/dist/src/slack/announce.js +725 -0
  653. package/dist/src/slack/announce.js.map +1 -0
  654. package/dist/src/slack/api.js +227 -0
  655. package/dist/src/slack/api.js.map +1 -0
  656. package/dist/src/slack/messages.js +158 -0
  657. package/dist/src/slack/messages.js.map +1 -0
  658. package/dist/src/slack/origin.js +550 -0
  659. package/dist/src/slack/origin.js.map +1 -0
  660. package/dist/src/slack/policy.js +225 -0
  661. package/dist/src/slack/policy.js.map +1 -0
  662. package/dist/src/slack/reply.js +153 -0
  663. package/dist/src/slack/reply.js.map +1 -0
  664. package/dist/src/slack/router.js +189 -0
  665. package/dist/src/slack/router.js.map +1 -0
  666. package/dist/src/slack/socket.js +323 -0
  667. package/dist/src/slack/socket.js.map +1 -0
  668. package/dist/src/slack/types.js +11 -0
  669. package/dist/src/slack/types.js.map +1 -0
  670. package/dist/src/sqlitehint.js +30 -0
  671. package/dist/src/sqlitehint.js.map +1 -0
  672. package/dist/src/ssh-observation.js +25 -0
  673. package/dist/src/ssh-observation.js.map +1 -0
  674. package/dist/src/state.js +5869 -0
  675. package/dist/src/state.js.map +1 -0
  676. package/dist/src/statemachine.js +220 -0
  677. package/dist/src/statemachine.js.map +1 -0
  678. package/dist/src/stubs.js +52 -0
  679. package/dist/src/stubs.js.map +1 -0
  680. package/dist/src/takeover.js +506 -0
  681. package/dist/src/takeover.js.map +1 -0
  682. package/dist/src/takeoverpi.js +249 -0
  683. package/dist/src/takeoverpi.js.map +1 -0
  684. package/dist/src/terminal-failure.js +26 -0
  685. package/dist/src/terminal-failure.js.map +1 -0
  686. package/dist/src/thread-resolution-refusal.js +4 -0
  687. package/dist/src/thread-resolution-refusal.js.map +1 -0
  688. package/dist/src/tmux.js +126 -0
  689. package/dist/src/tmux.js.map +1 -0
  690. package/dist/src/trackerid.js +43 -0
  691. package/dist/src/trackerid.js.map +1 -0
  692. package/dist/src/transcriptindex.js +512 -0
  693. package/dist/src/transcriptindex.js.map +1 -0
  694. package/dist/src/tui/actions.js +404 -0
  695. package/dist/src/tui/actions.js.map +1 -0
  696. package/dist/src/tui/alerts.js +523 -0
  697. package/dist/src/tui/alerts.js.map +1 -0
  698. package/dist/src/tui/app.js +1835 -0
  699. package/dist/src/tui/app.js.map +1 -0
  700. package/dist/src/tui/archiveops.js +164 -0
  701. package/dist/src/tui/archiveops.js.map +1 -0
  702. package/dist/src/tui/autofile.js +191 -0
  703. package/dist/src/tui/autofile.js.map +1 -0
  704. package/dist/src/tui/board.js +1143 -0
  705. package/dist/src/tui/board.js.map +1 -0
  706. package/dist/src/tui/candidates.js +699 -0
  707. package/dist/src/tui/candidates.js.map +1 -0
  708. package/dist/src/tui/cellwidth.js +151 -0
  709. package/dist/src/tui/cellwidth.js.map +1 -0
  710. package/dist/src/tui/changelog-store.js +404 -0
  711. package/dist/src/tui/changelog-store.js.map +1 -0
  712. package/dist/src/tui/chord.js +26 -0
  713. package/dist/src/tui/chord.js.map +1 -0
  714. package/dist/src/tui/clipboard.js +81 -0
  715. package/dist/src/tui/clipboard.js.map +1 -0
  716. package/dist/src/tui/cmdbar.js +253 -0
  717. package/dist/src/tui/cmdbar.js.map +1 -0
  718. package/dist/src/tui/companion.js +1275 -0
  719. package/dist/src/tui/companion.js.map +1 -0
  720. package/dist/src/tui/companionplan.js +200 -0
  721. package/dist/src/tui/companionplan.js.map +1 -0
  722. package/dist/src/tui/companiontab.js +61 -0
  723. package/dist/src/tui/companiontab.js.map +1 -0
  724. package/dist/src/tui/confighome.js +105 -0
  725. package/dist/src/tui/confighome.js.map +1 -0
  726. package/dist/src/tui/connect.js +44 -0
  727. package/dist/src/tui/connect.js.map +1 -0
  728. package/dist/src/tui/deploy-ceremony.js +75 -0
  729. package/dist/src/tui/deploy-ceremony.js.map +1 -0
  730. package/dist/src/tui/deploy-drain.js +93 -0
  731. package/dist/src/tui/deploy-drain.js.map +1 -0
  732. package/dist/src/tui/deploy-process.js +234 -0
  733. package/dist/src/tui/deploy-process.js.map +1 -0
  734. package/dist/src/tui/deploytab.js +1263 -0
  735. package/dist/src/tui/deploytab.js.map +1 -0
  736. package/dist/src/tui/design.js +111 -0
  737. package/dist/src/tui/design.js.map +1 -0
  738. package/dist/src/tui/digesttab.js +148 -0
  739. package/dist/src/tui/digesttab.js.map +1 -0
  740. package/dist/src/tui/emptystate.js +32 -0
  741. package/dist/src/tui/emptystate.js.map +1 -0
  742. package/dist/src/tui/evidence.js +790 -0
  743. package/dist/src/tui/evidence.js.map +1 -0
  744. package/dist/src/tui/feed.js +415 -0
  745. package/dist/src/tui/feed.js.map +1 -0
  746. package/dist/src/tui/feedbacktab.js +907 -0
  747. package/dist/src/tui/feedbacktab.js.map +1 -0
  748. package/dist/src/tui/filters.js +72 -0
  749. package/dist/src/tui/filters.js.map +1 -0
  750. package/dist/src/tui/ghread.js +272 -0
  751. package/dist/src/tui/ghread.js.map +1 -0
  752. package/dist/src/tui/historypull.js +61 -0
  753. package/dist/src/tui/historypull.js.map +1 -0
  754. package/dist/src/tui/inboxpull.js +197 -0
  755. package/dist/src/tui/inboxpull.js.map +1 -0
  756. package/dist/src/tui/index.js +1425 -0
  757. package/dist/src/tui/index.js.map +1 -0
  758. package/dist/src/tui/issuesink.js +206 -0
  759. package/dist/src/tui/issuesink.js.map +1 -0
  760. package/dist/src/tui/jsonfile.js +32 -0
  761. package/dist/src/tui/jsonfile.js.map +1 -0
  762. package/dist/src/tui/keys.js +97 -0
  763. package/dist/src/tui/keys.js.map +1 -0
  764. package/dist/src/tui/layout.js +70 -0
  765. package/dist/src/tui/layout.js.map +1 -0
  766. package/dist/src/tui/learning.js +469 -0
  767. package/dist/src/tui/learning.js.map +1 -0
  768. package/dist/src/tui/lessonswitch.js +103 -0
  769. package/dist/src/tui/lessonswitch.js.map +1 -0
  770. package/dist/src/tui/livecontrol.js +167 -0
  771. package/dist/src/tui/livecontrol.js.map +1 -0
  772. package/dist/src/tui/memforward.js +310 -0
  773. package/dist/src/tui/memforward.js.map +1 -0
  774. package/dist/src/tui/memincident.js +287 -0
  775. package/dist/src/tui/memincident.js.map +1 -0
  776. package/dist/src/tui/memory.js +485 -0
  777. package/dist/src/tui/memory.js.map +1 -0
  778. package/dist/src/tui/memtab.js +513 -0
  779. package/dist/src/tui/memtab.js.map +1 -0
  780. package/dist/src/tui/metricstab.js +979 -0
  781. package/dist/src/tui/metricstab.js.map +1 -0
  782. package/dist/src/tui/model.js +104 -0
  783. package/dist/src/tui/model.js.map +1 -0
  784. package/dist/src/tui/mouse.js +83 -0
  785. package/dist/src/tui/mouse.js.map +1 -0
  786. package/dist/src/tui/msg.js +196 -0
  787. package/dist/src/tui/msg.js.map +1 -0
  788. package/dist/src/tui/nativecompanion.js +321 -0
  789. package/dist/src/tui/nativecompanion.js.map +1 -0
  790. package/dist/src/tui/openurl.js +17 -0
  791. package/dist/src/tui/openurl.js.map +1 -0
  792. package/dist/src/tui/paint.js +501 -0
  793. package/dist/src/tui/paint.js.map +1 -0
  794. package/dist/src/tui/panes.js +74 -0
  795. package/dist/src/tui/panes.js.map +1 -0
  796. package/dist/src/tui/pipane.js +166 -0
  797. package/dist/src/tui/pipane.js.map +1 -0
  798. package/dist/src/tui/poll-hooks.js +11 -0
  799. package/dist/src/tui/poll-hooks.js.map +1 -0
  800. package/dist/src/tui/poll.js +113 -0
  801. package/dist/src/tui/poll.js.map +1 -0
  802. package/dist/src/tui/proposals.js +715 -0
  803. package/dist/src/tui/proposals.js.map +1 -0
  804. package/dist/src/tui/prpause.js +132 -0
  805. package/dist/src/tui/prpause.js.map +1 -0
  806. package/dist/src/tui/reload.js +351 -0
  807. package/dist/src/tui/reload.js.map +1 -0
  808. package/dist/src/tui/remote.js +312 -0
  809. package/dist/src/tui/remote.js.map +1 -0
  810. package/dist/src/tui/rollover.js +767 -0
  811. package/dist/src/tui/rollover.js.map +1 -0
  812. package/dist/src/tui/scantab.js +1053 -0
  813. package/dist/src/tui/scantab.js.map +1 -0
  814. package/dist/src/tui/setupentry.js +81 -0
  815. package/dist/src/tui/setupentry.js.map +1 -0
  816. package/dist/src/tui/sidebar.js +602 -0
  817. package/dist/src/tui/sidebar.js.map +1 -0
  818. package/dist/src/tui/sshtriage.js +393 -0
  819. package/dist/src/tui/sshtriage.js.map +1 -0
  820. package/dist/src/tui/startup.js +172 -0
  821. package/dist/src/tui/startup.js.map +1 -0
  822. package/dist/src/tui/tabs.js +41 -0
  823. package/dist/src/tui/tabs.js.map +1 -0
  824. package/dist/src/tui/termmirror.js +217 -0
  825. package/dist/src/tui/termmirror.js.map +1 -0
  826. package/dist/src/tui/theme.js +95 -0
  827. package/dist/src/tui/theme.js.map +1 -0
  828. package/dist/src/tui/ticketstab.js +224 -0
  829. package/dist/src/tui/ticketstab.js.map +1 -0
  830. package/dist/src/tui/tmuxsocket.js +101 -0
  831. package/dist/src/tui/tmuxsocket.js.map +1 -0
  832. package/dist/src/tui/trackertickets.js +77 -0
  833. package/dist/src/tui/trackertickets.js.map +1 -0
  834. package/dist/src/tui/unwedge.js +245 -0
  835. package/dist/src/tui/unwedge.js.map +1 -0
  836. package/dist/src/tui/viewer.js +479 -0
  837. package/dist/src/tui/viewer.js.map +1 -0
  838. package/dist/src/tui/viewerinput.js +125 -0
  839. package/dist/src/tui/viewerinput.js.map +1 -0
  840. package/dist/src/updatenotice.js +143 -0
  841. package/dist/src/updatenotice.js.map +1 -0
  842. package/dist/src/usage.js +1007 -0
  843. package/dist/src/usage.js.map +1 -0
  844. package/dist/src/verdicts.js +3848 -0
  845. package/dist/src/verdicts.js.map +1 -0
  846. package/dist/src/version.js +58 -0
  847. package/dist/src/version.js.map +1 -0
  848. package/dist/src/wakes.js +411 -0
  849. package/dist/src/wakes.js.map +1 -0
  850. package/dist/src/webhook.js +725 -0
  851. package/dist/src/webhook.js.map +1 -0
  852. package/dist/src/webhookheal.js +238 -0
  853. package/dist/src/webhookheal.js.map +1 -0
  854. package/dist/src/wire.js +1801 -0
  855. package/dist/src/wire.js.map +1 -0
  856. package/dist/src/worker-session-host.js +531 -0
  857. package/dist/src/worker-session-host.js.map +1 -0
  858. package/dist/src/workerbreaker.js +112 -0
  859. package/dist/src/workerbreaker.js.map +1 -0
  860. package/dist/src/workercontrol.js +49 -0
  861. package/dist/src/workercontrol.js.map +1 -0
  862. package/dist/src/workerlease.js +351 -0
  863. package/dist/src/workerlease.js.map +1 -0
  864. package/dist/src/workerliveness.js +84 -0
  865. package/dist/src/workerliveness.js.map +1 -0
  866. package/dist/src/workerlog.js +65 -0
  867. package/dist/src/workerlog.js.map +1 -0
  868. package/dist/src/workerruntime.js +10 -0
  869. package/dist/src/workerruntime.js.map +1 -0
  870. package/dist/src/workersupervisor.js +1206 -0
  871. package/dist/src/workersupervisor.js.map +1 -0
  872. package/dist/src/workerusage.js +23 -0
  873. package/dist/src/workerusage.js.map +1 -0
  874. package/dist/src/workerworld.js +332 -0
  875. package/dist/src/workerworld.js.map +1 -0
  876. package/dist/src/worktrees.js +984 -0
  877. package/dist/src/worktrees.js.map +1 -0
  878. package/docker/Dockerfile +284 -0
  879. package/docker/Dockerfile.dockerignore +35 -0
  880. package/docker/README.md +221 -0
  881. package/docker/companion/Dockerfile +139 -0
  882. package/docker/companion/agents/evidence.md +34 -0
  883. package/docker/companion/agents/explore.md +26 -0
  884. package/docker/companion/agents/pass-survey.md +49 -0
  885. package/docker/companion/agents/study.md +26 -0
  886. package/docker/companion/entrypoint.sh +343 -0
  887. package/docker/companion/memory-health.mjs +269 -0
  888. package/docker/entrypoint.sh +782 -0
  889. package/docker/memory/Dockerfile +191 -0
  890. package/docker/memory/Dockerfile.dockerignore +20 -0
  891. package/docker/memory/README.md +60 -0
  892. package/docker/memory/entrypoint.sh +163 -0
  893. package/docker/setid/qcr-setid.c +109 -0
  894. package/docker/tunnel/Dockerfile.cloudflared +51 -0
  895. package/docker/tunnel/Dockerfile.ngrok +40 -0
  896. package/docker/tunnel/sidecar.mjs +411 -0
  897. package/docker/vm/tmux.conf +26 -0
  898. package/docker-compose.yml +348 -0
  899. package/extensions/cli/bash-guard-ambient.ts +71 -0
  900. package/extensions/cli/bash-guard.ts +12 -0
  901. package/extensions/cli/companion-guard.ts +8 -0
  902. package/extensions/cli/debug-round.ts +9 -0
  903. package/extensions/cli/inject-bridge.ts +8 -0
  904. package/extensions/cli/ledger-pin.ts +13 -0
  905. package/extensions/cli/lens.ts +22 -0
  906. package/extensions/cli/muted-theme.ts +9 -0
  907. package/extensions/cli/onboarding.ts +8 -0
  908. package/extensions/cli/setup.ts +8 -0
  909. package/extensions/cli/tui-view.ts +8 -0
  910. package/extensions/companion-guard.ts +411 -0
  911. package/extensions/debug-round.ts +281 -0
  912. package/extensions/inject-bridge.ts +630 -0
  913. package/extensions/inline-bash.ts +102 -0
  914. package/extensions/muted-theme.json +68 -0
  915. package/extensions/muted-theme.ts +23 -0
  916. package/extensions/onboarding.ts +233 -0
  917. package/extensions/setup-replay.ts +336 -0
  918. package/extensions/setup-scan.ts +1236 -0
  919. package/extensions/setup-triage.ts +190 -0
  920. package/extensions/setup.ts +1428 -0
  921. package/extensions/tui-view.ts +489 -0
  922. package/extensions/vendor/openviking-pi/LICENSE +201 -0
  923. package/extensions/vendor/openviking-pi/NOTICE +68 -0
  924. package/extensions/vendor/openviking-pi/README.md +550 -0
  925. package/extensions/vendor/openviking-pi/client.ts +588 -0
  926. package/extensions/vendor/openviking-pi/config.json +28 -0
  927. package/extensions/vendor/openviking-pi/config.ts +271 -0
  928. package/extensions/vendor/openviking-pi/index.ts +523 -0
  929. package/extensions/vendor/openviking-pi/lib/capture-adapter.d.mts +10 -0
  930. package/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +88 -0
  931. package/extensions/vendor/openviking-pi/lib/takeover-core.d.mts +80 -0
  932. package/extensions/vendor/openviking-pi/lib/takeover-core.mjs +406 -0
  933. package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.d.mts +1 -0
  934. package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +42 -0
  935. package/extensions/vendor/openviking-pi/patches/0000-vendoring-and-build-conformance.patch +726 -0
  936. package/extensions/vendor/openviking-pi/patches/0001-remove-session-before-compact-handler.patch +101 -0
  937. package/extensions/vendor/openviking-pi/patches/0002-no-ambient-work-in-subagent-leaves.patch +202 -0
  938. package/extensions/vendor/openviking-pi/patches/0003-never-auto-create-openviking-sessions.patch +120 -0
  939. package/extensions/vendor/openviking-pi/patches/0004-viking-write-durable-batch-write.patch +521 -0
  940. package/extensions/vendor/openviking-pi/patches/0005-second-memory-namespace.patch +786 -0
  941. package/extensions/vendor/openviking-pi/patches/0006-writes-that-stick-and-honest-errors.patch +996 -0
  942. package/extensions/vendor/openviking-pi/patches/0007-bounded-recall-no-commit.patch +794 -0
  943. package/extensions/vendor/openviking-pi/patches/0008-pr-derived-recall.patch +379 -0
  944. package/extensions/vendor/openviking-pi/patches/0009-pr-session-tools-in-round-areas.patch +375 -0
  945. package/extensions/vendor/openviking-pi/patches/0010-typed-lessons.patch +448 -0
  946. package/extensions/vendor/openviking-pi/patches/0011-review-session-memory-boundary.patch +382 -0
  947. package/extensions/vendor/openviking-pi/patches/0012-typecheck-against-the-real-pi.patch +129 -0
  948. package/extensions/vendor/openviking-pi/patches/0013-createopenviking-factory.patch +1168 -0
  949. package/extensions/vendor/openviking-pi/qcr-hint.ts +313 -0
  950. package/extensions/vendor/openviking-pi/qcr-honest.ts +183 -0
  951. package/extensions/vendor/openviking-pi/qcr-hooks.ts +87 -0
  952. package/extensions/vendor/openviking-pi/qcr-leaf.ts +78 -0
  953. package/extensions/vendor/openviking-pi/qcr-lesson.ts +231 -0
  954. package/extensions/vendor/openviking-pi/qcr-namespaces.ts +198 -0
  955. package/extensions/vendor/openviking-pi/qcr-recall.ts +288 -0
  956. package/extensions/vendor/openviking-pi/qcr-shim.ts +142 -0
  957. package/extensions/vendor/openviking-pi/qcr-write.ts +427 -0
  958. package/extensions/vendor/openviking-pi/recall.ts +149 -0
  959. package/extensions/vendor/openviking-pi/shared/capture-utils.d.mts +9 -0
  960. package/extensions/vendor/openviking-pi/shared/capture-utils.mjs +515 -0
  961. package/extensions/vendor/openviking-pi/shared/credentials.d.mts +14 -0
  962. package/extensions/vendor/openviking-pi/shared/credentials.mjs +228 -0
  963. package/extensions/vendor/openviking-pi/shared/pending-queue.d.mts +19 -0
  964. package/extensions/vendor/openviking-pi/shared/pending-queue.mjs +458 -0
  965. package/extensions/vendor/openviking-pi/shared/profile-inject.d.mts +17 -0
  966. package/extensions/vendor/openviking-pi/shared/profile-inject.mjs +294 -0
  967. package/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +195 -0
  968. package/extensions/vendor/openviking-pi/shared/recall-core.d.mts +13 -0
  969. package/extensions/vendor/openviking-pi/shared/recall-core.mjs +609 -0
  970. package/extensions/vendor/openviking-pi/shared/retryable.mjs +9 -0
  971. package/extensions/vendor/openviking-pi/shared/session-model.d.mts +3 -0
  972. package/extensions/vendor/openviking-pi/shared/session-model.mjs +57 -0
  973. package/extensions/vendor/openviking-pi/shared/uri-guard.mjs +56 -0
  974. package/extensions/vendor/openviking-pi/shared/workspace-peer.d.mts +5 -0
  975. package/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
  976. package/extensions/vendor/openviking-pi/sync.ts +163 -0
  977. package/extensions/vendor/openviking-pi/takeover.ts +36 -0
  978. package/extensions/vendor/openviking-pi/tools.ts +479 -0
  979. package/extensions/vendor/openviking-pi/upstream.json +48 -0
  980. package/package.json +138 -4
  981. package/profiles/default/README.md +21 -0
  982. package/profiles/default/addenda/.gitkeep +0 -0
  983. package/profiles/default/profile.toml +17 -0
  984. package/profiles/example-django/README.md +21 -0
  985. package/profiles/example-django/addenda/10-migration-discipline.md +13 -0
  986. package/profiles/example-django/addenda/20-new-column-nullability.md +12 -0
  987. package/profiles/example-django/addenda/30-test-doctrine.md +12 -0
  988. package/profiles/example-django/addenda/40-orm-usage.md +19 -0
  989. package/profiles/example-django/addenda/50-declared-shape.md +10 -0
  990. package/profiles/example-django/addenda/60-config-placement.md +14 -0
  991. package/profiles/example-django/heuristics.toml +24 -0
  992. package/profiles/example-django/profile.toml +9 -0
  993. package/profiles/fast/README.md +24 -0
  994. package/profiles/fast/profile.toml +59 -0
  995. package/profiles/minimal/README.md +17 -0
  996. package/profiles/minimal/addenda/.gitkeep +0 -0
  997. package/profiles/minimal/profile.toml +8 -0
  998. package/profiles/snappy/README.md +24 -0
  999. package/profiles/snappy/addenda/10-race-as-question.md +6 -0
  1000. package/profiles/snappy/profile.toml +46 -0
  1001. package/prompts/companion-day-open.md +46 -0
  1002. package/prompts/companion-first-run.md +104 -0
  1003. package/prompts/companion.md +821 -0
  1004. package/prompts/conversation-system.md +117 -0
  1005. package/prompts/eod-sweep.md +120 -0
  1006. package/prompts/onboarding.md +74 -0
  1007. package/prompts/pass-common.md +52 -0
  1008. package/prompts/pass-quality-common.md +22 -0
  1009. package/prompts/round-first.md +874 -0
  1010. package/prompts/round-follow-up.md +991 -0
  1011. package/prompts/round-re-review.md +889 -0
  1012. package/prompts/round-recheck.md +196 -0
  1013. package/prompts/round-staleness.md +856 -0
  1014. package/schemas/review-result.v1.json +391 -0
  1015. package/schemas/scan-result.v1.json +421 -0
  1016. package/schemas/version.v1.json +57 -0
  1017. package/scripts/approve-pr.sh +16 -0
  1018. package/scripts/build-stamp.mjs +67 -0
  1019. package/scripts/commit-verdict.sh +155 -0
  1020. package/scripts/companion-e2e.sh +250 -0
  1021. package/scripts/companion-memory-forward.mjs +1966 -0
  1022. package/scripts/config-fingerprint.sh +28 -0
  1023. package/scripts/deploy-manifest.mjs +141 -0
  1024. package/scripts/deploy.sh +1435 -0
  1025. package/scripts/directory-lock.mjs +193 -0
  1026. package/scripts/heartbeat-fresh.mjs +54 -0
  1027. package/scripts/install-locator.sh +328 -0
  1028. package/scripts/install-wrapper.sh +76 -0
  1029. package/scripts/memory-search-check.mjs +98 -0
  1030. package/scripts/mint-token.sh +212 -0
  1031. package/scripts/openviking-embedder.py +392 -0
  1032. package/scripts/openviking-ov-conf.mjs +161 -0
  1033. package/scripts/openviking-provision.mjs +1122 -0
  1034. package/scripts/openviking-reembed.mjs +1154 -0
  1035. package/scripts/post-review.sh +16 -0
  1036. package/scripts/pr-checks.sh +256 -0
  1037. package/scripts/pr-context.py +174 -0
  1038. package/scripts/pr-context.sh +63 -0
  1039. package/scripts/pr-self.sh +144 -0
  1040. package/scripts/pr-threads.sh +145 -0
  1041. package/scripts/qcr +15 -0
  1042. package/scripts/qcr-host.sh +769 -0
  1043. package/scripts/render-review.sh +24 -0
  1044. package/scripts/reply-thread.sh +16 -0
  1045. package/scripts/required-passes.py +112 -0
  1046. package/scripts/smoke-companion.sh +211 -0
  1047. package/scripts/tui-deploy-runner.mjs +61 -0
  1048. package/scripts/verify-dist.mjs +201 -0
  1049. package/scripts/vm-ghcr-login.sh +188 -0
  1050. package/seeds/agents/pass-baseline.md +61 -0
  1051. package/seeds/agents/pass-contracts.md +30 -0
  1052. package/seeds/agents/pass-data-safety.md +54 -0
  1053. package/seeds/agents/pass-delivery.md +37 -0
  1054. package/seeds/agents/pass-prod-risk.md +24 -0
  1055. package/seeds/agents/pass-security.md +31 -0
  1056. package/seeds/agents/pass-statelessness.md +27 -0
  1057. package/seeds/agents/pass-ui.md +23 -0
  1058. package/seeds/agents/pass-validate.md +50 -0
  1059. package/seeds/prompts/conversation-system.md +117 -0
  1060. package/seeds/prompts/eod-sweep.md +120 -0
  1061. package/seeds/prompts/pass-common.md +52 -0
  1062. package/skills/companion/adjudicate/SKILL.md +247 -0
  1063. package/skills/companion/evidence/SKILL.md +150 -0
  1064. package/skills/companion/land/SKILL.md +200 -0
  1065. package/skills/companion/replay/SKILL.md +138 -0
  1066. package/skills/companion/setup/SKILL.md +167 -0
  1067. package/skills/companion/survey/SKILL.md +160 -0
  1068. package/skills/selfimprove/README.md +34 -0
  1069. package/skills/selfimprove/SKILL.md +127 -0
  1070. package/skills/selfimprove/agents/openai.yaml +2 -0
  1071. package/skills/selfimprove/commands/selfimprove.md +6 -0
  1072. package/skills/selfimprove/references/fix-loop.md +49 -0
  1073. package/skills/selfimprove/references/ledger.md +20 -0
  1074. package/skills/selfimprove/references/setup.md +40 -0
  1075. package/src/anomalies.ts +566 -0
  1076. package/src/answerworker.ts +251 -0
  1077. package/src/appstamp.ts +65 -0
  1078. package/src/archive/admission.ts +3 -0
  1079. package/src/archive/archivedb.ts +548 -0
  1080. package/src/archive/archiver.ts +1963 -0
  1081. package/src/archive/archivewrite.ts +279 -0
  1082. package/src/archive/bundle.ts +333 -0
  1083. package/src/archive/dbmove.ts +911 -0
  1084. package/src/archive/duplicatecounts.ts +98 -0
  1085. package/src/archive/eligibility.ts +181 -0
  1086. package/src/archive/extract.ts +342 -0
  1087. package/src/archive/fsutil.ts +415 -0
  1088. package/src/archive/gzfile.ts +370 -0
  1089. package/src/archive/kinds.ts +21 -0
  1090. package/src/archive/livecheckpoint.ts +217 -0
  1091. package/src/archive/liveretention.ts +180 -0
  1092. package/src/archive/manifest.ts +519 -0
  1093. package/src/archive/overseer.ts +336 -0
  1094. package/src/archive/piglobal.ts +174 -0
  1095. package/src/archive/plan.ts +612 -0
  1096. package/src/archive/prarchive.ts +984 -0
  1097. package/src/archive/prfiles.ts +406 -0
  1098. package/src/archive/report.ts +252 -0
  1099. package/src/archive/restore.ts +330 -0
  1100. package/src/archive/rows.ts +187 -0
  1101. package/src/archive/seal.ts +630 -0
  1102. package/src/archive/status.ts +719 -0
  1103. package/src/archive/storepass.ts +581 -0
  1104. package/src/backfill.ts +362 -0
  1105. package/src/bench/cost.ts +113 -0
  1106. package/src/bench/exec.ts +90 -0
  1107. package/src/bench/values.ts +13 -0
  1108. package/src/botcontrol.ts +176 -0
  1109. package/src/botworker.ts +953 -0
  1110. package/src/capture.ts +321 -0
  1111. package/src/cases.ts +1789 -0
  1112. package/src/channelfs.ts +26 -0
  1113. package/src/checklist.ts +506 -0
  1114. package/src/ciretry.ts +141 -0
  1115. package/src/cli.ts +2271 -0
  1116. package/src/cliops.ts +384 -0
  1117. package/src/clonedir.ts +15 -0
  1118. package/src/commandeffects.ts +57 -0
  1119. package/src/commands.ts +1678 -0
  1120. package/src/companioninbox.ts +239 -0
  1121. package/src/compare.ts +957 -0
  1122. package/src/config.ts +2050 -0
  1123. package/src/configcheck.ts +133 -0
  1124. package/src/coreext.ts +47 -0
  1125. package/src/daemon.ts +1302 -0
  1126. package/src/debuground.ts +927 -0
  1127. package/src/debugroundpi.ts +262 -0
  1128. package/src/diagnostics.ts +14 -0
  1129. package/src/digest.ts +183 -0
  1130. package/src/dispatch.ts +2250 -0
  1131. package/src/engine/answerguard.ts +27 -0
  1132. package/src/engine/botsdk.ts +336 -0
  1133. package/src/engine/caserunner-sdk.ts +423 -0
  1134. package/src/engine/debugpi.ts +235 -0
  1135. package/src/eod.ts +669 -0
  1136. package/src/eodpinwatch.ts +556 -0
  1137. package/src/escalation.ts +1253 -0
  1138. package/src/eventloop.ts +1045 -0
  1139. package/src/execboundary.ts +633 -0
  1140. package/src/feedback.ts +1162 -0
  1141. package/src/findingscheck.ts +298 -0
  1142. package/src/fsdenied.ts +24 -0
  1143. package/src/ghread.ts +242 -0
  1144. package/src/github.ts +1497 -0
  1145. package/src/homes.ts +175 -0
  1146. package/src/hostroute.ts +53 -0
  1147. package/src/improvements.ts +787 -0
  1148. package/src/install.ts +1103 -0
  1149. package/src/installkind.ts +50 -0
  1150. package/src/ipc/capability.ts +333 -0
  1151. package/src/ipc/client.ts +269 -0
  1152. package/src/ipc/control.ts +90 -0
  1153. package/src/ipc/ghreadshim.ts +94 -0
  1154. package/src/ipc/launcher.ts +65 -0
  1155. package/src/ipc/permits.ts +362 -0
  1156. package/src/ipc/postshim.ts +164 -0
  1157. package/src/ipc/protocol.ts +342 -0
  1158. package/src/ipc/server.ts +371 -0
  1159. package/src/ipc/workerspec.ts +218 -0
  1160. package/src/learner/learner.ts +1655 -0
  1161. package/src/learner/miner.ts +1290 -0
  1162. package/src/learner/packet.ts +340 -0
  1163. package/src/learner/receipt.ts +168 -0
  1164. package/src/learner/status.ts +107 -0
  1165. package/src/ledgercheck.ts +157 -0
  1166. package/src/lessonledger.ts +703 -0
  1167. package/src/lessonselect.ts +888 -0
  1168. package/src/lessonshadow.ts +810 -0
  1169. package/src/levers.ts +393 -0
  1170. package/src/live/projection.ts +491 -0
  1171. package/src/live/publish.ts +92 -0
  1172. package/src/live/read.ts +86 -0
  1173. package/src/livecli.ts +336 -0
  1174. package/src/loginhint.ts +56 -0
  1175. package/src/loginrefresh/atomic.ts +76 -0
  1176. package/src/loginrefresh/lock.ts +104 -0
  1177. package/src/loginrefresh/main.ts +59 -0
  1178. package/src/loginrefresh/pirefresh.ts +57 -0
  1179. package/src/loginrefresh/refresher.ts +326 -0
  1180. package/src/loginrefresh/supervisor.ts +273 -0
  1181. package/src/loginrefresh/types.ts +35 -0
  1182. package/src/memory/context.ts +385 -0
  1183. package/src/memory/datafence.ts +9 -0
  1184. package/src/memory/delivery.ts +171 -0
  1185. package/src/memory/eventtags.ts +80 -0
  1186. package/src/memory/health.ts +953 -0
  1187. package/src/memory/import.ts +664 -0
  1188. package/src/memory/layout.ts +261 -0
  1189. package/src/memory/legacy-file.ts +104 -0
  1190. package/src/memory/lessons.ts +447 -0
  1191. package/src/memory/ovsession.ts +601 -0
  1192. package/src/memory/relayout.ts +335 -0
  1193. package/src/memory/roundactivity.ts +209 -0
  1194. package/src/memory/selection.ts +989 -0
  1195. package/src/memory/shadowstatus.ts +125 -0
  1196. package/src/memory/store.ts +275 -0
  1197. package/src/memory/textidentity.ts +8 -0
  1198. package/src/metrics.ts +1056 -0
  1199. package/src/missingconfig.ts +7 -0
  1200. package/src/npmverbs.ts +264 -0
  1201. package/src/onboard/choices.ts +222 -0
  1202. package/src/onboard/engine.ts +518 -0
  1203. package/src/onboard/launch.ts +115 -0
  1204. package/src/onboard/steps/appslug.ts +265 -0
  1205. package/src/onboard/steps/bringup.ts +482 -0
  1206. package/src/onboard/steps/compose.ts +459 -0
  1207. package/src/onboard/steps/configdelivery.ts +58 -0
  1208. package/src/onboard/steps/configfill.ts +1352 -0
  1209. package/src/onboard/steps/deploysh.ts +105 -0
  1210. package/src/onboard/steps/embeddings.ts +550 -0
  1211. package/src/onboard/steps/escalation.ts +640 -0
  1212. package/src/onboard/steps/githubapp.ts +1414 -0
  1213. package/src/onboard/steps/golive.ts +577 -0
  1214. package/src/onboard/steps/index.ts +71 -0
  1215. package/src/onboard/steps/ingress.ts +907 -0
  1216. package/src/onboard/steps/inventory.ts +526 -0
  1217. package/src/onboard/steps/llmauth.ts +1095 -0
  1218. package/src/onboard/steps/memory.ts +1290 -0
  1219. package/src/onboard/steps/memorykeys.ts +85 -0
  1220. package/src/onboard/steps/preflight.ts +191 -0
  1221. package/src/onboard/steps/proof.ts +624 -0
  1222. package/src/onboard/steps/ssh.ts +377 -0
  1223. package/src/onboard/steps/topology.ts +337 -0
  1224. package/src/onboard/steps/transport.ts +665 -0
  1225. package/src/onboard/steps/verifyup.ts +448 -0
  1226. package/src/onboard/steps/vmdeploy.ts +442 -0
  1227. package/src/onboard/toml.ts +50 -0
  1228. package/src/opencli.ts +591 -0
  1229. package/src/openpr.ts +568 -0
  1230. package/src/openviking/client.ts +1131 -0
  1231. package/src/openviking/context.ts +263 -0
  1232. package/src/openviking/embedder.ts +516 -0
  1233. package/src/openviking/gapsink.ts +63 -0
  1234. package/src/openviking/health.ts +83 -0
  1235. package/src/openviking/index.ts +119 -0
  1236. package/src/openviking/lessonfind.ts +92 -0
  1237. package/src/openviking/probe.ts +129 -0
  1238. package/src/openviking/proc.ts +110 -0
  1239. package/src/openviking/provisionreport.ts +160 -0
  1240. package/src/openviking/reembed.ts +412 -0
  1241. package/src/openviking/relayout.ts +177 -0
  1242. package/src/openviking/sidecar-main.ts +38 -0
  1243. package/src/openviking/sidecar.ts +106 -0
  1244. package/src/openviking/store.ts +2025 -0
  1245. package/src/openviking/supervisor.ts +474 -0
  1246. package/src/openviking/types.ts +174 -0
  1247. package/src/operator-recovery.ts +237 -0
  1248. package/src/operatormessage.ts +52 -0
  1249. package/src/outcomes.ts +1670 -0
  1250. package/src/piauthcopy.ts +12 -0
  1251. package/src/picli.ts +49 -0
  1252. package/src/picwd.ts +25 -0
  1253. package/src/pilogin.ts +37 -0
  1254. package/src/pingpong.ts +1339 -0
  1255. package/src/posting-journal.ts +549 -0
  1256. package/src/posting-outbox.ts +802 -0
  1257. package/src/posting-receipt.ts +324 -0
  1258. package/src/posting.ts +603 -0
  1259. package/src/postops.ts +832 -0
  1260. package/src/pr-activity.ts +20 -0
  1261. package/src/pr-author.ts +6 -0
  1262. package/src/pr-conversation.ts +432 -0
  1263. package/src/pr-session-lifecycle.ts +631 -0
  1264. package/src/preflight.ts +391 -0
  1265. package/src/priorresolve.ts +343 -0
  1266. package/src/qcrbin.ts +186 -0
  1267. package/src/recheck.ts +1775 -0
  1268. package/src/reconcile.ts +1030 -0
  1269. package/src/recordedfindings.ts +27 -0
  1270. package/src/regression.ts +833 -0
  1271. package/src/replay.ts +816 -0
  1272. package/src/requiredpasses.ts +148 -0
  1273. package/src/resumecheck.ts +246 -0
  1274. package/src/reviewcli.ts +787 -0
  1275. package/src/reviewcontext.ts +35 -0
  1276. package/src/reviewdetach.ts +734 -0
  1277. package/src/reviewhistory.ts +427 -0
  1278. package/src/reviewhome.ts +36 -0
  1279. package/src/reviewmemory.ts +133 -0
  1280. package/src/reviewrendercli.ts +139 -0
  1281. package/src/round-work.ts +42 -0
  1282. package/src/runhost.ts +108 -0
  1283. package/src/scanauth.ts +36 -0
  1284. package/src/scanbox.ts +1081 -0
  1285. package/src/scancli.ts +1296 -0
  1286. package/src/scandriverlog.ts +32 -0
  1287. package/src/session-bridge.ts +139 -0
  1288. package/src/session-command-payload.ts +35 -0
  1289. package/src/session-command-supervisor.ts +1142 -0
  1290. package/src/sessionjsonl.ts +88 -0
  1291. package/src/sessions.ts +659 -0
  1292. package/src/setup/adapters.ts +335 -0
  1293. package/src/setup/baselines.ts +110 -0
  1294. package/src/setup/caserunner.ts +611 -0
  1295. package/src/setup/configcommit.ts +443 -0
  1296. package/src/setup/engine.ts +1001 -0
  1297. package/src/setup/facts.ts +627 -0
  1298. package/src/setup/findings.ts +1264 -0
  1299. package/src/setup/history.ts +443 -0
  1300. package/src/setup/interview.ts +243 -0
  1301. package/src/setup/ledger.ts +324 -0
  1302. package/src/setup/pins.ts +66 -0
  1303. package/src/setup/portrait.ts +635 -0
  1304. package/src/setup/presentation.ts +18 -0
  1305. package/src/setup/profile.ts +508 -0
  1306. package/src/setup/replay.ts +370 -0
  1307. package/src/setup/replaybuild.ts +392 -0
  1308. package/src/setup/replayrun.ts +1096 -0
  1309. package/src/setup/replaysource.ts +198 -0
  1310. package/src/setup/retune.ts +415 -0
  1311. package/src/setup/retunelog.ts +95 -0
  1312. package/src/setup/scan.ts +1033 -0
  1313. package/src/setup/scanbuild.ts +168 -0
  1314. package/src/setup/scanfacts.ts +107 -0
  1315. package/src/setup/scanlease.ts +297 -0
  1316. package/src/setup/scanreport.ts +373 -0
  1317. package/src/setup/scanrun.ts +2268 -0
  1318. package/src/setup/scantree.ts +259 -0
  1319. package/src/setup/stage2.ts +926 -0
  1320. package/src/setup/stage2request.ts +347 -0
  1321. package/src/setup/survey.ts +458 -0
  1322. package/src/setup/surveyrun.ts +758 -0
  1323. package/src/setup/textsniff.ts +95 -0
  1324. package/src/setup/trackersink.ts +433 -0
  1325. package/src/setup/triage.ts +768 -0
  1326. package/src/setup/tuning.ts +766 -0
  1327. package/src/setup/unitcut.ts +523 -0
  1328. package/src/singleflight.ts +86 -0
  1329. package/src/slack/announce.ts +911 -0
  1330. package/src/slack/api.ts +303 -0
  1331. package/src/slack/messages.ts +288 -0
  1332. package/src/slack/origin.ts +599 -0
  1333. package/src/slack/policy.ts +354 -0
  1334. package/src/slack/reply.ts +201 -0
  1335. package/src/slack/router.ts +285 -0
  1336. package/src/slack/socket.ts +384 -0
  1337. package/src/slack/types.ts +108 -0
  1338. package/src/sqlitehint.ts +32 -0
  1339. package/src/ssh-observation.ts +30 -0
  1340. package/src/state.ts +8243 -0
  1341. package/src/statemachine.ts +369 -0
  1342. package/src/stubs.ts +68 -0
  1343. package/src/takeover.ts +634 -0
  1344. package/src/takeoverpi.ts +293 -0
  1345. package/src/terminal-failure.ts +41 -0
  1346. package/src/thread-resolution-refusal.ts +2 -0
  1347. package/src/tmux.ts +153 -0
  1348. package/src/trackerid.ts +41 -0
  1349. package/src/transcriptindex.ts +665 -0
  1350. package/src/tui/actions.ts +484 -0
  1351. package/src/tui/alerts.ts +666 -0
  1352. package/src/tui/app.ts +1979 -0
  1353. package/src/tui/archiveops.ts +192 -0
  1354. package/src/tui/autofile.ts +233 -0
  1355. package/src/tui/board.ts +1432 -0
  1356. package/src/tui/candidates.ts +889 -0
  1357. package/src/tui/cellwidth.ts +148 -0
  1358. package/src/tui/changelog-store.ts +503 -0
  1359. package/src/tui/chord.ts +25 -0
  1360. package/src/tui/clipboard.ts +92 -0
  1361. package/src/tui/cmdbar.ts +296 -0
  1362. package/src/tui/companion.ts +1427 -0
  1363. package/src/tui/companionplan.ts +247 -0
  1364. package/src/tui/companiontab.ts +83 -0
  1365. package/src/tui/confighome.ts +155 -0
  1366. package/src/tui/connect.ts +68 -0
  1367. package/src/tui/deploy-ceremony.ts +104 -0
  1368. package/src/tui/deploy-drain.ts +129 -0
  1369. package/src/tui/deploy-process.ts +265 -0
  1370. package/src/tui/deploytab.ts +1523 -0
  1371. package/src/tui/design.ts +125 -0
  1372. package/src/tui/digesttab.ts +214 -0
  1373. package/src/tui/emptystate.ts +39 -0
  1374. package/src/tui/evidence.ts +999 -0
  1375. package/src/tui/feed.ts +604 -0
  1376. package/src/tui/feedbacktab.ts +1077 -0
  1377. package/src/tui/filters.ts +90 -0
  1378. package/src/tui/ghread.ts +321 -0
  1379. package/src/tui/historypull.ts +81 -0
  1380. package/src/tui/inboxpull.ts +236 -0
  1381. package/src/tui/index.ts +1565 -0
  1382. package/src/tui/issuesink.ts +238 -0
  1383. package/src/tui/jsonfile.ts +42 -0
  1384. package/src/tui/keys.ts +109 -0
  1385. package/src/tui/layout.ts +108 -0
  1386. package/src/tui/learning.ts +627 -0
  1387. package/src/tui/lessonswitch.ts +118 -0
  1388. package/src/tui/livecontrol.ts +228 -0
  1389. package/src/tui/memforward.ts +443 -0
  1390. package/src/tui/memincident.ts +363 -0
  1391. package/src/tui/memory.ts +620 -0
  1392. package/src/tui/memtab.ts +606 -0
  1393. package/src/tui/metricstab.ts +1177 -0
  1394. package/src/tui/model.ts +131 -0
  1395. package/src/tui/mouse.ts +98 -0
  1396. package/src/tui/msg.ts +268 -0
  1397. package/src/tui/nativecompanion.ts +417 -0
  1398. package/src/tui/openurl.ts +21 -0
  1399. package/src/tui/paint.ts +511 -0
  1400. package/src/tui/panes.ts +98 -0
  1401. package/src/tui/pipane.ts +219 -0
  1402. package/src/tui/poll-hooks.ts +12 -0
  1403. package/src/tui/poll.ts +147 -0
  1404. package/src/tui/proposals.ts +838 -0
  1405. package/src/tui/prpause.ts +150 -0
  1406. package/src/tui/reload.ts +436 -0
  1407. package/src/tui/remote.ts +413 -0
  1408. package/src/tui/rollover.ts +969 -0
  1409. package/src/tui/scantab.ts +1290 -0
  1410. package/src/tui/setupentry.ts +93 -0
  1411. package/src/tui/sidebar.ts +749 -0
  1412. package/src/tui/sshtriage.ts +510 -0
  1413. package/src/tui/startup.ts +231 -0
  1414. package/src/tui/tabs.ts +45 -0
  1415. package/src/tui/termmirror.ts +224 -0
  1416. package/src/tui/theme.ts +95 -0
  1417. package/src/tui/ticketstab.ts +310 -0
  1418. package/src/tui/tmuxsocket.ts +129 -0
  1419. package/src/tui/trackertickets.ts +84 -0
  1420. package/src/tui/unwedge.ts +286 -0
  1421. package/src/tui/viewer.ts +575 -0
  1422. package/src/tui/viewerinput.ts +173 -0
  1423. package/src/updatenotice.ts +178 -0
  1424. package/src/usage.ts +1154 -0
  1425. package/src/verdicts.ts +4689 -0
  1426. package/src/version.ts +80 -0
  1427. package/src/wakes.ts +476 -0
  1428. package/src/webhook.ts +942 -0
  1429. package/src/webhookheal.ts +276 -0
  1430. package/src/wire.ts +2153 -0
  1431. package/src/worker-session-host.ts +602 -0
  1432. package/src/workerbreaker.ts +156 -0
  1433. package/src/workercontrol.ts +104 -0
  1434. package/src/workerlease.ts +441 -0
  1435. package/src/workerliveness.ts +103 -0
  1436. package/src/workerlog.ts +79 -0
  1437. package/src/workerruntime.ts +30 -0
  1438. package/src/workersupervisor.ts +1538 -0
  1439. package/src/workerusage.ts +42 -0
  1440. package/src/workerworld.ts +427 -0
  1441. package/src/worktrees.ts +1138 -0
  1442. package/tests/fixtures/regression-cases.json +795 -0
  1443. package/tests/fixtures/seed-manifest.json +19 -0
@@ -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