@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,1398 @@
1
+ // Post-approval delta recheck.
2
+ //
3
+ // Once a PR is approved, a later change can still regress it. A staleness
4
+ // check that re-reviews on every commit after the approval spends most of its
5
+ // output concluding nothing changed, so the delta is classified first, and
6
+ // the standing verdict is UPHELD BY DEFAULT.
7
+ //
8
+ // Shape of the mechanism:
9
+ //
10
+ // trigger ──► planRecheck() ──► no-op (uphold, nothing posted, nothing spent)
11
+ // └─► one nudge to the PR's OWN sticky session
12
+ //
13
+ // Four triggers: a push after an approve, CI completing at the head, the base
14
+ // branch advancing, and an author reply. The author reply is deliberately a
15
+ // no-op HERE: `src/pingpong.ts` already owns that path (≤3 exchanges, reply +
16
+ // optional thread resolve) and a second reply handler would duplicate it.
17
+ // With no code delta there is nothing to recheck; the value is the reply,
18
+ // which is already owned.
19
+ //
20
+ // Invariants this module keeps structurally, not by prompting:
21
+ //
22
+ // 1. **Only APPROVE → REQUEST_CHANGES.** A recheck is planned only when the
23
+ // standing verdict is APPROVE, and the decision file accepts exactly two
24
+ // outcomes: `uphold` and `request_changes`. Anything else — including a
25
+ // literal "APPROVE" or "NEEDS_HUMAN" — is coerced to `uphold` and logged.
26
+ // The path can therefore never re-approve and never park.
27
+ // 2. **A trivial or empty delta costs one classification and a no-op.** That
28
+ // is what makes the broad trigger set affordable. A base advance touching
29
+ // no PR-owned file is the loudest instance: every file it brings is a
30
+ // `base-merge` file, so it resolves to `trivial-delta`.
31
+ // 3. **An unchanged head with no new evidence is a no-op** — the same rule
32
+ // the gate's guard applies (`guards.hasNewEvidence`), read here off the
33
+ // same single `reviewed_heads` mark. There is no second copy.
34
+ // 4. **Single shepherd.** The recheck is a nudge to the PR's existing `pr-<n>`
35
+ // session — no new session, no lens fan-out, no round. It goes through
36
+ // `wakeOrHold`, so a nudge arriving mid-round is held and flushed at round
37
+ // end like any other wake.
38
+ // 5. **No GitHub write lives here.** The module reads (compare, files) and
39
+ // writes nothing; the session posts its review through the gated scripts.
40
+ // The MutationGate keeps its monopoly by this module having no mutation.
41
+ import { existsSync, mkdirSync, readdirSync, renameSync, watch } from "node:fs";
42
+ import { join } from "node:path";
43
+ import { readFindingsRecord } from "@quickchatai/pi-quickreview/src/findingsrecord.js";
44
+ import { isMigrationPath } from "@quickchatai/pi-quickreview/src/heuristics.js";
45
+ import { parseHunkHeader } from "@quickchatai/pi-quickreview/src/hunkheader.js";
46
+ import { applyEvent } from "./statemachine.js";
47
+ import { recheckHead12, recheckKeyPrefix } from "@quickchatai/pi-quickreview/src/findingkey.js";
48
+ import { recheckWakeReason } from "./state.js";
49
+ import { normalizeVerdict, recheckPromptMarker, stampPrompt, } from "@quickchatai/pi-quickreview/src/types.js";
50
+ import { standingVerdict } from "@quickchatai/pi-quickreview/src/standing-review.js";
51
+ import { wakeOrHold, withRepoFacts } from "./wakes.js";
52
+ import { prWorktreeRel } from "./worktrees.js";
53
+ import { readSettledJson, resolvePartialWritePolicy, } from "@quickchatai/pi-quickreview/src/settledread.js";
54
+ /** The delta of a range GitHub could not list: no files, and significant by
55
+ * construction — nothing is known about what it holds. */
56
+ export function unreadDelta() {
57
+ return Object.assign([], { unread: true });
58
+ }
59
+ /** Dependency-manifest lockfiles: generated, never reviewed on their content. */
60
+ const LOCKFILES = new Set([
61
+ "package-lock.json",
62
+ "npm-shrinkwrap.json",
63
+ "yarn.lock",
64
+ "pnpm-lock.yaml",
65
+ "poetry.lock",
66
+ "uv.lock",
67
+ "Pipfile.lock",
68
+ "Cargo.lock",
69
+ "Gemfile.lock",
70
+ "composer.lock",
71
+ "go.sum",
72
+ ]);
73
+ /** Manifests whose version field moves on a release bump. */
74
+ const VERSION_MANIFESTS = new Set([
75
+ "package.json",
76
+ "pyproject.toml",
77
+ "Cargo.toml",
78
+ "setup.cfg",
79
+ "VERSION",
80
+ "version.txt",
81
+ ]);
82
+ function baseName(path) {
83
+ return path.split("/").pop() ?? path;
84
+ }
85
+ /**
86
+ * Split a migration filename into its number and the rest: `0042_add_foo.py`
87
+ * → `{ num: "0042", rest: "add_foo.py" }`. Null when the file is not a
88
+ * numbered migration.
89
+ */
90
+ export function migrationName(path) {
91
+ const m = /^(\d+)[_-](.+)$/.exec(baseName(path));
92
+ if (!m || !isMigrationPath(path))
93
+ return null;
94
+ return { num: m[1], rest: m[2] };
95
+ }
96
+ /** A rename that only moves a migration's number: `0042_x.py` → `0043_x.py`. */
97
+ export function isMigrationRenumber(from, to) {
98
+ const a = migrationName(from);
99
+ const b = migrationName(to);
100
+ return a !== null && b !== null && a.rest === b.rest && a.num !== b.num;
101
+ }
102
+ /**
103
+ * The hunks of a unified diff. File headers (`---`/`+++`) are recognised only
104
+ * before the first hunk — inside one, `--- x` is a removed line that reads
105
+ * `-- x`. A patch with no `@@` line is one hunk.
106
+ */
107
+ function parseHunks(patch) {
108
+ const hunks = [];
109
+ let cur = null;
110
+ for (const line of patch.split("\n")) {
111
+ if (line.startsWith("@@")) {
112
+ cur = {
113
+ section: /^@@[^@]*@@ ?(.*)$/.exec(line)?.[1] ?? "",
114
+ oldStart: parseHunkHeader(line)?.old_start ?? null,
115
+ lines: [],
116
+ };
117
+ hunks.push(cur);
118
+ continue;
119
+ }
120
+ if (cur === null && (line.startsWith("+++") || line.startsWith("---")))
121
+ continue;
122
+ const kind = line.charAt(0);
123
+ if (kind !== " " && kind !== "+" && kind !== "-")
124
+ continue;
125
+ if (cur === null) {
126
+ cur = { section: "", oldStart: null, lines: [] };
127
+ hunks.push(cur);
128
+ }
129
+ cur.lines.push({ kind, text: line.slice(1) });
130
+ }
131
+ return hunks;
132
+ }
133
+ function changeBlocks(hunk) {
134
+ const blocks = [];
135
+ let cur = null;
136
+ for (const line of hunk.lines) {
137
+ if (line.kind === " ") {
138
+ cur = null;
139
+ continue;
140
+ }
141
+ if (cur === null) {
142
+ cur = { added: [], removed: [] };
143
+ blocks.push(cur);
144
+ }
145
+ (line.kind === "+" ? cur.added : cur.removed).push(line.text);
146
+ }
147
+ return blocks;
148
+ }
149
+ /** Every line of one kind in a patch, prefix kept — a whole added or removed
150
+ * file's text. */
151
+ function changedLines(patch, kind) {
152
+ return parseHunks(patch).flatMap((h) => h.lines.filter((l) => l.kind === kind).map((l) => `${kind}${l.text}`));
153
+ }
154
+ /** A quoted literal on one line. Whitespace inside one is content, never
155
+ * formatting: `"a b"` → `"ab"` changes the program. */
156
+ const QUOTED = /("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`)/;
157
+ /** Drop whitespace OUTSIDE quoted literals; the literals stay verbatim. */
158
+ function stripOutsideQuotes(line) {
159
+ return line
160
+ .split(QUOTED)
161
+ .map((part, i) => (i % 2 === 1 ? part : part.replace(/\s+/g, "")))
162
+ .join("");
163
+ }
164
+ /** Languages whose leading whitespace is syntax — a dedent in Python moves a
165
+ * line out of its block; in YAML it moves a key to another mapping; in the
166
+ * indented style-sheet syntaxes (Sass, Stylus) it moves a rule out of the
167
+ * selector it was nested under. */
168
+ function indentSignificant(path) {
169
+ if (!path)
170
+ return false;
171
+ const base = baseName(path);
172
+ return /\.(py|pyi|ya?ml|sass|styl)$/i.test(base) || base === "Makefile" || /\.mk$/i.test(base);
173
+ }
174
+ /** A shell script's basename (a Dockerfile counts). */
175
+ function isShellFile(base) {
176
+ return /\.(sh|bash|zsh|ksh)$/i.test(base) || base === "Dockerfile";
177
+ }
178
+ /** A Ruby file's basename (a Gemfile counts). */
179
+ function isRubyFile(base) {
180
+ return /\.rb$/i.test(base) || base === "Gemfile";
181
+ }
182
+ /** Languages where a line break ends a command or statement — in a shell
183
+ * script `a\n-b` runs two commands where `a -b` runs one; Ruby and Go end a
184
+ * statement at most line breaks. */
185
+ function lineTerminated(path) {
186
+ if (!path)
187
+ return false;
188
+ const base = baseName(path);
189
+ return isShellFile(base) || isRubyFile(base) || /\.go$/i.test(base);
190
+ }
191
+ /**
192
+ * The whitespace-insensitive form of a side of a patch. Whitespace between
193
+ * tokens is dropped (quoted literals kept verbatim) and the lines are
194
+ * concatenated, so a re-indent or a re-flow compares equal. For an
195
+ * indentation-significant file each non-blank line keeps its leading
196
+ * whitespace and stays its own line — a re-flow or re-indent there is not
197
+ * provably meaningless, so it is not formatting. For a file whose line
198
+ * breaks end statements each non-blank line stays its own line, so a re-flow
199
+ * there is not formatting while a re-indent still is.
200
+ */
201
+ function squash(lines, path) {
202
+ if (lineTerminated(path))
203
+ return lines
204
+ .filter((l) => l.trim() !== "")
205
+ .map(stripOutsideQuotes)
206
+ .join("\n");
207
+ if (!indentSignificant(path))
208
+ return lines.map(stripOutsideQuotes).join("");
209
+ return lines
210
+ .filter((l) => l.trim() !== "")
211
+ .map((l) => (/^\s*/.exec(l)?.[0] ?? "") + stripOutsideQuotes(l))
212
+ .join("\n");
213
+ }
214
+ /**
215
+ * The slash-span form of a side of a patch: on each line, the raw text from
216
+ * its first `/` to its last is kept verbatim (delimited), and whitespace
217
+ * elsewhere is dropped. A regex literal never crosses a line, so it always
218
+ * lies inside its line's span; whitespace inside one is content (`/a b/` →
219
+ * `/ab/` changes the program). Telling a regex from a division, a comment or
220
+ * a markup closing tag is not attempted: any whitespace change between the
221
+ * first and last `/` of a line is held significant, so this form can only
222
+ * make a delta less trivial, never more.
223
+ */
224
+ function slashSpans(lines) {
225
+ return lines
226
+ .map((l) => {
227
+ const i = l.indexOf("/");
228
+ const j = l.lastIndexOf("/");
229
+ if (i === j)
230
+ return l.replace(/\s+/g, "");
231
+ return `${l.slice(0, i).replace(/\s+/g, "")}\0${l.slice(i, j + 1)}\0${l.slice(j + 1).replace(/\s+/g, "")}`;
232
+ })
233
+ .join("");
234
+ }
235
+ /** The class of a character for token joining: two characters of one class
236
+ * that whitespace separates would read as ONE token once it is dropped —
237
+ * `const n` → `constn`, `a + ++b` → `a+++b` (the same as `a++ + b`). Null for
238
+ * a character no neighbour joins with (brackets, separators, quotes). */
239
+ function joinClass(ch) {
240
+ if (/[\w$]/.test(ch) || ch.charCodeAt(0) > 127)
241
+ return "word";
242
+ if (/[+\-*/%&|^!~<>=?:.@#]/.test(ch))
243
+ return "op";
244
+ return null;
245
+ }
246
+ /** A word after which a line break ends the statement (JavaScript's
247
+ * restricted productions: `return\n(x)` returns undefined). Matched against
248
+ * the last nine characters, one more than the longest word. */
249
+ const RESTRICTED_WORD_END = /(?:^|[^\w$])(?:return|throw|break|continue|yield|async)$/;
250
+ /**
251
+ * The token-seam form of a side of a patch: whitespace outside quoted
252
+ * literals is dropped EXCEPT where it separates two characters of one join
253
+ * class, where it becomes a single space — or a line break, when the gap
254
+ * holds one. Dropping that whitespace would merge two tokens into one
255
+ * (`a + ++b` and `a++ + b` both squash to `a+++b`), so whether it is there is
256
+ * content. A line break is also kept wherever it can end a statement however
257
+ * the tokens join: after a restricted word (`return\n(x)`) and beside `++` or
258
+ * `--` (`a++\nb` against `a\n++b`). Every line break kept is a `\n` the form
259
+ * holds nowhere else, so compared beside `squash` this form can only make a
260
+ * delta less trivial, never more.
261
+ */
262
+ function tokenSeams(lines) {
263
+ let out = "";
264
+ let gap = false;
265
+ let lineBreak = false;
266
+ // A line comment runs to the end of its line, so the break after a line
267
+ // that holds a comment marker outside a literal is content.
268
+ const LINE_COMMENT = /\/\/|#|--/;
269
+ const push = (text) => {
270
+ if (text === "")
271
+ return;
272
+ const prev = out.slice(-1);
273
+ if (gap && prev !== "") {
274
+ const cls = joinClass(prev);
275
+ const seam = cls !== null && cls === joinClass(text.charAt(0));
276
+ const ends = lineBreak &&
277
+ (seam ||
278
+ RESTRICTED_WORD_END.test(out.slice(-9)) ||
279
+ /(?:\+\+|--)$/.test(out.slice(-2)) ||
280
+ /^(?:\+\+|--)/.test(text));
281
+ // A seam keeps its space and gains the break, so deleting every "\n"
282
+ // gives back the form without breaks exactly — before and after
283
+ // `digitsOnly` masks it — and the break form only ever refines it.
284
+ if (seam)
285
+ out += " ";
286
+ if (ends)
287
+ out += "\n";
288
+ }
289
+ out += text;
290
+ gap = false;
291
+ lineBreak = false;
292
+ };
293
+ for (const line of lines) {
294
+ gap = true;
295
+ lineBreak = true;
296
+ let commented = false;
297
+ line.split(QUOTED).forEach((part, i) => {
298
+ if (i % 2 === 1)
299
+ return push(part);
300
+ if (LINE_COMMENT.test(part))
301
+ commented = true;
302
+ part.split(/(\s+)/).forEach((chunk, k) => {
303
+ if (k % 2 === 1)
304
+ gap = true;
305
+ else
306
+ push(chunk);
307
+ });
308
+ });
309
+ if (commented && out !== "")
310
+ out += "\n";
311
+ }
312
+ return out;
313
+ }
314
+ /** Style sheets: whitespace between two selector tokens is a combinator
315
+ * (`a .child` is a descendant, `a.child` a compound), so no whitespace that
316
+ * separates two tokens there may vanish or appear, whatever their classes. */
317
+ function selectorWhitespace(path) {
318
+ return path !== undefined && /\.(css|scss|sass|less|styl)$/i.test(baseName(path));
319
+ }
320
+ /** Every whitespace run inside a line collapsed to one space and the lines
321
+ * joined by one: a re-indent and a re-flow compare equal, a gap that opens or
322
+ * closes between two tokens does not. */
323
+ function collapsed(lines) {
324
+ return lines
325
+ .map((l) => l.trim().replace(/\s+/g, " "))
326
+ .filter((l) => l !== "")
327
+ .join(" ");
328
+ }
329
+ /** A heredoc opener: `<<EOF`, `<<-'EOF'`, `<< "EOF"` (shell) and `<<~SQL`
330
+ * (Ruby, no space — `a << b` appends). */
331
+ const SHELL_HEREDOC = /<<-?\s*(['"]?)[A-Za-z_]\w*\1/;
332
+ const RUBY_HEREDOC = /<<[-~]?(['"]?)[A-Za-z_]\w*\1/;
333
+ /** A YAML block scalar indicator closing its line (`key: |`, `- >-`). */
334
+ const YAML_BLOCK_SCALAR = /(?:^|:|-)\s*[|>][-+1-9]{0,2}\s*(?:#.*)?$/;
335
+ /**
336
+ * May this hunk's changed lines sit inside a literal that spans lines — a
337
+ * template literal, a triple-quoted string, a heredoc, a YAML block scalar?
338
+ * Whitespace inside one is content, and the per-line `QUOTED` rule cannot see
339
+ * a literal that opens or closes on another line. Judged from what the hunk
340
+ * shows (its section text, its context and its changed lines): an unbalanced
341
+ * backtick, any triple quote, or the language's own opener. A literal whose
342
+ * delimiters both lie outside the hunk is beyond what the patch shows.
343
+ */
344
+ function mayTouchMultilineLiteral(hunk, path) {
345
+ const base = path === undefined ? "" : baseName(path);
346
+ const shell = isShellFile(base);
347
+ const ruby = isRubyFile(base);
348
+ const yaml = /\.ya?ml$/i.test(base);
349
+ return [hunk.section, ...hunk.lines.map((l) => l.text)].some((text) => text.includes('"""') ||
350
+ text.includes("'''") ||
351
+ (text.replace(/\\./g, "").split("`").length - 1) % 2 === 1 ||
352
+ (shell && SHELL_HEREDOC.test(text)) ||
353
+ (ruby && RUBY_HEREDOC.test(text)) ||
354
+ (yaml && YAML_BLOCK_SCALAR.test(text)));
355
+ }
356
+ /**
357
+ * The forms a side of a change block is compared in, every one of which must
358
+ * match for the change to be whitespace-only: `squash` (insignificant
359
+ * whitespace dropped — never inside a quoted literal, never leading
360
+ * indentation of a whitespace-significant language), `slashSpans` (no line's
361
+ * whitespace changed between its first and last `/` — a regex literal's
362
+ * whitespace is content), `tokenSeams` (no whitespace that separates two
363
+ * tokens appeared or vanished) and, in a style sheet, `collapsed`. The one
364
+ * list both `formattingOnly` and `digitsOnly` compare.
365
+ */
366
+ function whitespaceForms(lines, path) {
367
+ const forms = [squash(lines, path), slashSpans(lines), tokenSeams(lines)];
368
+ if (selectorWhitespace(path))
369
+ forms.push(collapsed(lines));
370
+ return forms;
371
+ }
372
+ /** The two sides of a block agree in every `whitespaceForms` form, each
373
+ * compared after `mask` (identity unless a caller masks something out). */
374
+ function sameForms(block, path, mask = (form) => form) {
375
+ const removed = whitespaceForms(block.removed, path);
376
+ return whitespaceForms(block.added, path).every((form, i) => mask(form) === mask(removed[i]));
377
+ }
378
+ /**
379
+ * Whitespace-only change, judged change block by change block (see
380
+ * `changeBlocks`): within a block, concatenation (not a per-line set) is the
381
+ * comparison, so a re-indent or a re-flow passes while a reordering of real
382
+ * lines does not — and a line moved across unchanged code is two blocks that
383
+ * each differ. A hunk that may touch a multi-line literal is never
384
+ * formatting.
385
+ */
386
+ export function formattingOnly(patch, path) {
387
+ const hunks = parseHunks(patch);
388
+ const blocks = hunks.flatMap(changeBlocks);
389
+ if (blocks.length === 0)
390
+ return false;
391
+ if (hunks.some((h) => mayTouchMultilineLiteral(h, path)))
392
+ return false;
393
+ return blocks.every((b) => sameForms(b, path));
394
+ }
395
+ /** A migration number: a digit run that starts a token and is followed by `_`
396
+ * and a letter — the `NNNN_name` shape a migration filename or `dependencies`
397
+ * entry carries. `1_000` (a numeric literal) is not one. */
398
+ const MIGRATION_NUMBER = /(?<![A-Za-z0-9_])\d+(?=_[A-Za-z])/g;
399
+ /** Same text once every MIGRATION-NUMBER run is masked — the renumber's
400
+ * content edit (`dependencies = [("app", "0042_x")]` → `"0043_x"`) and nothing
401
+ * else. Only the migration-number shape is masked (`MIGRATION_NUMBER`): a
402
+ * semantic numeric change inside a renamed migration — a batch size, a
403
+ * timeout, a default, `1_000` → `2_000` — does NOT mask away, so it
404
+ * classifies significant. Judged block by block like `formattingOnly`; in a
405
+ * hunk that may touch a multi-line literal the raw lines are compared, so only
406
+ * the numbers may differ there. */
407
+ export function digitsOnly(patch, path) {
408
+ const masked = (text) => text.replace(MIGRATION_NUMBER, "#");
409
+ return parseHunks(patch).every((hunk) => {
410
+ const literal = mayTouchMultilineLiteral(hunk, path);
411
+ return changeBlocks(hunk).every((block) => literal
412
+ ? masked(block.added.join("\n")) === masked(block.removed.join("\n"))
413
+ : sameForms(block, path, masked));
414
+ });
415
+ }
416
+ /** One release-version line and nothing else on it: `"version": "1.2.3",`
417
+ * (package.json) or `version = "1.2.3"` (pyproject/Cargo). The value must
418
+ * look like a version — a digit first, no spaces — so the npm lifecycle
419
+ * script `"version": "npm run build"` does not pass, and a line that merely
420
+ * MENTIONS version (`serde = { version = "1", features = [...] }`, an inline
421
+ * dependency with a `git` source) is not the key itself. */
422
+ const RELEASE_VERSION_LINE = /^\s*(?:"version"\s*:\s*"v?\d[^"\s]*"\s*,?|version\s*=\s*(?:"v?\d[^"\s]*"|'v?\d[^'\s]*'))\s*$/i;
423
+ /** A TOML table header line, its name with surrounding space dropped:
424
+ * `[package]` → `package`; an array table (`[[bin]]`) keeps its brackets. */
425
+ const TOML_TABLE = /^\s*(\[?\[[^\]]+\]\]?)\s*(?:#.*)?$/;
426
+ /** The TOML tables whose `version` key is the package's own release. */
427
+ const RELEASE_TABLES = new Set(["package", "workspace.package", "project", "tool.poetry"]);
428
+ function tomlTable(text) {
429
+ const m = TOML_TABLE.exec(text);
430
+ if (!m)
431
+ return null;
432
+ const header = m[1];
433
+ return header.startsWith("[[") ? header : header.slice(1, -1).replace(/\s+/g, "");
434
+ }
435
+ /** How far a line of JSON opens (+) or closes (−) objects and arrays, its
436
+ * quoted strings left out. */
437
+ function jsonDepthDelta(text) {
438
+ let delta = 0;
439
+ text.split(QUOTED).forEach((part, i) => {
440
+ if (i % 2 === 1)
441
+ return;
442
+ for (const ch of part) {
443
+ if (ch === "{" || ch === "[")
444
+ delta++;
445
+ else if (ch === "}" || ch === "]")
446
+ delta--;
447
+ }
448
+ });
449
+ return delta;
450
+ }
451
+ /** Every changed line is the release-version key itself — the release-bump
452
+ * shape. The key must also be the package's own, proven from what the hunk
453
+ * shows: in a TOML manifest it sits in a release table (`RELEASE_TABLES`) —
454
+ * `version = "2"` under `[dependencies.serde]` is a dependency requirement —
455
+ * and in a JSON manifest it is a key of the top-level object, which the hunk
456
+ * proves only when it starts at the file's first line (`"version"` under
457
+ * `"config"` is configuration, not the release). A line whose place the patch
458
+ * does not show is not proven to be the release. */
459
+ export function versionOnly(patch, path) {
460
+ if (path !== undefined && ["VERSION", "version.txt"].includes(baseName(path))) {
461
+ const lines = parseHunks(patch).flatMap((h) => h.lines);
462
+ const version = /^v?\d+(?:\.\d+)*(?:[-+][0-9A-Za-z.-]+)?$/;
463
+ return (lines.some((line) => line.kind !== " ") &&
464
+ lines.every((line) => line.text.trim() === "" ? line.kind === " " : version.test(line.text.trim())));
465
+ }
466
+ const toml = path !== undefined && /\.toml$/i.test(path);
467
+ const json = path !== undefined && /\.json$/i.test(path);
468
+ let changed = 0;
469
+ for (const hunk of parseHunks(patch)) {
470
+ let table = tomlTable(hunk.section);
471
+ // The old side's nesting depth before the line; unknown (null) unless
472
+ // the hunk starts at the top of the file.
473
+ let depth = hunk.oldStart === 1 ? 0 : null;
474
+ for (const line of hunk.lines) {
475
+ table = tomlTable(line.text) ?? table;
476
+ if (line.kind !== " ") {
477
+ changed++;
478
+ if (!RELEASE_VERSION_LINE.test(line.text))
479
+ return false;
480
+ if (toml && (table === null || !RELEASE_TABLES.has(table)))
481
+ return false;
482
+ if (json && depth !== 1)
483
+ return false;
484
+ }
485
+ if (depth !== null && line.kind !== "+")
486
+ depth += jsonDepthDelta(line.text);
487
+ }
488
+ }
489
+ return changed > 0;
490
+ }
491
+ /** Does an ownership set hold this file — under its name, or, for a rename,
492
+ * under the name it had? The one ownership rule the classifier and the
493
+ * reviewed-head ownership read share. */
494
+ export function ownedBy(file, owned) {
495
+ return (owned.has(file.filename) ||
496
+ (file.previous_filename !== undefined && owned.has(file.previous_filename)));
497
+ }
498
+ /** The PR's own files as an ownership set — or undefined, "ownership not
499
+ * consulted", when the list was truncated: a PR-owned file past GitHub's cap
500
+ * would otherwise read as a base-merge file, which is trivial. */
501
+ export function ownershipSet(own) {
502
+ if (own.truncated)
503
+ return undefined;
504
+ return new Set(own.flatMap((f) => (f.previous_filename ? [f.filename, f.previous_filename] : [f.filename])));
505
+ }
506
+ /** Classify one file. The rules are deliberately narrow: anything a rule does
507
+ * not positively recognise is significant. */
508
+ export function classifyFile(file, opts = {}) {
509
+ const name = file.filename;
510
+ const trivial = (reason) => ({
511
+ filename: name,
512
+ class: "trivial",
513
+ reason,
514
+ });
515
+ // A hand-resolved merge conflict is never trivial, wherever the file lives.
516
+ if (opts.resolvedFiles?.has(name))
517
+ return { filename: name, class: "significant", reason: "content" };
518
+ if (opts.prFiles && !ownedBy(file, opts.prFiles))
519
+ return trivial("base-merge");
520
+ const patch = file.patch ?? "";
521
+ const churn = (file.additions ?? 0) + (file.deletions ?? 0);
522
+ if (file.previous_filename && isMigrationRenumber(file.previous_filename, name)) {
523
+ // A missing patch with churn (GitHub omits it on large files) proves
524
+ // nothing — the same reading classifyDelta's delete+add pairing takes.
525
+ if (churn === 0 || (patch && (digitsOnly(patch, name) || formattingOnly(patch, name))))
526
+ return trivial("migration-renumber");
527
+ return { filename: name, class: "significant", reason: "content" };
528
+ }
529
+ if (file.previous_filename && churn === 0)
530
+ return trivial("pure-rename");
531
+ if (LOCKFILES.has(baseName(name)))
532
+ return trivial("version-bump");
533
+ if (VERSION_MANIFESTS.has(baseName(name)) && patch && versionOnly(patch, name))
534
+ return trivial("version-bump");
535
+ if (patch && formattingOnly(patch, name))
536
+ return trivial("formatting");
537
+ if (!patch && churn === 0)
538
+ return trivial("no-content-change");
539
+ return { filename: name, class: "significant", reason: "content" };
540
+ }
541
+ /**
542
+ * Classify the whole delta. Run before the per-file pass: GitHub reports a
543
+ * migration renumber as a rename only when it detects one — a large enough
544
+ * content edit comes back as a delete + an add. That pair is a renumber only
545
+ * when its content, too, differs by migration numbers alone (`digitsOnly`).
546
+ */
547
+ export function classifyDelta(files, opts = {}) {
548
+ const removedMigrations = new Map();
549
+ for (const f of files) {
550
+ if (f.status !== "removed")
551
+ continue;
552
+ const m = migrationName(f.filename);
553
+ if (m)
554
+ removedMigrations.set(m.rest, f);
555
+ }
556
+ const renumbered = new Set();
557
+ for (const f of files) {
558
+ if (f.status !== "added")
559
+ continue;
560
+ const m = migrationName(f.filename);
561
+ const partner = m ? removedMigrations.get(m.rest) : undefined;
562
+ if (!m || !partner)
563
+ continue;
564
+ if (migrationName(partner.filename)?.num === m.num)
565
+ continue;
566
+ // The names only say it MIGHT be a renumber; the content must say it is.
567
+ // GitHub reports a rename as delete + add exactly when similarity is low,
568
+ // so the pair is trivial only when the removed file's text and the added
569
+ // file's text differ by migration numbers alone. A missing patch (GitHub
570
+ // omits it on large files) proves nothing, so it is not a renumber.
571
+ if (!f.patch || !partner.patch)
572
+ continue;
573
+ const gone = changedLines(partner.patch, "-");
574
+ const came = changedLines(f.patch, "+");
575
+ if (gone.length === 0 && came.length === 0)
576
+ continue;
577
+ if (!digitsOnly(["@@", ...gone, ...came].join("\n"), f.filename))
578
+ continue;
579
+ renumbered.add(f.filename);
580
+ renumbered.add(partner.filename);
581
+ }
582
+ const classified = files.map((f) => {
583
+ // A hand-resolved conflict beats every trivial rule, renumber included:
584
+ // classifyFile alone decides it.
585
+ if (renumbered.has(f.filename) && !opts.resolvedFiles?.has(f.filename)) {
586
+ // Ownership still wins: a migration that arrived with the base is a
587
+ // base-merge file whatever its number does.
588
+ const owned = !opts.prFiles || opts.prFiles.has(f.filename);
589
+ return {
590
+ filename: f.filename,
591
+ class: "trivial",
592
+ reason: owned ? "migration-renumber" : "base-merge",
593
+ };
594
+ }
595
+ return classifyFile(f, opts);
596
+ });
597
+ const significant = classified.filter((f) => f.class === "significant");
598
+ // A truncated delta is significant by construction: the files GitHub did not
599
+ // return may be exactly the PR-owned edit, and "all visible files trivial"
600
+ // says nothing about them. An unread one, for the same reason.
601
+ if (files.unread)
602
+ return { class: "significant", files: classified, significant, unread: true };
603
+ if (files.truncated)
604
+ return { class: "significant", files: classified, significant, truncated: true };
605
+ const cls = classified.length === 0 ? "empty" : significant.length > 0 ? "significant" : "trivial";
606
+ return { class: cls, files: classified, significant };
607
+ }
608
+ /** CI conclusions that carry no bad news. Anything else at an approved head is
609
+ * new evidence (guard 2's sense): something the standing review did not see. */
610
+ const CI_BENIGN = new Set(["success", "neutral", "skipped", "cancelled", "stale"]);
611
+ export function ciIsAdverse(conclusion) {
612
+ if (!conclusion)
613
+ return false;
614
+ return !CI_BENIGN.has(conclusion.toLowerCase());
615
+ }
616
+ /**
617
+ * Guard 2's rule as the recheck reads it, off the same mark: an unchanged head
618
+ * with nothing new is a no-op. A base advance is exempt because its delta is on
619
+ * the base side, and an adverse CI conclusion IS the new evidence. The runner's
620
+ * free early refusal and the plan both ask this one predicate.
621
+ */
622
+ export function unchangedHeadNoop(trigger, headSha, lastReviewedSha, ci) {
623
+ const adverseCi = trigger === "ci" && ciIsAdverse(ci?.conclusion);
624
+ return trigger !== "base_advance" && headSha === lastReviewedSha && !adverseCi;
625
+ }
626
+ /** Move a withheld-approval mark to a newer head after an uphold. This is NOT
627
+ * a re-approve — the approval was never posted; the CI-gate retry still
628
+ * demands green CI at the new head before anything posts. No mark, or a mark
629
+ * already at this head, is a silent no-op. */
630
+ export function carryWithheldMark(store, pr, newHead, via, log = () => { }) {
631
+ const withheld = store.approvalWithheld(pr);
632
+ if (!withheld || withheld.head_sha === newHead)
633
+ return;
634
+ // Only ever carry TO the PR's current head. Overlapping rechecks finish in
635
+ // any order: an older push's handler finishing after the newer one's would
636
+ // otherwise carry the mark BACK, and the ci_completed retry at the real head
637
+ // then no-ops on "head-moved" with the push backstop already claimed —
638
+ // stranded. Read and write are one synchronous stretch, so nothing can
639
+ // interleave between them.
640
+ const current = store.getPr(pr)?.head_sha;
641
+ if (current && current !== newHead) {
642
+ store.logEvent("approval_withheld_carry_stale", pr, {
643
+ from: withheld.head_sha,
644
+ to: newHead,
645
+ current,
646
+ via,
647
+ });
648
+ log(`recheck: #${pr} withheld approval NOT carried to ${newHead.slice(0, 7)} — the PR is at ${current.slice(0, 7)} (${via})`);
649
+ return;
650
+ }
651
+ // The classifier verdict that caused the withhold rides along with the
652
+ // mark — the carried head has no fresher CI read to offer, and the CI-gate
653
+ // retry re-classifies at release time anyway.
654
+ store.recordApprovalWithheld(pr, {
655
+ head_sha: newHead,
656
+ ci_state: withheld.ci_state ?? null,
657
+ reason: withheld.reason ?? "red-ci",
658
+ });
659
+ store.logEvent("approval_withheld_carried", pr, { from: withheld.head_sha, to: newHead, via });
660
+ log(`recheck: #${pr} withheld approval carried ${withheld.head_sha.slice(0, 7)} → ${newHead.slice(0, 7)} (${via})`);
661
+ }
662
+ /**
663
+ * The whole decision, pure. Order matters: the cheap structural refusals come
664
+ * before anything that would cost a classification, and the classification
665
+ * comes before anything that would cost a session turn.
666
+ */
667
+ export function planRecheck(input) {
668
+ const noop = (reason, classification) => ({
669
+ action: "noop",
670
+ reason,
671
+ class: classification?.class ?? "empty",
672
+ ...(classification ? { classification } : {}),
673
+ });
674
+ // The author-reply trigger is owned end to end by src/pingpong.ts. Nothing
675
+ // to recheck without a code delta, and a second reply handler would
676
+ // duplicate it.
677
+ if (input.trigger === "author_reply")
678
+ return noop("pingpong-owns-reply");
679
+ // Only a standing APPROVE can be moved by this path, and only downwards.
680
+ // Everything else (REQUEST_CHANGES, NEEDS_HUMAN, never reviewed) already has
681
+ // a route: the ordinary trigger → round path.
682
+ if (normalizeVerdict(input.standing) !== "APPROVE")
683
+ return noop("no-standing-approval");
684
+ if (unchangedHeadNoop(input.trigger, input.head_sha, input.last_reviewed_sha, input.ci))
685
+ return noop("unchanged-head");
686
+ const adverseCi = input.trigger === "ci" && ciIsAdverse(input.ci?.conclusion);
687
+ const classification = classifyDelta(input.delta, {
688
+ ...(input.prFiles ? { prFiles: new Set(input.prFiles) } : {}),
689
+ ...(input.resolvedFiles ? { resolvedFiles: new Set(input.resolvedFiles) } : {}),
690
+ });
691
+ if (adverseCi)
692
+ return {
693
+ action: "recheck",
694
+ class: classification.class,
695
+ evidence: classification.unread
696
+ ? `CI concluded ${input.ci?.conclusion}; the delta's files could not be read from GitHub — read the delta in the worktree yourself`
697
+ : `CI concluded ${input.ci?.conclusion}`,
698
+ classification,
699
+ };
700
+ if (classification.class === "empty")
701
+ return noop("empty-delta", classification);
702
+ if (classification.class === "trivial")
703
+ return noop("trivial-delta", classification);
704
+ return {
705
+ action: "recheck",
706
+ class: classification.class,
707
+ evidence: classification.truncated
708
+ ? `GitHub truncated the delta at ${classification.files.length} file(s) — the unseen remainder may hold changes this review has not seen`
709
+ : `${classification.significant.length} changed file(s) this review has not seen`,
710
+ classification,
711
+ };
712
+ }
713
+ // ---- the order handed to the session --------------------------------------
714
+ /** `pr-<n>-recheck-<head12>.json` — the decision file's name under `verdict/`,
715
+ * and the findings record's under `state/findings/`. */
716
+ export function recheckDecisionFile(pr, headSha) {
717
+ return `pr-${pr}-recheck-${recheckHead12(headSha)}.json`;
718
+ }
719
+ export const RECHECK_FILE_RE = /^pr-(\d+)-recheck-([0-9a-f]{7,40})\.json$/i;
720
+ /** The runaway-guard identity of one recheck question: (trigger, head, ci
721
+ * conclusion, base target). Shared by runRecheck's claim and the reconcile
722
+ * backstop's has-this-been-asked check. */
723
+ export function recheckFingerprint(req, headOverride) {
724
+ const head = headOverride ?? req.head_sha ?? "";
725
+ return [req.trigger, head, req.ci?.conclusion ?? "", req.base?.after ?? ""].join(":");
726
+ }
727
+ /** Fallback renderer (stub-wired daemon, unit tests): the order as JSON. */
728
+ export function defaultRecheckPrompt(order) {
729
+ return JSON.stringify(order, null, 2);
730
+ }
731
+ /**
732
+ * The files the PR owns for delta classification: what it owns at the current
733
+ * head PLUS what it owned at the REVIEWED head. A push that
734
+ * restores a reviewed file to its base content drops it from the current diff,
735
+ * yet that is the PR undoing its own reviewed change, not a base merge. The
736
+ * second read happens only when a delta file falls outside today's ownership:
737
+ * `<base_ref>...<reviewed>` is the PR's own diff at the reviewed head. Throws
738
+ * on a read failure — every caller already treats that as "not trivial".
739
+ */
740
+ export async function reviewedOwnership(adapter, pr, reviewedHead, delta, ownedNow) {
741
+ // Unknown ownership (a truncated listing) stays unknown.
742
+ if (!ownedNow)
743
+ return undefined;
744
+ const unowned = delta.some((f) => !ownedBy(f, ownedNow));
745
+ if (!unowned || !adapter.getPr || !adapter.compareFiles)
746
+ return new Set(ownedNow);
747
+ const { base_ref } = await adapter.getPr(pr);
748
+ if (!base_ref)
749
+ return new Set(ownedNow);
750
+ const then = ownershipSet(await adapter.compareFiles(base_ref, reviewedHead));
751
+ return then ? new Set([...ownedNow, ...then]) : undefined;
752
+ }
753
+ /**
754
+ * The files hand-resolved inside the merges of `from..to` — the one reading
755
+ * of the merge check both the recheck and dispatch's system re-trigger
756
+ * classifier make. No check wired ⇒ none. Degraded (git could not tell) keeps
757
+ * the delta-only classification, but is recorded as an event and a durable
758
+ * anomaly row — never silently quieter. `via` names a caller other than the
759
+ * recheck.
760
+ */
761
+ export async function verifyMerges(store, resolve, from, to, pr, via) {
762
+ if (!resolve)
763
+ return [];
764
+ const res = await resolve(from, to, pr);
765
+ if (res.degraded) {
766
+ store.logEvent("recheck_merge_verify_degraded", pr, { from, to, ...(via ? { via } : {}) });
767
+ store.logAnomaly("recheck_merge_verify_degraded", pr, null, `merge-verify degraded ${from.slice(0, 7)}→${to.slice(0, 7)}${via ? ` at ${via}` : ""} — delta-only classification kept`);
768
+ }
769
+ return res.files;
770
+ }
771
+ /**
772
+ * One trigger, start to finish. Returns the plan so callers (and tests) can see
773
+ * exactly why a trigger cost nothing.
774
+ */
775
+ export async function runRecheck(deps, req) {
776
+ const { cfg, store } = deps;
777
+ const log = deps.log ?? (() => { });
778
+ const now = deps.now ?? (() => new Date());
779
+ const done = (plan) => {
780
+ if (plan.action === "noop")
781
+ store.logEvent("recheck_noop", req.pr, {
782
+ trigger: req.trigger,
783
+ reason: plan.reason,
784
+ delta_class: plan.class,
785
+ files: plan.classification?.files.map((f) => `${f.filename} (${f.reason})`) ?? [],
786
+ });
787
+ return plan;
788
+ };
789
+ // Deploy-window drain: generate no recheck nudge at all —
790
+ // prefer not creating work over hold-then-flush, because the session the
791
+ // nudge would wake is about to be killed by the deploy. The reconcile
792
+ // push-backstop re-raises the trigger after the lift.
793
+ if (store.drainState().draining)
794
+ return done({ action: "noop", reason: "draining", class: "empty" });
795
+ if (!cfg.recheck.enabled || !triggerEnabled(cfg, req.trigger))
796
+ return done({ action: "noop", reason: "disabled", class: "empty" });
797
+ // Cheapest refusal first: ping-pong owns the author reply.
798
+ if (req.trigger === "author_reply")
799
+ return done(planRecheck({ ...emptyPlanInput(req), standing: "APPROVE" }));
800
+ const mark = store.reviewedHead(req.pr);
801
+ if (standingVerdict(mark) !== "APPROVE")
802
+ return done({ action: "noop", reason: "no-standing-approval", class: "empty" });
803
+ const row = store.getPr(req.pr);
804
+ if (!row || row.state === "closed")
805
+ return done({ action: "noop", reason: "pr-not-open", class: "empty" });
806
+ // A pause stops this PR's review work, a nudge included — it defers the
807
+ // question, never loses it. A push question is asked again by the reconcile
808
+ // backstop once the pause lifts; a CI or base-advance question has no such
809
+ // sweep, so it goes on to its claim and its nudge is held until the resume
810
+ // (`deferWhilePaused` below).
811
+ if (row.paused && req.trigger === "push")
812
+ return done({ action: "noop", reason: "paused", class: "empty" });
813
+ const head = req.head_sha || row.head_sha;
814
+ if (head.toLowerCase() !== row.head_sha.toLowerCase())
815
+ return done({ action: "noop", reason: "stale-head", class: "empty" });
816
+ const lastReviewed = mark.head_sha;
817
+ // Refuse before spending a single API call on a head we already decided.
818
+ if (unchangedHeadNoop(req.trigger, head, lastReviewed, req.ci))
819
+ return done({ action: "noop", reason: "unchanged-head", class: "empty" });
820
+ // The range the delta is read over — and the one its merges are checked in.
821
+ const range = req.trigger === "base_advance" && req.base
822
+ ? { from: req.base.before, to: req.base.after }
823
+ : { from: lastReviewed, to: head };
824
+ if (!deps.adapter.compareFiles || !deps.adapter.listPrFiles)
825
+ return done({ action: "noop", reason: "no-delta-source", class: "empty" });
826
+ let delta;
827
+ let prFiles;
828
+ try {
829
+ delta = await deps.adapter.compareFiles(range.from, range.to);
830
+ prFiles = ownershipSet(await deps.adapter.listPrFiles(req.pr));
831
+ // Ownership is the union of the reviewed head's and the current head's —
832
+ // see reviewedOwnership. A base advance leaves the
833
+ // head where it was reviewed, so there is nothing to revert. A truncated
834
+ // listing leaves ownership unknown (undefined): every delta file is then
835
+ // significant.
836
+ if (req.trigger !== "base_advance")
837
+ prFiles = await reviewedOwnership(deps.adapter, req.pr, lastReviewed, delta, prFiles);
838
+ }
839
+ catch (err) {
840
+ // A read failure is not evidence of anything. A question the reconcile
841
+ // push backstop asks again — a push, or a benign CI result at a new head,
842
+ // whose push question is the same — upholds and is asked once the read
843
+ // recovers. An adverse CI result is asked whatever its delta holds, so it
844
+ // goes on with the delta UNREAD and the session is told so. A base advance
845
+ // is NOT asked unread: almost every one touches no PR-owned file, and
846
+ // waking a session on an advance nobody could classify is the delta-blind
847
+ // staleness check the recheck exists to avoid. It upholds, with this
848
+ // anomaly row as its record — nothing asks it again.
849
+ const askUnread = ciIsAdverse(req.ci?.conclusion);
850
+ log(`recheck: #${req.pr} delta read failed`, err);
851
+ store.logEvent("recheck_delta_failed", req.pr, {
852
+ trigger: req.trigger,
853
+ error: String(err),
854
+ asked_unread: askUnread,
855
+ });
856
+ // Durable anomaly row, quiet class: DB-only, reviewed at the daily
857
+ // heartbeat.
858
+ store.logAnomaly("recheck_delta_failed", req.pr, null, `delta read failed (${req.trigger}): ${String(err)} — ${askUnread
859
+ ? "asked with the delta unread"
860
+ : req.trigger === "base_advance"
861
+ ? "upheld unread; nothing asks this base advance again"
862
+ : "the reconcile push backstop asks again"}`);
863
+ if (!askUnread)
864
+ return done({ action: "noop", reason: "no-delta-source", class: "empty" });
865
+ delta = unreadDelta();
866
+ prFiles = undefined;
867
+ }
868
+ // Conflict resolutions are never trivial: ask git which files in this
869
+ // delta's merges were hand-resolved.
870
+ const resolvedFiles = await verifyMerges(store, deps.resolvedFilesInMerges, range.from, range.to, req.pr);
871
+ const plan = planRecheck({
872
+ trigger: req.trigger,
873
+ standing: standingVerdict(mark),
874
+ last_reviewed_sha: lastReviewed,
875
+ head_sha: head,
876
+ delta,
877
+ ...(prFiles ? { prFiles: [...prFiles] } : {}),
878
+ resolvedFiles,
879
+ ...(req.ci ? { ci: req.ci } : {}),
880
+ });
881
+ if (store.getPr(req.pr)?.head_sha.toLowerCase() !== head.toLowerCase())
882
+ return done({ action: "noop", reason: "stale-head", class: "empty" });
883
+ // Carry-forward: a withheld approval pinned to an old sha would be stranded
884
+ // — the retry no-ops on "head-moved", and the recheck can never approve.
885
+ // When the delta itself upholds (empty/trivial), the never-posted approval
886
+ // moves with the head, so green CI at the NEW head releases it. A
887
+ // significant delta carries only after the session's explicit uphold
888
+ // (watcher below).
889
+ if (plan.action === "noop") {
890
+ if ((plan.reason === "empty-delta" || plan.reason === "trivial-delta") &&
891
+ head !== lastReviewed) {
892
+ carryWithheldMark(store, req.pr, head, `recheck-noop:${plan.reason}`, log);
893
+ // A trivial/empty answer is terminal for this head: claim it, so the
894
+ // reconcile backstop (below in reconcile.ts) never re-classifies the
895
+ // same question every sweep. No wake goes out, so no receipt token can
896
+ // ever exist for it: `nudge: false` keeps the undelivered sweep from
897
+ // reading its absence as a lost nudge and releasing it.
898
+ store.claimRecheck(req.pr, recheckFingerprint(req, head), {
899
+ head_sha: head,
900
+ trigger: req.trigger,
901
+ reason: `noop:${plan.reason}`,
902
+ nudge: false,
903
+ });
904
+ }
905
+ return done(plan);
906
+ }
907
+ // The runaway guard: one nudge per (trigger, head, evidence). Without it a
908
+ // reconcile replay or a redelivered webhook re-asks the same question and
909
+ // pays for it every time.
910
+ const fingerprint = recheckFingerprint(req, head);
911
+ if (store.hasRecheck(req.pr, fingerprint))
912
+ return done({
913
+ action: "noop",
914
+ reason: "already-rechecked",
915
+ class: plan.class,
916
+ classification: plan.classification,
917
+ });
918
+ const order = {
919
+ pr: req.pr,
920
+ title: row.title,
921
+ trigger: req.trigger,
922
+ standing_verdict: "APPROVE",
923
+ last_reviewed_sha: lastReviewed,
924
+ head_sha: head,
925
+ worktree: (deps.worktreeFor ?? prWorktreeRel)(req.pr),
926
+ // A base advance's delta is the base branch's range, not anything since
927
+ // the reviewed head: the evidence names the range that was compared.
928
+ evidence: req.trigger === "base_advance" && req.base
929
+ ? `the base branch \`${req.base.branch}\` advanced ${range.from.slice(0, 12)}→${range.to.slice(0, 12)}; ${plan.evidence}`
930
+ : plan.evidence,
931
+ files: plan.classification.files,
932
+ resolved_files: resolvedFiles,
933
+ decision_file: recheckDecisionFile(req.pr, head),
934
+ issued_at: now().toISOString(),
935
+ };
936
+ const render = deps.renderPrompt ?? defaultRecheckPrompt;
937
+ // The repo facts for the files the session must review
938
+ // (the significant ones; every delta file when none is) — "" unless
939
+ // lessons are delivered. Bounded and total: never a reason to hold or lose
940
+ // the nudge.
941
+ let facts = "";
942
+ if (deps.repoFacts) {
943
+ const touched = plan.classification.significant.length
944
+ ? plan.classification.significant.map((f) => f.filename)
945
+ : plan.classification.files.map((f) => f.filename);
946
+ try {
947
+ facts = await deps.repoFacts({
948
+ pr: req.pr,
949
+ title: row.title,
950
+ paths: touched,
951
+ head_sha: head,
952
+ purpose: "recheck",
953
+ });
954
+ }
955
+ catch (err) {
956
+ log(`recheck: #${req.pr} repo facts unavailable (ignored)`, err);
957
+ facts = "";
958
+ }
959
+ if (typeof facts !== "string")
960
+ facts = "";
961
+ }
962
+ // Mid-round nudges are held and flushed at round end — the PR's one main
963
+ // pi is never given two instructions at once.
964
+ let delivered;
965
+ let claimed = false;
966
+ try {
967
+ const payload = stampPrompt(withRepoFacts(render(order), facts), recheckPromptMarker(req.pr, fingerprint));
968
+ if (store.getPr(req.pr)?.head_sha.toLowerCase() !== head.toLowerCase())
969
+ return done({ action: "noop", reason: "stale-head", class: "empty" });
970
+ if (standingVerdict(store.reviewedHead(req.pr)) !== "APPROVE")
971
+ return done({ action: "noop", reason: "no-standing-approval", class: "empty" });
972
+ // A pause that landed while the reads ran: a push question is asked again
973
+ // by the reconcile backstop once it lifts.
974
+ if (req.trigger === "push" && store.getPr(req.pr)?.paused)
975
+ return done({ action: "noop", reason: "paused", class: "empty" });
976
+ claimed = store.db.transaction(() => {
977
+ if (!store.claimRecheck(req.pr, fingerprint, {
978
+ head_sha: head,
979
+ trigger: req.trigger,
980
+ reason: plan.evidence,
981
+ ...(req.trigger !== "push"
982
+ ? { delivery: { payload, reason: recheckWakeReason(req.trigger) } }
983
+ : {}),
984
+ }))
985
+ return false;
986
+ // A same-head question (an adverse CI conclusion, a base advance) is new
987
+ // evidence at a head already reviewed. Recorded on the mark the posting
988
+ // guard reads, so the change request it may lead to is not refused as a
989
+ // duplicate of the review that stands (guard 2).
990
+ if (head === lastReviewed) {
991
+ store.noteNewEvidence(req.pr, now().toISOString());
992
+ store.logEvent("recheck_evidence_noted", req.pr, {
993
+ trigger: req.trigger,
994
+ head_sha: head,
995
+ evidence: plan.evidence,
996
+ });
997
+ }
998
+ return true;
999
+ })();
1000
+ if (!claimed)
1001
+ return done({
1002
+ action: "noop",
1003
+ reason: "already-rechecked",
1004
+ class: plan.class,
1005
+ classification: plan.classification,
1006
+ });
1007
+ delivered = await wakeOrHold({ store, inject: deps.inject, log }, req.pr,
1008
+ // The receipt token distinguishes confirmed delivery from owed work.
1009
+ // Pushes can be regenerated; CI/base questions retain these exact bytes.
1010
+ payload, recheckWakeReason(req.trigger),
1011
+ // A push question the reconcile backstop asks again; a CI or base-advance
1012
+ // question uses its durable payload, so its claim stays and the wake is held
1013
+ // for the flush paths rather than released on a failed injection — and
1014
+ // held, not delivered, while the PR is paused.
1015
+ { holdOnFailure: req.trigger !== "push", deferWhilePaused: req.trigger !== "push" });
1016
+ }
1017
+ catch (err) {
1018
+ // Pushes have a backstop. Other triggers retain the claim and exact
1019
+ // payload so a failed injection cannot consume their only question.
1020
+ if (claimed && req.trigger === "push")
1021
+ store.releaseRecheck(req.pr, fingerprint);
1022
+ store.logEvent("recheck_delivery_failed", req.pr, {
1023
+ trigger: req.trigger,
1024
+ head_sha: head,
1025
+ error: String(err),
1026
+ });
1027
+ log(`recheck: #${req.pr} delivery failed — ${req.trigger === "push" ? "claim released for retry" : "durable question retained when claimed"}`, err);
1028
+ return done({
1029
+ action: "noop",
1030
+ reason: "delivery-failed",
1031
+ class: plan.class,
1032
+ classification: plan.classification,
1033
+ });
1034
+ }
1035
+ store.logEvent("recheck_nudged", req.pr, {
1036
+ trigger: req.trigger,
1037
+ head_sha: head,
1038
+ last_reviewed_sha: lastReviewed,
1039
+ delta_class: plan.class,
1040
+ significant: plan.classification.significant.map((f) => f.filename),
1041
+ evidence: plan.evidence,
1042
+ delivered,
1043
+ });
1044
+ log(`recheck: #${req.pr} nudged (${req.trigger}, ${plan.classification.significant.length} file(s)) — ${delivered}`);
1045
+ return { ...plan, delivered, order };
1046
+ }
1047
+ function triggerEnabled(cfg, trigger) {
1048
+ switch (trigger) {
1049
+ case "push":
1050
+ return cfg.recheck.on_push;
1051
+ case "ci":
1052
+ return cfg.recheck.on_ci;
1053
+ case "base_advance":
1054
+ return cfg.recheck.on_base_advance;
1055
+ case "author_reply":
1056
+ return true; // always reaches the ping-pong no-op
1057
+ }
1058
+ }
1059
+ function emptyPlanInput(req) {
1060
+ return {
1061
+ trigger: req.trigger,
1062
+ standing: null,
1063
+ last_reviewed_sha: "",
1064
+ head_sha: req.head_sha ?? "",
1065
+ delta: [],
1066
+ };
1067
+ }
1068
+ /**
1069
+ * A base branch moved: fan the advance out to the PRs that target it and hold
1070
+ * a standing approval. Every other PR is skipped without an API call.
1071
+ */
1072
+ export async function runBaseAdvance(deps, advance) {
1073
+ const out = new Map();
1074
+ const log = deps.log ?? (() => { });
1075
+ if (!deps.cfg.recheck.enabled || !deps.cfg.recheck.on_base_advance)
1076
+ return out;
1077
+ // Deploy-window drain: skip the whole fan-out before it spends per-PR API
1078
+ // calls (runRecheck would refuse each one anyway).
1079
+ if (deps.store.drainState().draining)
1080
+ return out;
1081
+ for (const row of deps.store.allPrs()) {
1082
+ if (row.state === "closed")
1083
+ continue;
1084
+ if (standingVerdict(deps.store.reviewedHead(row.number)) !== "APPROVE")
1085
+ continue;
1086
+ if (deps.adapter.getPr) {
1087
+ try {
1088
+ // Called on the adapter, never through a detached reference: the real
1089
+ // GitHubAdapter's methods are class methods and need their `this`.
1090
+ const ref = await deps.adapter.getPr(row.number);
1091
+ // GitHub already says closed: there is nothing left to audit, and the
1092
+ // reconcile closed-sync settles the row.
1093
+ if (ref.state === "closed" || ref.merged)
1094
+ continue;
1095
+ if (ref.base_ref && ref.base_ref !== advance.branch)
1096
+ continue;
1097
+ }
1098
+ catch (err) {
1099
+ log(`recheck: #${row.number} base ref lookup failed`, err);
1100
+ continue;
1101
+ }
1102
+ }
1103
+ out.set(row.number, await runRecheck(deps, { pr: row.number, trigger: "base_advance", base: advance }));
1104
+ }
1105
+ return out;
1106
+ }
1107
+ export function validateRecheckDecision(raw) {
1108
+ if (typeof raw !== "object" || raw === null)
1109
+ return { ok: false, error: "not an object" };
1110
+ const v = raw;
1111
+ if (typeof v.pr === "string" && /^\d+$/.test(v.pr))
1112
+ v.pr = Number(v.pr);
1113
+ if (typeof v.pr !== "number" || !Number.isInteger(v.pr))
1114
+ return { ok: false, error: "pr must be an integer" };
1115
+ if (typeof v.head_sha !== "string" || v.head_sha.length === 0)
1116
+ return { ok: false, error: "head_sha must be a non-empty string" };
1117
+ const raw_outcome = typeof v.outcome === "string" ? v.outcome.trim().toLowerCase() : "";
1118
+ // NOT a rejection: an unrecognised outcome (including "approve" and
1119
+ // "needs_human", which a session may reach for out of round habit) becomes
1120
+ // the default — uphold. The recheck path has no other verdict to give.
1121
+ const coerced = raw_outcome === "uphold" || raw_outcome === "request_changes"
1122
+ ? undefined
1123
+ : String(v.outcome ?? "");
1124
+ const outcome = raw_outcome === "request_changes" ? "request_changes" : "uphold";
1125
+ return {
1126
+ ok: true,
1127
+ decision: {
1128
+ pr: v.pr,
1129
+ head_sha: v.head_sha,
1130
+ outcome,
1131
+ ...(typeof v.reason === "string" ? { reason: v.reason.trim().slice(0, 800) } : {}),
1132
+ },
1133
+ ...(coerced !== undefined ? { coerced } : {}),
1134
+ };
1135
+ }
1136
+ /**
1137
+ * Consumes `verdict/pr-<n>-recheck-<head12>.json`. It touches GitHub not at
1138
+ * all: on `uphold` there is nothing to say, and on `request_changes` the
1139
+ * session has already posted the review through the gated scripts — the daemon
1140
+ * only moves the lifecycle state.
1141
+ *
1142
+ * Neither outcome moves the reviewed-head mark to a new head. The mark
1143
+ * records what was really REVIEWED and posted; an upheld recheck posted
1144
+ * nothing, and stamping it would make the no-new-evidence guard swallow a
1145
+ * genuine review re-request at that head. A change request the session posted
1146
+ * was stamped by the posting route itself; the watcher only writes its
1147
+ * decided verdict onto that stamp (`settleChangeRequest`). The recheck's own
1148
+ * runaway guard is the `rechecks` claim table.
1149
+ */
1150
+ export class RecheckWatcher {
1151
+ deps;
1152
+ watcher = null;
1153
+ inFlight = new Set();
1154
+ processedDir;
1155
+ constructor(deps) {
1156
+ this.deps = deps;
1157
+ this.processedDir = join(deps.verdictDir, "processed");
1158
+ }
1159
+ start() {
1160
+ mkdirSync(this.deps.verdictDir, { recursive: true });
1161
+ void this.sweep();
1162
+ this.watcher = watch(this.deps.verdictDir, (_event, filename) => {
1163
+ const name = filename ? String(filename) : "";
1164
+ if (RECHECK_FILE_RE.test(name))
1165
+ void this.handleFile(name);
1166
+ });
1167
+ }
1168
+ stop() {
1169
+ this.watcher?.close();
1170
+ this.watcher = null;
1171
+ }
1172
+ /** One PR's decisions in name order, different PRs side by side: a file
1173
+ * still being written is waited out for seconds and must not
1174
+ * hold another PR's decision behind it. */
1175
+ async sweep() {
1176
+ if (!existsSync(this.deps.verdictDir))
1177
+ return [];
1178
+ const byPr = new Map();
1179
+ for (const name of readdirSync(this.deps.verdictDir).sort()) {
1180
+ const match = RECHECK_FILE_RE.exec(name);
1181
+ if (!match)
1182
+ continue;
1183
+ byPr.set(match[1], [...(byPr.get(match[1]) ?? []), name]);
1184
+ }
1185
+ const done = [];
1186
+ await Promise.all([...byPr.values()].map(async (names) => {
1187
+ for (const name of names)
1188
+ if (await this.handleFile(name))
1189
+ done.push(name);
1190
+ }));
1191
+ return done.sort();
1192
+ }
1193
+ archive(name) {
1194
+ mkdirSync(this.processedDir, { recursive: true });
1195
+ const ts = (this.deps.now ?? (() => new Date()))().toISOString().replace(/[:.]/g, "-");
1196
+ try {
1197
+ renameSync(join(this.deps.verdictDir, name), join(this.processedDir, `${ts}-${name}`));
1198
+ }
1199
+ catch (err) {
1200
+ this.deps.log?.(`recheck: could not archive ${name}`, err);
1201
+ }
1202
+ }
1203
+ /**
1204
+ * Parse `state/findings/pr-<n>-recheck-<head12>.json` into `round_findings`.
1205
+ *
1206
+ * A recheck owns no round row, so its entries are attributed to the round
1207
+ * whose verdict it just moved — the PR's reviewed-head mark, falling back to
1208
+ * its highest round number. Keys are prefixed `recheck:<head12>:` so a
1209
+ * recheck's rows can never collide with that round's own.
1210
+ */
1211
+ async recordRecheckFindings(pr, headSha) {
1212
+ const dir = this.deps.findingsDir;
1213
+ if (!dir)
1214
+ return;
1215
+ try {
1216
+ const path = join(dir, recheckDecisionFile(pr, headSha));
1217
+ const store = this.deps.store;
1218
+ const roundNo = store.reviewedHead(pr)?.round_no ??
1219
+ store.roundsFor(pr).reduce((m, r) => (r.round_no > m ? r.round_no : m), 0);
1220
+ const parsed = readFindingsRecord(path, pr, roundNo);
1221
+ if (parsed.status !== "ok" || !parsed.record) {
1222
+ store.logEvent("recheck_findings_record_absent", pr, {
1223
+ head_sha: headSha,
1224
+ status: parsed.status,
1225
+ });
1226
+ this.deps.log?.(`recheck: #${pr} moved the verdict with no findings record (${parsed.status}) — decision stands`);
1227
+ return;
1228
+ }
1229
+ const entries = parsed.record.findings.map((f) => ({
1230
+ ...f,
1231
+ key: `${recheckKeyPrefix(headSha)}${f.key}`,
1232
+ }));
1233
+ store.recordRoundFindings({ pr, round_id: null, round_no: roundNo, head_sha: headSha }, entries);
1234
+ store.logEvent("recheck_findings_recorded", pr, {
1235
+ head_sha: headSha,
1236
+ round_no: roundNo,
1237
+ entries: entries.length,
1238
+ });
1239
+ }
1240
+ catch (err) {
1241
+ this.deps.log?.(`recheck: #${pr} findings record read failed`, err);
1242
+ }
1243
+ }
1244
+ /**
1245
+ * The lifecycle half of a change request the session posted. The review on
1246
+ * GitHub is the standing decision at its head: when the shell stamped it at
1247
+ * the head the mark already held, the mark keeps the verdict it had, so the
1248
+ * decided verdict is written here — whether it posted as REQUEST_CHANGES or
1249
+ * guard 1 softened it to a COMMENT (a person approved the head), the same
1250
+ * enrichment a round's verdict gets. A mark still reading APPROVE posted
1251
+ * holds no post of this decision and is left as it is. The lifecycle moves to awaiting_author
1252
+ * unless newer work owns the PR — a round running, or a person's queued
1253
+ * re-review request — which this answer must not settle or erase; that
1254
+ * work's own verdict decides. The PR's head is GitHub's and is never
1255
+ * rewound to the decision's.
1256
+ */
1257
+ settleChangeRequest(pr, headSha, file) {
1258
+ const { store } = this.deps;
1259
+ const mark = store.reviewedHead(pr);
1260
+ if (mark &&
1261
+ mark.head_sha.toLowerCase() === headSha.toLowerCase() &&
1262
+ (mark.posted_event === "REQUEST_CHANGES" || mark.posted_event === "COMMENT") &&
1263
+ standingVerdict(mark) !== "REQUEST_CHANGES")
1264
+ store.recordReviewedVerdict(pr, mark.head_sha, "REQUEST_CHANGES");
1265
+ const row = store.getPr(pr);
1266
+ if (row?.state === "reviewing" ||
1267
+ (row?.state === "queued" && row.trigger_provenance === "human")) {
1268
+ store.logEvent("recheck_lifecycle_deferred", pr, {
1269
+ file,
1270
+ head_sha: headSha,
1271
+ state: row.state,
1272
+ trigger_provenance: row.trigger_provenance ?? null,
1273
+ });
1274
+ return;
1275
+ }
1276
+ applyEvent(store, pr, { type: "verdict", verdict: "REQUEST_CHANGES" }, {}, {
1277
+ botLogin: this.deps.cfg.github.bot_login,
1278
+ ...(this.deps.now ? { now: this.deps.now } : {}),
1279
+ reason: "recheck:request-changes",
1280
+ });
1281
+ }
1282
+ superseded(pr, head) {
1283
+ const { store } = this.deps;
1284
+ const mark = store.reviewedHead(pr);
1285
+ if (!mark || mark.head_sha.toLowerCase() === head.toLowerCase())
1286
+ return false;
1287
+ if (mark.head_sha.toLowerCase() === store.getPr(pr)?.head_sha.toLowerCase())
1288
+ return true;
1289
+ const claims = store
1290
+ .rechecksFor(pr)
1291
+ .filter((claim) => claim.head_sha.toLowerCase() === head.toLowerCase());
1292
+ return claims.some((claim) => mark.reviewed_at > claim.created_at);
1293
+ }
1294
+ async handleFile(name) {
1295
+ const match = RECHECK_FILE_RE.exec(name);
1296
+ if (!match || this.inFlight.has(name))
1297
+ return false;
1298
+ this.inFlight.add(name);
1299
+ try {
1300
+ const path = join(this.deps.verdictDir, name);
1301
+ if (!existsSync(path))
1302
+ return false;
1303
+ const pr = Number(match[1]);
1304
+ // fs.watch fires the moment the file appears, often before the
1305
+ // session's write completes, so a single eager read would discard valid
1306
+ // decisions as "unparseable". An unparseable file is waited out while
1307
+ // it is still moving; one that parses is judged at once.
1308
+ const read = await readSettledJson(path, resolvePartialWritePolicy(this.deps.partialWrite));
1309
+ if (read.kind === "missing")
1310
+ return false;
1311
+ const reads = read.reads;
1312
+ const parsed = read.kind === "parsed"
1313
+ ? validateRecheckDecision(read.value)
1314
+ : { ok: false, error: `unparseable: ${read.error}` };
1315
+ if (!parsed.ok || !parsed.decision) {
1316
+ // Malformed is the safe direction here — the standing verdict simply
1317
+ // stays standing. No park, no anomaly round, nothing posted. But say
1318
+ // so out loud: a silent drop would hide a partial-read race.
1319
+ this.deps.store.logEvent("recheck_decision_malformed", pr, {
1320
+ file: name,
1321
+ error: parsed.error ?? "unknown",
1322
+ });
1323
+ this.deps.log?.(`recheck: #${pr} decision ${name} malformed after ${reads} read(s) — standing verdict kept (${parsed.error ?? "unknown"})`);
1324
+ this.archive(name);
1325
+ return true;
1326
+ }
1327
+ const d = parsed.decision;
1328
+ if (d.pr !== pr || recheckHead12(d.head_sha) !== match[2].toLowerCase()) {
1329
+ this.deps.store.logEvent("recheck_decision_rejected", pr, {
1330
+ file: name,
1331
+ reason: "pr/head do not match the filename",
1332
+ });
1333
+ this.archive(name);
1334
+ return true;
1335
+ }
1336
+ if (parsed.coerced !== undefined)
1337
+ this.deps.store.logEvent("recheck_outcome_coerced", pr, {
1338
+ file: name,
1339
+ given: parsed.coerced,
1340
+ applied: "uphold",
1341
+ });
1342
+ if (this.superseded(pr, d.head_sha)) {
1343
+ this.deps.store.logEvent("recheck_decision_superseded", pr, {
1344
+ file: name,
1345
+ head_sha: d.head_sha,
1346
+ });
1347
+ this.archive(name);
1348
+ return true;
1349
+ }
1350
+ if (d.outcome === "request_changes") {
1351
+ // The reasoning behind a backed-down approval. Best-effort
1352
+ // and strictly additive: a missing record here is logged and nothing
1353
+ // else — the decision above is what moves the PR.
1354
+ await this.recordRecheckFindings(pr, d.head_sha);
1355
+ // Ingestion yields: a newer round may have settled in the meantime.
1356
+ if (this.superseded(pr, d.head_sha)) {
1357
+ this.archive(name);
1358
+ return true;
1359
+ }
1360
+ // Approval is off the table now — a lingering withheld mark would let
1361
+ // a later green CI re-queue "for the withheld approval" of a PR whose
1362
+ // standing verdict this very decision just flipped.
1363
+ this.deps.store.clearApprovalWithheld(pr);
1364
+ this.settleChangeRequest(pr, d.head_sha, name);
1365
+ }
1366
+ if (d.outcome === "uphold") {
1367
+ // Session-examined significant delta, verdict upheld: the withheld
1368
+ // approval (if any) follows the head.
1369
+ carryWithheldMark(this.deps.store, pr, d.head_sha, "recheck-uphold", (m) => this.deps.log?.(m));
1370
+ // Ledger freshness after the uphold (optional wiring).
1371
+ if (this.deps.checkLedger && this.deps.store.getPr(pr)?.state !== "closed") {
1372
+ try {
1373
+ await this.deps.checkLedger(pr, d.head_sha);
1374
+ }
1375
+ catch (err) {
1376
+ this.deps.log?.(`recheck: ledger check failed on #${pr}`, err);
1377
+ }
1378
+ }
1379
+ }
1380
+ this.deps.store.logEvent(d.outcome === "request_changes" ? "recheck_request_changes" : "recheck_upheld", pr, {
1381
+ file: name,
1382
+ head_sha: d.head_sha,
1383
+ reason: d.reason ?? null,
1384
+ });
1385
+ this.deps.log?.(`recheck: #${pr} ${d.outcome} at ${d.head_sha.slice(0, 7)}`);
1386
+ this.archive(name);
1387
+ return true;
1388
+ }
1389
+ catch (err) {
1390
+ this.deps.log?.(`recheck: ${name} failed`, err);
1391
+ return false;
1392
+ }
1393
+ finally {
1394
+ this.inFlight.delete(name);
1395
+ }
1396
+ }
1397
+ }
1398
+ //# sourceMappingURL=recheck.js.map