toolroll 0.5.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 (626) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1113 -0
  3. package/THIRD_PARTY_NOTICES.md +449 -0
  4. package/dist/accent-colors.d.ts +48 -0
  5. package/dist/accent-colors.js +122 -0
  6. package/dist/action-ledger.d.ts +26 -0
  7. package/dist/action-ledger.js +75 -0
  8. package/dist/agent-fence.d.ts +42 -0
  9. package/dist/agent-fence.js +183 -0
  10. package/dist/agentconfig.d.ts +216 -0
  11. package/dist/agentconfig.js +454 -0
  12. package/dist/api-tokens.d.ts +16 -0
  13. package/dist/api-tokens.js +27 -0
  14. package/dist/approval-policy.d.ts +69 -0
  15. package/dist/approval-policy.js +137 -0
  16. package/dist/approval-rules-ui.d.ts +25 -0
  17. package/dist/approval-rules-ui.js +39 -0
  18. package/dist/assignment-adapters.d.ts +180 -0
  19. package/dist/assignment-adapters.js +239 -0
  20. package/dist/assignment-brief.d.ts +71 -0
  21. package/dist/assignment-brief.js +129 -0
  22. package/dist/assignment-delivery.d.ts +56 -0
  23. package/dist/assignment-delivery.js +160 -0
  24. package/dist/assignment-presentation.d.ts +26 -0
  25. package/dist/assignment-presentation.js +80 -0
  26. package/dist/assignment-status.d.ts +62 -0
  27. package/dist/assignment-status.js +152 -0
  28. package/dist/assignment-ui.d.ts +71 -0
  29. package/dist/assignment-ui.js +103 -0
  30. package/dist/assignment.d.ts +222 -0
  31. package/dist/assignment.js +399 -0
  32. package/dist/attest.d.ts +56 -0
  33. package/dist/attest.js +153 -0
  34. package/dist/backend.d.ts +97 -0
  35. package/dist/backend.js +166 -0
  36. package/dist/backup-ui.d.ts +22 -0
  37. package/dist/backup-ui.js +59 -0
  38. package/dist/backup.d.ts +84 -0
  39. package/dist/backup.js +421 -0
  40. package/dist/beads.d.ts +34 -0
  41. package/dist/beads.js +135 -0
  42. package/dist/bin.d.ts +2 -0
  43. package/dist/bin.js +22 -0
  44. package/dist/board.d.ts +150 -0
  45. package/dist/board.js +210 -0
  46. package/dist/boot-identity.d.ts +63 -0
  47. package/dist/boot-identity.js +99 -0
  48. package/dist/browser/THIRD_PARTY_NOTICES.txt +2295 -0
  49. package/dist/browser/workspace.css +4 -0
  50. package/dist/browser/workspace.js +225 -0
  51. package/dist/browser-crew.d.ts +11 -0
  52. package/dist/browser-crew.js +50 -0
  53. package/dist/browser-shell.d.ts +8 -0
  54. package/dist/browser-shell.js +60 -0
  55. package/dist/browser-workspace.d.ts +780 -0
  56. package/dist/browser-workspace.js +47 -0
  57. package/dist/budget-alerts.d.ts +19 -0
  58. package/dist/budget-alerts.js +46 -0
  59. package/dist/builder.d.ts +370 -0
  60. package/dist/builder.js +3262 -0
  61. package/dist/capscan.d.ts +35 -0
  62. package/dist/capscan.js +113 -0
  63. package/dist/chat-acceptance.d.ts +71 -0
  64. package/dist/chat-acceptance.js +120 -0
  65. package/dist/chat-actions.d.ts +265 -0
  66. package/dist/chat-actions.js +1273 -0
  67. package/dist/chat-channel.d.ts +109 -0
  68. package/dist/chat-channel.js +652 -0
  69. package/dist/chat-continuity.d.ts +32 -0
  70. package/dist/chat-continuity.js +317 -0
  71. package/dist/chat-controls.d.ts +113 -0
  72. package/dist/chat-controls.js +53 -0
  73. package/dist/chat-delivery-state.d.ts +159 -0
  74. package/dist/chat-delivery-state.js +309 -0
  75. package/dist/chat-delivery.d.ts +38 -0
  76. package/dist/chat-delivery.js +693 -0
  77. package/dist/chat-display.d.ts +1 -0
  78. package/dist/chat-display.js +18 -0
  79. package/dist/chat-evidence.d.ts +123 -0
  80. package/dist/chat-evidence.js +207 -0
  81. package/dist/chat-flow.d.ts +44 -0
  82. package/dist/chat-flow.js +215 -0
  83. package/dist/chat-inbox.d.ts +57 -0
  84. package/dist/chat-inbox.js +123 -0
  85. package/dist/chat-polish.d.ts +16 -0
  86. package/dist/chat-polish.js +135 -0
  87. package/dist/chat-review.d.ts +46 -0
  88. package/dist/chat-review.js +98 -0
  89. package/dist/chat-rooms.d.ts +123 -0
  90. package/dist/chat-rooms.js +216 -0
  91. package/dist/chat-task-actions.d.ts +44 -0
  92. package/dist/chat-task-actions.js +79 -0
  93. package/dist/check-progress.d.ts +51 -0
  94. package/dist/check-progress.js +290 -0
  95. package/dist/child-database.d.ts +12 -0
  96. package/dist/child-database.js +30 -0
  97. package/dist/claim.d.ts +688 -0
  98. package/dist/claim.js +1740 -0
  99. package/dist/cli.d.ts +137 -0
  100. package/dist/cli.js +1461 -0
  101. package/dist/codex-limits.d.ts +15 -0
  102. package/dist/codex-limits.js +145 -0
  103. package/dist/coding-context.d.ts +55 -0
  104. package/dist/coding-context.js +92 -0
  105. package/dist/coding-handoff.d.ts +57 -0
  106. package/dist/coding-handoff.js +277 -0
  107. package/dist/coding-provider.d.ts +72 -0
  108. package/dist/coding-provider.js +424 -0
  109. package/dist/coding-shipping-ui.d.ts +4 -0
  110. package/dist/coding-shipping-ui.js +12 -0
  111. package/dist/coding-types.d.ts +62 -0
  112. package/dist/coding-types.js +1 -0
  113. package/dist/coding-ui.d.ts +17 -0
  114. package/dist/coding-ui.js +364 -0
  115. package/dist/coding-update.d.ts +15 -0
  116. package/dist/coding-update.js +149 -0
  117. package/dist/coding-workspace.d.ts +124 -0
  118. package/dist/coding-workspace.js +768 -0
  119. package/dist/container-state.d.ts +5 -0
  120. package/dist/container-state.js +62 -0
  121. package/dist/containment.d.ts +197 -0
  122. package/dist/containment.js +559 -0
  123. package/dist/contest.d.ts +318 -0
  124. package/dist/contest.js +753 -0
  125. package/dist/control-setup.d.ts +35 -0
  126. package/dist/control-setup.js +40 -0
  127. package/dist/control-ui.d.ts +29 -0
  128. package/dist/control-ui.js +53 -0
  129. package/dist/controller-service.d.ts +1 -0
  130. package/dist/controller-service.js +19 -0
  131. package/dist/controller-supervisor.d.ts +30 -0
  132. package/dist/controller-supervisor.js +104 -0
  133. package/dist/converse.d.ts +304 -0
  134. package/dist/converse.js +849 -0
  135. package/dist/coordinator-proposals.d.ts +29 -0
  136. package/dist/coordinator-proposals.js +137 -0
  137. package/dist/coordinator.d.ts +186 -0
  138. package/dist/coordinator.js +447 -0
  139. package/dist/credentials-ui.d.ts +25 -0
  140. package/dist/credentials-ui.js +48 -0
  141. package/dist/daemon.d.ts +209 -0
  142. package/dist/daemon.js +604 -0
  143. package/dist/decision.d.ts +120 -0
  144. package/dist/decision.js +388 -0
  145. package/dist/demo.d.ts +55 -0
  146. package/dist/demo.js +992 -0
  147. package/dist/desktop-access.d.ts +28 -0
  148. package/dist/desktop-access.js +88 -0
  149. package/dist/desktop-bundle.d.ts +16 -0
  150. package/dist/desktop-bundle.js +74 -0
  151. package/dist/desktop-host.d.ts +54 -0
  152. package/dist/desktop-host.js +508 -0
  153. package/dist/desktop-update-gate.d.ts +12 -0
  154. package/dist/desktop-update-gate.js +91 -0
  155. package/dist/desktop-update-recovery.d.ts +14 -0
  156. package/dist/desktop-update-recovery.js +243 -0
  157. package/dist/desktop-update.d.ts +110 -0
  158. package/dist/desktop-update.js +740 -0
  159. package/dist/discord-api.d.ts +20 -0
  160. package/dist/discord-api.js +138 -0
  161. package/dist/discord-chat.d.ts +16 -0
  162. package/dist/discord-chat.js +381 -0
  163. package/dist/discord-settings.d.ts +7 -0
  164. package/dist/discord-settings.js +75 -0
  165. package/dist/discord.d.ts +10 -0
  166. package/dist/discord.js +169 -0
  167. package/dist/discover.d.ts +75 -0
  168. package/dist/discover.js +150 -0
  169. package/dist/dispatch.d.ts +100 -0
  170. package/dist/dispatch.js +311 -0
  171. package/dist/dispose.d.ts +161 -0
  172. package/dist/dispose.js +803 -0
  173. package/dist/email-settings.d.ts +34 -0
  174. package/dist/email-settings.js +50 -0
  175. package/dist/envelope.d.ts +47 -0
  176. package/dist/envelope.js +105 -0
  177. package/dist/evidence-pack.d.ts +185 -0
  178. package/dist/evidence-pack.js +258 -0
  179. package/dist/evidence.d.ts +345 -0
  180. package/dist/evidence.js +782 -0
  181. package/dist/exec.d.ts +285 -0
  182. package/dist/exec.js +1841 -0
  183. package/dist/exhaustion.d.ts +85 -0
  184. package/dist/exhaustion.js +141 -0
  185. package/dist/export-ui.d.ts +4 -0
  186. package/dist/export-ui.js +17 -0
  187. package/dist/export.d.ts +48 -0
  188. package/dist/export.js +305 -0
  189. package/dist/flow-actions.d.ts +77 -0
  190. package/dist/flow-actions.js +203 -0
  191. package/dist/flow-code.d.ts +50 -0
  192. package/dist/flow-code.js +159 -0
  193. package/dist/flow-draft.d.ts +38 -0
  194. package/dist/flow-draft.js +80 -0
  195. package/dist/flow-engine.d.ts +66 -0
  196. package/dist/flow-engine.js +416 -0
  197. package/dist/flow-insights.d.ts +95 -0
  198. package/dist/flow-insights.js +149 -0
  199. package/dist/flow-live.d.ts +24 -0
  200. package/dist/flow-live.js +150 -0
  201. package/dist/flow-people.d.ts +24 -0
  202. package/dist/flow-people.js +91 -0
  203. package/dist/flow-replies.d.ts +48 -0
  204. package/dist/flow-replies.js +113 -0
  205. package/dist/flow-scripts.d.ts +38 -0
  206. package/dist/flow-scripts.js +73 -0
  207. package/dist/flow-secrets.d.ts +13 -0
  208. package/dist/flow-secrets.js +45 -0
  209. package/dist/flow-sort.d.ts +82 -0
  210. package/dist/flow-sort.js +153 -0
  211. package/dist/flow-steps.d.ts +40 -0
  212. package/dist/flow-steps.js +408 -0
  213. package/dist/flow-triggers.d.ts +244 -0
  214. package/dist/flow-triggers.js +959 -0
  215. package/dist/flows-ui.d.ts +20 -0
  216. package/dist/flows-ui.js +175 -0
  217. package/dist/flows.d.ts +229 -0
  218. package/dist/flows.js +968 -0
  219. package/dist/fonts.d.ts +19 -0
  220. package/dist/fonts.js +19 -0
  221. package/dist/gaps.d.ts +35 -0
  222. package/dist/gaps.js +101 -0
  223. package/dist/gate-failure.d.ts +40 -0
  224. package/dist/gate-failure.js +66 -0
  225. package/dist/git.d.ts +54 -0
  226. package/dist/git.js +94 -0
  227. package/dist/google-mail.d.ts +58 -0
  228. package/dist/google-mail.js +151 -0
  229. package/dist/grant.d.ts +136 -0
  230. package/dist/grant.js +238 -0
  231. package/dist/graph.d.ts +164 -0
  232. package/dist/graph.js +383 -0
  233. package/dist/guides.d.ts +22 -0
  234. package/dist/guides.js +363 -0
  235. package/dist/held.d.ts +150 -0
  236. package/dist/held.js +799 -0
  237. package/dist/invoke.d.ts +112 -0
  238. package/dist/invoke.js +780 -0
  239. package/dist/issues.d.ts +41 -0
  240. package/dist/issues.js +131 -0
  241. package/dist/job-object-helper.ps1 +252 -0
  242. package/dist/jsonl-discriminants.d.ts +11 -0
  243. package/dist/jsonl-discriminants.js +93 -0
  244. package/dist/keys.d.ts +104 -0
  245. package/dist/keys.js +229 -0
  246. package/dist/kits-ui.d.ts +17 -0
  247. package/dist/kits-ui.js +46 -0
  248. package/dist/kits.d.ts +86 -0
  249. package/dist/kits.js +215 -0
  250. package/dist/knowledge-cli.d.ts +101 -0
  251. package/dist/knowledge-cli.js +92 -0
  252. package/dist/knowledge-ui.d.ts +24 -0
  253. package/dist/knowledge-ui.js +40 -0
  254. package/dist/lead-context.d.ts +8 -0
  255. package/dist/lead-context.js +56 -0
  256. package/dist/lead-follow.d.ts +22 -0
  257. package/dist/lead-follow.js +167 -0
  258. package/dist/lead-status.d.ts +75 -0
  259. package/dist/lead-status.js +285 -0
  260. package/dist/ledger-chain.d.ts +46 -0
  261. package/dist/ledger-chain.js +187 -0
  262. package/dist/ledger-csv.d.ts +4 -0
  263. package/dist/ledger-csv.js +11 -0
  264. package/dist/ledger-view.d.ts +24 -0
  265. package/dist/ledger-view.js +58 -0
  266. package/dist/limits-ui.d.ts +26 -0
  267. package/dist/limits-ui.js +67 -0
  268. package/dist/link.d.ts +72 -0
  269. package/dist/link.js +217 -0
  270. package/dist/live.d.ts +96 -0
  271. package/dist/live.js +366 -0
  272. package/dist/liveness.d.ts +30 -0
  273. package/dist/liveness.js +42 -0
  274. package/dist/log.d.ts +7 -0
  275. package/dist/log.js +25 -0
  276. package/dist/mailbox.d.ts +57 -0
  277. package/dist/mailbox.js +150 -0
  278. package/dist/maintenance.d.ts +11 -0
  279. package/dist/maintenance.js +35 -0
  280. package/dist/mate-cli.d.ts +52 -0
  281. package/dist/mate-cli.js +345 -0
  282. package/dist/mate-contract.d.ts +10 -0
  283. package/dist/mate-contract.js +30 -0
  284. package/dist/mate-doors.d.ts +69 -0
  285. package/dist/mate-doors.js +548 -0
  286. package/dist/mate-progress.d.ts +29 -0
  287. package/dist/mate-progress.js +121 -0
  288. package/dist/mate-tools.d.ts +94 -0
  289. package/dist/mate-tools.js +1878 -0
  290. package/dist/mate.d.ts +92 -0
  291. package/dist/mate.js +435 -0
  292. package/dist/mcp-connect.d.ts +103 -0
  293. package/dist/mcp-connect.js +252 -0
  294. package/dist/mcp.d.ts +27 -0
  295. package/dist/mcp.js +651 -0
  296. package/dist/memory-cli.d.ts +466 -0
  297. package/dist/memory-cli.js +111 -0
  298. package/dist/memory-pass.d.ts +160 -0
  299. package/dist/memory-pass.js +409 -0
  300. package/dist/metrics.d.ts +3 -0
  301. package/dist/metrics.js +56 -0
  302. package/dist/mobile-viewport.d.ts +3 -0
  303. package/dist/mobile-viewport.js +37 -0
  304. package/dist/model-catalog.d.ts +118 -0
  305. package/dist/model-catalog.js +376 -0
  306. package/dist/models-cli.d.ts +102 -0
  307. package/dist/models-cli.js +66 -0
  308. package/dist/models-ui.d.ts +34 -0
  309. package/dist/models-ui.js +68 -0
  310. package/dist/modes.d.ts +80 -0
  311. package/dist/modes.js +182 -0
  312. package/dist/monitoring-settings.d.ts +44 -0
  313. package/dist/monitoring-settings.js +173 -0
  314. package/dist/monitoring-ui.d.ts +20 -0
  315. package/dist/monitoring-ui.js +51 -0
  316. package/dist/monitoring.d.ts +34 -0
  317. package/dist/monitoring.js +221 -0
  318. package/dist/names.d.ts +67 -0
  319. package/dist/names.js +102 -0
  320. package/dist/observations.d.ts +40 -0
  321. package/dist/observations.js +211 -0
  322. package/dist/oidc.d.ts +71 -0
  323. package/dist/oidc.js +182 -0
  324. package/dist/onboard.d.ts +108 -0
  325. package/dist/onboard.js +325 -0
  326. package/dist/openrouter-models.d.ts +30 -0
  327. package/dist/openrouter-models.js +120 -0
  328. package/dist/operate.d.ts +118 -0
  329. package/dist/operate.js +11321 -0
  330. package/dist/peek-cli.d.ts +97 -0
  331. package/dist/peek-cli.js +215 -0
  332. package/dist/peek.d.ts +134 -0
  333. package/dist/peek.js +578 -0
  334. package/dist/phase-routing.d.ts +307 -0
  335. package/dist/phase-routing.js +658 -0
  336. package/dist/plan-auto.d.ts +14 -0
  337. package/dist/plan-auto.js +92 -0
  338. package/dist/plan.d.ts +186 -0
  339. package/dist/plan.js +401 -0
  340. package/dist/planner-source.d.ts +251 -0
  341. package/dist/planner-source.js +460 -0
  342. package/dist/planner.d.ts +75 -0
  343. package/dist/planner.js +992 -0
  344. package/dist/policy-ui.d.ts +20 -0
  345. package/dist/policy-ui.js +41 -0
  346. package/dist/policy.d.ts +111 -0
  347. package/dist/policy.js +231 -0
  348. package/dist/prepared-evidence.d.ts +16 -0
  349. package/dist/prepared-evidence.js +96 -0
  350. package/dist/pricing.d.ts +45 -0
  351. package/dist/pricing.js +77 -0
  352. package/dist/principal.d.ts +42 -0
  353. package/dist/principal.js +82 -0
  354. package/dist/probe.d.ts +46 -0
  355. package/dist/probe.js +79 -0
  356. package/dist/process-custody.d.ts +17 -0
  357. package/dist/process-custody.js +91 -0
  358. package/dist/process-liveness.d.ts +10 -0
  359. package/dist/process-liveness.js +81 -0
  360. package/dist/process-recovery-anchor.d.ts +75 -0
  361. package/dist/process-recovery-anchor.js +188 -0
  362. package/dist/process-recovery-coalition.d.ts +25 -0
  363. package/dist/process-recovery-coalition.js +105 -0
  364. package/dist/process-recovery-eligibility.d.ts +178 -0
  365. package/dist/process-recovery-eligibility.js +170 -0
  366. package/dist/process-recovery-native.d.ts +172 -0
  367. package/dist/process-recovery-native.js +592 -0
  368. package/dist/process-recovery-provenance.d.ts +163 -0
  369. package/dist/process-recovery-provenance.js +292 -0
  370. package/dist/process-recovery-services.d.ts +56 -0
  371. package/dist/process-recovery-services.js +191 -0
  372. package/dist/process-recovery-settlement.d.ts +32 -0
  373. package/dist/process-recovery-settlement.js +132 -0
  374. package/dist/process-recovery.d.ts +41 -0
  375. package/dist/process-recovery.js +91 -0
  376. package/dist/process-tree.d.ts +41 -0
  377. package/dist/process-tree.js +264 -0
  378. package/dist/project-access.d.ts +14 -0
  379. package/dist/project-access.js +34 -0
  380. package/dist/project-cli.d.ts +18 -0
  381. package/dist/project-cli.js +104 -0
  382. package/dist/project-delete-ui.d.ts +22 -0
  383. package/dist/project-delete-ui.js +48 -0
  384. package/dist/project-delete.d.ts +55 -0
  385. package/dist/project-delete.js +413 -0
  386. package/dist/project-knowledge.d.ts +111 -0
  387. package/dist/project-knowledge.js +241 -0
  388. package/dist/project-learning.d.ts +100 -0
  389. package/dist/project-learning.js +438 -0
  390. package/dist/project-memory.d.ts +81 -0
  391. package/dist/project-memory.js +264 -0
  392. package/dist/project-skills.d.ts +152 -0
  393. package/dist/project-skills.js +660 -0
  394. package/dist/project-tools.d.ts +219 -0
  395. package/dist/project-tools.js +796 -0
  396. package/dist/project.d.ts +61 -0
  397. package/dist/project.js +125 -0
  398. package/dist/prompt.d.ts +22 -0
  399. package/dist/prompt.js +72 -0
  400. package/dist/proof.d.ts +513 -0
  401. package/dist/proof.js +1140 -0
  402. package/dist/proposal.d.ts +82 -0
  403. package/dist/proposal.js +210 -0
  404. package/dist/provider-connection.d.ts +22 -0
  405. package/dist/provider-connection.js +98 -0
  406. package/dist/provider-limits.d.ts +35 -0
  407. package/dist/provider-limits.js +95 -0
  408. package/dist/provider.d.ts +279 -0
  409. package/dist/provider.js +935 -0
  410. package/dist/publish.d.ts +107 -0
  411. package/dist/publish.js +650 -0
  412. package/dist/pulls.d.ts +118 -0
  413. package/dist/pulls.js +240 -0
  414. package/dist/push.d.ts +95 -0
  415. package/dist/push.js +353 -0
  416. package/dist/quality.d.ts +8 -0
  417. package/dist/quality.js +6 -0
  418. package/dist/recipe-ui.d.ts +18 -0
  419. package/dist/recipe-ui.js +152 -0
  420. package/dist/recipes.d.ts +81 -0
  421. package/dist/recipes.js +332 -0
  422. package/dist/remote.d.ts +67 -0
  423. package/dist/remote.js +122 -0
  424. package/dist/render.d.ts +64 -0
  425. package/dist/render.js +587 -0
  426. package/dist/report-summary.d.ts +13 -0
  427. package/dist/report-summary.js +15 -0
  428. package/dist/repos.d.ts +65 -0
  429. package/dist/repos.js +277 -0
  430. package/dist/repository-context-ui.d.ts +2 -0
  431. package/dist/repository-context-ui.js +10 -0
  432. package/dist/repository-context.d.ts +67 -0
  433. package/dist/repository-context.js +376 -0
  434. package/dist/restart-certification.d.ts +134 -0
  435. package/dist/restart-certification.js +226 -0
  436. package/dist/result-actions.d.ts +28 -0
  437. package/dist/result-actions.js +270 -0
  438. package/dist/result-completion.d.ts +18 -0
  439. package/dist/result-completion.js +66 -0
  440. package/dist/result-review.d.ts +213 -0
  441. package/dist/result-review.js +430 -0
  442. package/dist/retention-ui.d.ts +12 -0
  443. package/dist/retention-ui.js +39 -0
  444. package/dist/retention.d.ts +72 -0
  445. package/dist/retention.js +288 -0
  446. package/dist/review-context.d.ts +243 -0
  447. package/dist/review-context.js +1043 -0
  448. package/dist/review-evidence.d.ts +33 -0
  449. package/dist/review-evidence.js +56 -0
  450. package/dist/reviewer.d.ts +97 -0
  451. package/dist/reviewer.js +202 -0
  452. package/dist/routine.d.ts +228 -0
  453. package/dist/routine.js +764 -0
  454. package/dist/runner.d.ts +266 -0
  455. package/dist/runner.js +399 -0
  456. package/dist/scan.d.ts +32 -0
  457. package/dist/scan.js +92 -0
  458. package/dist/scope.d.ts +695 -0
  459. package/dist/scope.js +1176 -0
  460. package/dist/scout-report.d.ts +42 -0
  461. package/dist/scout-report.js +108 -0
  462. package/dist/scout.d.ts +69 -0
  463. package/dist/scout.js +351 -0
  464. package/dist/serve.d.ts +313 -0
  465. package/dist/serve.js +21776 -0
  466. package/dist/session-brief.d.ts +8 -0
  467. package/dist/session-brief.js +24 -0
  468. package/dist/session-cli.d.ts +13 -0
  469. package/dist/session-cli.js +284 -0
  470. package/dist/session-contract.d.ts +183 -0
  471. package/dist/session-contract.js +122 -0
  472. package/dist/session-http.d.ts +19 -0
  473. package/dist/session-http.js +154 -0
  474. package/dist/session-server.d.ts +12 -0
  475. package/dist/session-server.js +37 -0
  476. package/dist/session-service.d.ts +22 -0
  477. package/dist/session-service.js +157 -0
  478. package/dist/setup-guide.d.ts +39 -0
  479. package/dist/setup-guide.js +93 -0
  480. package/dist/sign-in-guard.d.ts +45 -0
  481. package/dist/sign-in-guard.js +74 -0
  482. package/dist/skills-ui.d.ts +11 -0
  483. package/dist/skills-ui.js +58 -0
  484. package/dist/skills.d.ts +97 -0
  485. package/dist/skills.js +276 -0
  486. package/dist/slack-api.d.ts +55 -0
  487. package/dist/slack-api.js +229 -0
  488. package/dist/slack-chat.d.ts +28 -0
  489. package/dist/slack-chat.js +413 -0
  490. package/dist/slack-settings.d.ts +7 -0
  491. package/dist/slack-settings.js +75 -0
  492. package/dist/slack-state.d.ts +13 -0
  493. package/dist/slack-state.js +9 -0
  494. package/dist/slack.d.ts +13 -0
  495. package/dist/slack.js +167 -0
  496. package/dist/spend-ui.d.ts +28 -0
  497. package/dist/spend-ui.js +95 -0
  498. package/dist/spend.d.ts +142 -0
  499. package/dist/spend.js +313 -0
  500. package/dist/sqlite-runtime.d.ts +3 -0
  501. package/dist/sqlite-runtime.js +6 -0
  502. package/dist/sso-settings.d.ts +33 -0
  503. package/dist/sso-settings.js +103 -0
  504. package/dist/sso-ui.d.ts +22 -0
  505. package/dist/sso-ui.js +39 -0
  506. package/dist/storage.d.ts +15 -0
  507. package/dist/storage.js +84 -0
  508. package/dist/store.d.ts +7754 -0
  509. package/dist/store.js +23279 -0
  510. package/dist/structured-output.d.ts +58 -0
  511. package/dist/structured-output.js +103 -0
  512. package/dist/style-asset.d.ts +7 -0
  513. package/dist/style-asset.js +41 -0
  514. package/dist/subscription-chat.d.ts +46 -0
  515. package/dist/subscription-chat.js +275 -0
  516. package/dist/summary.d.ts +33 -0
  517. package/dist/summary.js +71 -0
  518. package/dist/supervisor.mjs +297 -0
  519. package/dist/surface.d.ts +63 -0
  520. package/dist/surface.js +352 -0
  521. package/dist/sync.d.ts +94 -0
  522. package/dist/sync.js +279 -0
  523. package/dist/task-composer.d.ts +13 -0
  524. package/dist/task-composer.js +52 -0
  525. package/dist/task-control.d.ts +165 -0
  526. package/dist/task-control.js +214 -0
  527. package/dist/task-outcome-cli.d.ts +10 -0
  528. package/dist/task-outcome-cli.js +92 -0
  529. package/dist/task-text.d.ts +30 -0
  530. package/dist/task-text.js +41 -0
  531. package/dist/team-cli.d.ts +15 -0
  532. package/dist/team-cli.js +618 -0
  533. package/dist/team-contract.d.ts +92 -0
  534. package/dist/team-contract.js +1 -0
  535. package/dist/team-http.d.ts +17 -0
  536. package/dist/team-http.js +144 -0
  537. package/dist/team-leads.d.ts +81 -0
  538. package/dist/team-leads.js +565 -0
  539. package/dist/team-runtime.d.ts +27 -0
  540. package/dist/team-runtime.js +264 -0
  541. package/dist/team-ui.d.ts +3 -0
  542. package/dist/team-ui.js +9 -0
  543. package/dist/team-updates.d.ts +8 -0
  544. package/dist/team-updates.js +57 -0
  545. package/dist/teammate-admin.d.ts +53 -0
  546. package/dist/teammate-admin.js +111 -0
  547. package/dist/teammate-desk.d.ts +60 -0
  548. package/dist/teammate-desk.js +154 -0
  549. package/dist/teammate-memory.d.ts +64 -0
  550. package/dist/teammate-memory.js +196 -0
  551. package/dist/teammate-question.d.ts +59 -0
  552. package/dist/teammate-question.js +175 -0
  553. package/dist/teammate-tools.d.ts +108 -0
  554. package/dist/teammate-tools.js +271 -0
  555. package/dist/teammate-week.d.ts +68 -0
  556. package/dist/teammate-week.js +132 -0
  557. package/dist/teammate-work.d.ts +76 -0
  558. package/dist/teammate-work.js +280 -0
  559. package/dist/teammates-ui.d.ts +20 -0
  560. package/dist/teammates-ui.js +166 -0
  561. package/dist/teammates.d.ts +180 -0
  562. package/dist/teammates.js +253 -0
  563. package/dist/teams-api.d.ts +33 -0
  564. package/dist/teams-api.js +204 -0
  565. package/dist/teams-chat.d.ts +26 -0
  566. package/dist/teams-chat.js +215 -0
  567. package/dist/teams-settings.d.ts +12 -0
  568. package/dist/teams-settings.js +57 -0
  569. package/dist/teams.d.ts +21 -0
  570. package/dist/teams.js +124 -0
  571. package/dist/telegram-flow.d.ts +49 -0
  572. package/dist/telegram-flow.js +110 -0
  573. package/dist/telegram-mate.d.ts +137 -0
  574. package/dist/telegram-mate.js +707 -0
  575. package/dist/telegram-progress.d.ts +14 -0
  576. package/dist/telegram-progress.js +129 -0
  577. package/dist/telegram-settings.d.ts +9 -0
  578. package/dist/telegram-settings.js +36 -0
  579. package/dist/telegram-status.d.ts +75 -0
  580. package/dist/telegram-status.js +265 -0
  581. package/dist/telegram-team.d.ts +85 -0
  582. package/dist/telegram-team.js +359 -0
  583. package/dist/telegram.d.ts +230 -0
  584. package/dist/telegram.js +1931 -0
  585. package/dist/templates.d.ts +65 -0
  586. package/dist/templates.js +118 -0
  587. package/dist/tool-launcher.d.ts +1 -0
  588. package/dist/tool-launcher.js +101 -0
  589. package/dist/tools-ui.d.ts +30 -0
  590. package/dist/tools-ui.js +40 -0
  591. package/dist/transitions-recipes.d.ts +1 -0
  592. package/dist/transitions-recipes.js +203 -0
  593. package/dist/tree-proof.d.ts +34 -0
  594. package/dist/tree-proof.js +58 -0
  595. package/dist/verification-evidence.d.ts +44 -0
  596. package/dist/verification-evidence.js +238 -0
  597. package/dist/version.d.ts +2 -0
  598. package/dist/version.js +10 -0
  599. package/dist/webhooks.d.ts +91 -0
  600. package/dist/webhooks.js +283 -0
  601. package/dist/work-index.d.ts +68 -0
  602. package/dist/work-index.js +384 -0
  603. package/dist/work-summary.d.ts +57 -0
  604. package/dist/work-summary.js +116 -0
  605. package/dist/workspace-motion.d.ts +4 -0
  606. package/dist/workspace-motion.js +133 -0
  607. package/dist/workspace-revision.d.ts +19 -0
  608. package/dist/workspace-revision.js +92 -0
  609. package/dist/workspace-ui.d.ts +204 -0
  610. package/dist/workspace-ui.js +411 -0
  611. package/dist/worktree-notices.d.ts +13 -0
  612. package/dist/worktree-notices.js +17 -0
  613. package/dist/worktree.d.ts +249 -0
  614. package/dist/worktree.js +720 -0
  615. package/package.json +122 -0
  616. package/scripts/canary-assertions.mjs +42 -0
  617. package/scripts/crash-canary.mjs +253 -0
  618. package/scripts/fixtures/crash-process.mjs +74 -0
  619. package/scripts/fixtures/pilot-scenarios.mjs +85 -0
  620. package/scripts/fixtures/restart-service.mjs +27 -0
  621. package/scripts/launchd-certification.mjs +143 -0
  622. package/scripts/pilot.mjs +121 -0
  623. package/scripts/proof-preflight.mjs +140 -0
  624. package/scripts/provider-canary.mjs +379 -0
  625. package/scripts/recovery-canary.mjs +135 -0
  626. package/scripts/restart-certification.mjs +98 -0
@@ -0,0 +1,650 @@
1
+ /**
2
+ * Publication (§M4): built work becomes a pushed branch and a pull request,
3
+ * under a grant whose every term was shown before the yes, through a durable
4
+ * intent that survives any crash between "done" and "opened".
5
+ *
6
+ * Two external writes, two named capabilities. Pushing a branch and opening
7
+ * a PR hurt differently, so `push-branch` and `open-pr` are granted
8
+ * separately, with the exact GitHub repository, remote, allowed head
9
+ * prefix, and base branch fixed at grant time — the worker re-proves all of
10
+ * them at publish time, because a grant revoked overnight must stop the
11
+ * morning's pushes.
12
+ *
13
+ * The push names the exact SHA the fenced completion accepted —
14
+ * `git push <remote> <sha>:refs/heads/<head>` — never "whatever the branch
15
+ * points at now". The PR is adopt-or-create: the worker first asks GitHub
16
+ * for an open PR on that head and adopts it, so a crash after `pr create`
17
+ * and before the record lands cannot mint a twin. The body is generated
18
+ * only from durable state (task, scope, run record, answered decisions),
19
+ * with the agent's prose fenced in a quote block so `Fixes #…` or an
20
+ * @-mention in its conclusion cannot acquire GitHub semantics.
21
+ */
22
+ import { createHash } from "node:crypto";
23
+ import { protectedPublicationHold } from "./result-completion.js";
24
+ import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
25
+ import { tmpdir } from "node:os";
26
+ import { join } from "node:path";
27
+ import { run as execRun } from "./exec.js";
28
+ import { summarizeChecks } from "./pulls.js";
29
+ import { headWithin } from "./names.js";
30
+ export const BODY_TEMPLATE_VERSION = 2;
31
+ /** After this many failed attempts a publication stops retrying and pages instead. */
32
+ export const MAX_PUBLISH_ATTEMPTS = 5;
33
+ const EXEC_TIMEOUT_MS = 60_000;
34
+ /** The grant's terms, in the words the approver must be shown before agreeing. */
35
+ export function describePublicationGrant(grant) {
36
+ return [
37
+ ` repo ${grant.repo}`,
38
+ ` github ${grant.githubRepo}`,
39
+ ` remote ${grant.remote}`,
40
+ ` may push branches under ${grant.headPrefix} — nothing else, ever`,
41
+ ` base PRs target ${grant.base}`,
42
+ ` capabilities ${grant.capabilities.join(", ")}`,
43
+ ` tasks ${grant.selector === "ours" ? "only tasks Toolroll created or was given" : "any task"}`,
44
+ ` mode ${grant.draft ? "draft PRs" : "ready-for-review PRs"}`,
45
+ ];
46
+ }
47
+ /**
48
+ * The PR body, from durable state only — the same rows produce the same
49
+ * bytes after any crash, which is what lets the intent carry a hash of it.
50
+ */
51
+ /** One line of untrusted prose, kept inline: newlines flatten so no agent
52
+ * text can START a Markdown line and gain semantics (audit IV-9) —
53
+ * `Closes #123` at line start closes; mid-sentence it is words. */
54
+ function mdInline(text) {
55
+ return text.replace(/[\r\n\u2028\u2029]+/g, " ").trim();
56
+ }
57
+ /** Untrusted multi-line prose as an INDENTED code block: backticks cannot
58
+ * terminate indentation, so no conclusion escapes its quoting (audit IV-9). */
59
+ function mdIndented(text) {
60
+ return text.replace(/\r\n?/g, "\n").split("\n").map(line => ` ${line}`);
61
+ }
62
+ export function publicationBody(store, publication) {
63
+ const run = store.getRun(publication.run);
64
+ const taskId = store.externalIdFor(publication.taskRef) ?? "?";
65
+ const task = taskId === "?" ? null : store.getTask(taskId);
66
+ const scope = taskId === "?" ? null : store.getScope(taskId);
67
+ const answers = store.answersFor(publication.run);
68
+ const lines = [
69
+ `## ${mdInline(task?.title ?? taskId)}`,
70
+ "",
71
+ ...(scope === null ? [] : [`**Goal:** ${mdInline(scope.goal)}`, ""]),
72
+ `Built unattended by Toolroll (task \`${taskId}\`, run #${publication.run}).`,
73
+ `Base \`${run?.baseRevision ?? "?"}\` → head \`${publication.headSha}\`.`,
74
+ ];
75
+ if (answers.length > 0) {
76
+ lines.push("", "**Decisions a person answered along the way:**");
77
+ for (const answer of answers) {
78
+ lines.push(`- ${mdInline(answer.decision.question)} → **${mdInline(answer.choice)}** (${mdInline(answer.decision.answeredBy ?? "?")})`);
79
+ }
80
+ }
81
+ // The agent's own words ride INDENTED: an indented code block has no
82
+ // terminator an agent's text could supply, so prose stays prose.
83
+ if (run?.handoff !== null && run?.handoff !== undefined) {
84
+ lines.push("", "**The agent's conclusion, quoted:**", "", ...mdIndented(run.handoff));
85
+ }
86
+ lines.push("", `*Template v${BODY_TEMPLATE_VERSION} — generated from run records, reproducible.*`);
87
+ return lines.join("\n");
88
+ }
89
+ export function bodyHashOf(body) {
90
+ return createHash("sha256").update(`v${BODY_TEMPLATE_VERSION}\n${body}`, "utf8").digest("hex");
91
+ }
92
+ /**
93
+ * One pass over everything owed. Each phase re-proves the grant and moves
94
+ * exactly one durable state — a crash at any point leaves a row the next
95
+ * pass picks up where this one stopped.
96
+ */
97
+ export async function publishPass(store, options) {
98
+ const clock = options.clock ?? (() => new Date());
99
+ const exec = options.exec ?? execRun;
100
+ const report = { pushed: 0, opened: 0, adopted: 0, failed: 0, problems: [] };
101
+ for (const publication of store.pendingPublications()) {
102
+ // The secret gate (audit IV-7): a run whose accepted diff carried a
103
+ // high-confidence secret shape publishes NOTHING — pushing the branch
104
+ // would hand the credential to the remote. Fail closed, say why once,
105
+ // and leave the person to rewrite the branch and requeue.
106
+ if (store.hasRedactedTerminalDiff(publication.run)) {
107
+ concede(store, publication, "the accepted diff carries a detected secret — rewrite the branch before anything publishes", report, clock);
108
+ continue;
109
+ }
110
+ // The grant is re-read per publication, live: revocation is immediate,
111
+ // and an intent created under a grant that has since died goes nowhere.
112
+ const grant = store.publicationGrantFor(options.repo);
113
+ const verdict = permitsPublication(grant, publication);
114
+ if (!verdict.ok) {
115
+ report.problems.push(`publication ${publication.id}: ${verdict.message}`);
116
+ const attempts = store.recordPublicationError(publication.id, verdict.message, clock());
117
+ if (attempts >= MAX_PUBLISH_ATTEMPTS)
118
+ concede(store, publication, verdict.message, report, clock);
119
+ continue;
120
+ }
121
+ // v102: separation of duties holds the push itself (so no PR and no merge follow) until a second person
122
+ // has seen a result that changed protected files on a one-person approval.
123
+ if (publication.state === "intended") {
124
+ const held = protectedPublicationHold(store, publication.run, options.evidenceRoot);
125
+ if (held !== null) {
126
+ report.problems.push(`publication ${publication.id}: ${held}`);
127
+ continue;
128
+ }
129
+ }
130
+ if (publication.state === "intended") {
131
+ // The exact SHA to the exact ref. A branch that moved since completion
132
+ // is not what was accepted, and this push cannot be talked into it.
133
+ const push = await exec("git", ["push", publication.remote, `${publication.headSha}:refs/heads/${publication.head}`], { cwd: options.repo, timeoutMs: EXEC_TIMEOUT_MS });
134
+ if (push.code !== 0) {
135
+ const why = push.notFound ? "git is not on PATH" : firstLine(push.stderr) || `git push exited ${push.code}`;
136
+ const attempts = store.recordPublicationError(publication.id, why, clock());
137
+ report.problems.push(`publication ${publication.id}: ${why}`);
138
+ if (attempts >= MAX_PUBLISH_ATTEMPTS)
139
+ concede(store, publication, why, report, clock);
140
+ continue;
141
+ }
142
+ store.markPublicationPushed(publication.id, clock());
143
+ report.pushed++;
144
+ publication.state = "pushed";
145
+ }
146
+ if (publication.state === "pushed") {
147
+ const outcome = await openOrAdopt(store, publication, grant, exec, clock);
148
+ if (!outcome.ok) {
149
+ const attempts = store.recordPublicationError(publication.id, outcome.why, clock());
150
+ report.problems.push(`publication ${publication.id}: ${outcome.why}`);
151
+ if (attempts >= MAX_PUBLISH_ATTEMPTS)
152
+ concede(store, publication, outcome.why, report, clock);
153
+ continue;
154
+ }
155
+ if (outcome.adopted)
156
+ report.adopted++;
157
+ else
158
+ report.opened++;
159
+ }
160
+ }
161
+ return report;
162
+ }
163
+ /** Why a publication may not proceed — checked against the whole grant, per phase. */
164
+ export function permitsPublication(grant, publication) {
165
+ if (grant === null)
166
+ return { ok: false, message: "no live publication grant — nothing may be pushed" };
167
+ if (grant.githubRepo !== publication.githubRepo || grant.remote !== publication.remote || grant.base !== publication.base) {
168
+ return { ok: false, message: "the intent's terms no longer match the live grant" };
169
+ }
170
+ if (!headWithin(publication.head, grant.headPrefix)) {
171
+ return { ok: false, message: `head ${publication.head} is outside the granted prefix ${grant.headPrefix}` };
172
+ }
173
+ const needs = publication.state === "intended" ? "push-branch" : "open-pr";
174
+ if (!grant.capabilities.includes(needs)) {
175
+ return { ok: false, message: `the grant does not include ${needs}` };
176
+ }
177
+ return { ok: true };
178
+ }
179
+ async function openOrAdopt(store, publication, grant, exec, clock) {
180
+ // Adopt first: a PR this head already has — whoever opened it, including
181
+ // a crashed earlier attempt — is the PR. Creating a second would be the
182
+ // duplicate the durable intent exists to prevent.
183
+ const listed = await exec("gh", [
184
+ "pr", "list",
185
+ "--repo", publication.githubRepo,
186
+ "--head", publication.head,
187
+ // The base filter (merge-grant finding 21): a same-head PR aimed at
188
+ // a DIFFERENT base is not this publication's PR and must never be
189
+ // adopted under authority granted for another branch.
190
+ "--base", publication.base,
191
+ "--state", "open",
192
+ "--json", "number,url",
193
+ ], { timeoutMs: EXEC_TIMEOUT_MS });
194
+ if (listed.code !== 0) {
195
+ return {
196
+ ok: false,
197
+ why: listed.notFound ? "gh is not on PATH" : firstLine(listed.stderr) || `gh pr list exited ${listed.code}`,
198
+ };
199
+ }
200
+ let existing;
201
+ try {
202
+ existing = JSON.parse(listed.stdout || "[]");
203
+ }
204
+ catch {
205
+ return { ok: false, why: "gh pr list said something that is not JSON" };
206
+ }
207
+ const adopted = existing[0];
208
+ if (adopted !== undefined) {
209
+ store.markPublicationOpened(publication.id, adopted.number, adopted.url, clock());
210
+ enqueueOpened(store, publication, adopted.url, clock);
211
+ return { ok: true, adopted: true };
212
+ }
213
+ const body = publicationBody(store, publication);
214
+ const taskId = store.externalIdFor(publication.taskRef) ?? publication.head;
215
+ const title = store.getTask(taskId)?.title ?? taskId;
216
+ // --body-file, not --body: the body quotes an agent and a shell must not
217
+ // meet it. The file lives in a private temp dir for exactly one call.
218
+ const dir = mkdtempSync(join(tmpdir(), "standing-orders-pr-"));
219
+ try {
220
+ const bodyFile = join(dir, "body.md");
221
+ writeFileSync(bodyFile, body, { mode: 0o600 });
222
+ const created = await exec("gh", [
223
+ "pr", "create",
224
+ "--repo", publication.githubRepo,
225
+ "--base", publication.base,
226
+ "--head", publication.head,
227
+ "--title", `${taskId}: ${title}`.slice(0, 200),
228
+ "--body-file", bodyFile,
229
+ ...(publication.draft ? ["--draft"] : []),
230
+ ], { timeoutMs: EXEC_TIMEOUT_MS, env: { GH_PROMPT_DISABLED: "1", GH_NO_UPDATE_NOTIFIER: "1" } });
231
+ if (created.code !== 0) {
232
+ return { ok: false, why: firstLine(created.stderr) || `gh pr create exited ${created.code}` };
233
+ }
234
+ const url = firstLine(created.stdout);
235
+ const number = Number(/\/pull\/(\d+)/.exec(url)?.[1] ?? 0);
236
+ if (number === 0)
237
+ return { ok: false, why: "gh pr create returned no PR URL" };
238
+ store.markPublicationOpened(publication.id, number, url, clock());
239
+ enqueueOpened(store, publication, url, clock);
240
+ return { ok: true, adopted: false };
241
+ }
242
+ finally {
243
+ rmSync(dir, { recursive: true, force: true });
244
+ }
245
+ }
246
+ /**
247
+ * Read CI for every PR this control plane opened — structured rollup and the
248
+ * exact head OID, never a command's exit status (finding 18). Episodes are
249
+ * keyed `ci:<pr>:<headOid>`: a red head pages once; the episode resolves
250
+ * when that exact head turns green or a newer commit supersedes it. `none`,
251
+ * `running`, and not-read are never called green — an unread rollup neither
252
+ * pages nor resolves anything.
253
+ */
254
+ export async function observeChecks(store, options) {
255
+ const clock = options.clock ?? (() => new Date());
256
+ const exec = options.exec ?? execRun;
257
+ const report = { observed: 0, failing: 0, resolved: 0, problems: [] };
258
+ for (const publication of store.openedPublications()) {
259
+ if (publication.prNumber === null)
260
+ continue;
261
+ // The observation generation is RESERVED before the network call
262
+ // (merge grant, findings 20/24): a stalled older response loses its
263
+ // conditional settle, and a loser has NO effects — not the mirror
264
+ // row, not remote_state, not check state, not an episode.
265
+ const generation = store.reserveObservation(publication.githubRepo, publication.prNumber);
266
+ const viewed = await exec("gh", [
267
+ "pr", "view", String(publication.prNumber),
268
+ "--repo", publication.githubRepo,
269
+ "--json", "statusCheckRollup,headRefOid,state",
270
+ ], { timeoutMs: EXEC_TIMEOUT_MS });
271
+ if (viewed.code !== 0) {
272
+ report.problems.push(`PR #${publication.prNumber}: not read — ${viewed.notFound ? "gh is not on PATH" : firstLine(viewed.stderr) || `exit ${viewed.code}`}`);
273
+ continue;
274
+ }
275
+ let payload;
276
+ try {
277
+ payload = JSON.parse(viewed.stdout);
278
+ }
279
+ catch {
280
+ report.problems.push(`PR #${publication.prNumber}: not read — gh said something that is not JSON`);
281
+ continue;
282
+ }
283
+ const headOid = typeof payload.headRefOid === "string" ? payload.headRefOid : null;
284
+ if (headOid === null) {
285
+ report.problems.push(`PR #${publication.prNumber}: not read — no head OID in the answer`);
286
+ continue;
287
+ }
288
+ report.observed++;
289
+ // A merged or closed PR leaves the watch (Codex M5-M8 audit, C-6): its
290
+ // remote state is recorded, its episodes resolve, and neither the
291
+ // review queue nor the next observation pass carries it forever.
292
+ const remoteState = typeof payload.state === "string" ? payload.state.toUpperCase() : null;
293
+ if (remoteState === "MERGED" || remoteState === "CLOSED") {
294
+ store.transact(() => {
295
+ const settled = store.settleObservation({ githubRepo: publication.githubRepo, prNumber: publication.prNumber, headSha: headOid, state: "none", generation }, clock());
296
+ if (!settled.won)
297
+ return;
298
+ store.recordPublicationRemoteState(publication.id, remoteState, clock());
299
+ report.resolved += store.resolveCiEpisodes(publication.githubRepo, publication.prNumber, null, clock());
300
+ // A closed PR moots its merge blocker and supersedes its intent —
301
+ // and its person-needed episode (arc 3 finding 23).
302
+ store.liftMergeBlocker(publication.id, "the-remote", clock());
303
+ store.resolveEpisodes(`merge-attn:${publication.id}`, clock());
304
+ const intent = store.mergeIntentFor(publication.id);
305
+ if (intent !== null && ["pending", "claimed", "waiting-human", "firing"].includes(intent.state)) {
306
+ // The observer settles ANY nonterminal on an observed remote
307
+ // terminal (E1/F1) - generation bumped so a live claim or
308
+ // firing owner's later write loses to the observed truth.
309
+ store.settleMergeIntent(intent.id, intent.generation, remoteState === "MERGED" ? "merged" : "superseded", clock(), {
310
+ receipt: `observed ${remoteState.toLowerCase()} remotely`, bumpGeneration: true,
311
+ });
312
+ }
313
+ });
314
+ continue;
315
+ }
316
+ const state = summarizeChecks(payload.statusCheckRollup);
317
+ const settled = store.settleObservation({ githubRepo: publication.githubRepo, prNumber: publication.prNumber, headSha: headOid, state, generation }, clock());
318
+ if (!settled.won) {
319
+ // An out-of-order response: a newer observation already spoke.
320
+ // Nothing persists from this one (round-4 findings 24/25).
321
+ report.problems.push(`PR #${publication.prNumber}: superseded observation discarded`);
322
+ continue;
323
+ }
324
+ store.recordPublicationCheckState(publication.id, state, clock());
325
+ // The episode identity carries the REPOSITORY (audit C-2): PR #55 in
326
+ // repo A and PR #55 in repo B are different worlds, and one's failure
327
+ // must never light the other's repair button.
328
+ const key = `ci:${publication.githubRepo}:${publication.prNumber}:${headOid}`;
329
+ const taskId = store.externalIdFor(publication.taskRef) ?? publication.head;
330
+ if (state === "failing") {
331
+ report.failing++;
332
+ store.transact(() => {
333
+ // Older heads' episodes die with their commits; this head's opens once.
334
+ store.resolveCiEpisodes(publication.githubRepo, publication.prNumber, key, clock());
335
+ store.enqueueNotification({
336
+ source: { run: publication.run },
337
+ dedupeKey: key,
338
+ kind: "ci-failing",
339
+ subject: `CI failing: ${taskId} (PR #${publication.prNumber})`,
340
+ body: `${publication.prUrl ?? publication.githubRepo} — head ${headOid.slice(0, 12)} is red.`,
341
+ }, clock());
342
+ });
343
+ }
344
+ else if (state === "passing") {
345
+ report.resolved += store.resolveCiEpisodes(publication.githubRepo, publication.prNumber, null, clock());
346
+ }
347
+ else {
348
+ // running / none: not good news, not bad news — old episodes for
349
+ // *other* heads still resolve (their commit is gone), this head's
350
+ // verdict stays open.
351
+ store.resolveCiEpisodes(publication.githubRepo, publication.prNumber, key, clock());
352
+ }
353
+ }
354
+ return report;
355
+ }
356
+ /** Retries are over; say so durably and page a person — silence is the one failure mode ruled out. */
357
+ function concede(store, publication, why, report, clock) {
358
+ store.transact(() => {
359
+ store.failPublication(publication.id, clock());
360
+ store.enqueueNotification({
361
+ source: { run: publication.run },
362
+ dedupeKey: `publication:${publication.id}:failed`,
363
+ kind: "publication-failed",
364
+ // A person is needed now (v4 review, finding 6): never a digest.
365
+ pushClass: "attention",
366
+ link: `/r/${publication.run}`,
367
+ subject: `publication of run #${publication.run} gave up`,
368
+ body: `After ${MAX_PUBLISH_ATTEMPTS} attempts: ${why}. The commit ${publication.headSha} is safe locally; nothing was lost, nothing more will be pushed.`,
369
+ }, clock());
370
+ });
371
+ report.failed++;
372
+ }
373
+ function enqueueOpened(store, publication, url, clock) {
374
+ store.enqueueNotification({
375
+ source: { run: publication.run },
376
+ dedupeKey: `publication:${publication.id}:opened`,
377
+ kind: "publication-opened",
378
+ subject: `PR ready: ${store.externalIdFor(publication.taskRef) ?? publication.head}`,
379
+ body: url,
380
+ }, clock());
381
+ }
382
+ function firstLine(text) {
383
+ return text.split("\n")[0]?.trim() ?? "";
384
+ }
385
+ // ---- the merge sweep (v21; four review rounds are the spec) ---------------
386
+ export const MERGE_LEASE_MS = 5 * 60_000;
387
+ // F1: a firing row is STALE only when this deadline passes - one sweep
388
+ // interval plus grace, stamped at the CAS, never a guess.
389
+ export const FIRING_GRACE_MS = 10 * 60_000;
390
+ export const MERGE_MAX_ATTEMPTS = 3;
391
+ /** The exact terms a merge intent binds - drift supersedes, never surprises. */
392
+ export function mergeTermsHash(grant) {
393
+ return createHash("sha256")
394
+ .update("merge-terms/v1 " + grant.githubRepo + " " + grant.base + " " + grant.headPrefix + " " + (grant.mergeMethod ?? "") + " " + (grant.mergeDeleteBranch === true ? 1 : 0))
395
+ .digest("hex");
396
+ }
397
+ /** One fresh, WINNING observation - the only tuple that may authorize. */
398
+ async function observeOne(store, exec, clock, githubRepo, prNumber) {
399
+ const generation = store.reserveObservation(githubRepo, prNumber);
400
+ const viewed = await exec("gh", ["pr", "view", String(prNumber), "--repo", githubRepo, "--json", "statusCheckRollup,headRefOid,state,isDraft,baseRefName"], { timeoutMs: EXEC_TIMEOUT_MS });
401
+ if (viewed.code !== 0)
402
+ return { ok: false, why: firstLine(viewed.stderr) || ("exit " + viewed.code) };
403
+ let payload;
404
+ try {
405
+ payload = JSON.parse(viewed.stdout);
406
+ }
407
+ catch {
408
+ return { ok: false, why: "gh said something that is not JSON" };
409
+ }
410
+ const headSha = typeof payload.headRefOid === "string" ? payload.headRefOid : null;
411
+ if (headSha === null)
412
+ return { ok: false, why: "no head OID in the answer" };
413
+ if (payload.isDraft === true)
414
+ return { ok: false, why: "draft" };
415
+ if (String(payload.state ?? "").toUpperCase() === "MERGED")
416
+ return { ok: false, why: "pr-merged" };
417
+ if (String(payload.state ?? "").toUpperCase() !== "OPEN")
418
+ return { ok: false, why: "pr-" + String(payload.state ?? "gone").toLowerCase() };
419
+ const state = summarizeChecks(payload.statusCheckRollup);
420
+ const settled = store.settleObservation({ githubRepo, prNumber, headSha, state, generation }, clock());
421
+ // A losing settle is NO observation (round-4 finding 24).
422
+ if (!settled.won)
423
+ return { ok: false, why: "stale-observation" };
424
+ return { ok: true, state, headSha };
425
+ }
426
+ /** A COMPLETE negative proof that no merge queue governs the base - or no
427
+ * merge (round-4 finding 22): every rules page, plus the classic-protection
428
+ * probe with a STRUCTURED status; any classic protection at all, or any
429
+ * unreadable answer, refuses merge-queue-unknown, fail closed. */
430
+ async function proveNoMergeQueue(exec, githubRepo, base) {
431
+ const rules = await exec("gh", ["api", "--paginate", "--slurp", "repos/" + githubRepo + "/rules/branches/" + encodeURIComponent(base)], { timeoutMs: EXEC_TIMEOUT_MS });
432
+ if (rules.code !== 0)
433
+ return { ok: false, why: "merge-queue-unknown" };
434
+ try {
435
+ const pages = JSON.parse(rules.stdout);
436
+ if (!Array.isArray(pages))
437
+ return { ok: false, why: "merge-queue-unknown" };
438
+ const flat = pages.flat();
439
+ for (const rule of flat) {
440
+ if (typeof rule === "object" && rule !== null && rule["type"] === "merge_queue") {
441
+ return { ok: false, why: "merge-queue" };
442
+ }
443
+ }
444
+ }
445
+ catch {
446
+ return { ok: false, why: "merge-queue-unknown" };
447
+ }
448
+ // Classic protection: the endpoint cannot PROVE queue absence, so any
449
+ // 200 refuses conservatively; only a structured 404 is a readable "no".
450
+ const classic = await exec("gh", ["api", "--include", "repos/" + githubRepo + "/branches/" + encodeURIComponent(base) + "/protection"], { timeoutMs: EXEC_TIMEOUT_MS });
451
+ const statusLine = classic.stdout.split("\n")[0] ?? "";
452
+ if (classic.code === 0 && /^HTTP\/[\d.]+ 200/.test(statusLine))
453
+ return { ok: false, why: "merge-queue-unknown" };
454
+ if (/^HTTP\/[\d.]+ 404/.test(statusLine) || /HTTP 404/.test(firstLine(classic.stderr)))
455
+ return { ok: true };
456
+ return { ok: false, why: "merge-queue-unknown" };
457
+ }
458
+ /**
459
+ * Merge what the grant allows and the evidence proves: this plane's own
460
+ * publications, on this repo, whose PR was OBSERVED green on the exact
461
+ * head - through a claim lease, a live-grant re-read, the queue proof,
462
+ * and GitHub's own head CAS. Every refusal is typed and durable.
463
+ */
464
+ export async function sweepMerges(store, options) {
465
+ const clock = options.clock ?? (() => new Date());
466
+ const exec = options.exec ?? execRun;
467
+ const who = options.runner ?? "publish";
468
+ const report = { merged: 0, refused: 0, skipped: 0, problems: [] };
469
+ // Person-needed merge episodes are STAMPED and episodic (arc 3 findings
470
+ // 14/22/23): one open episode per publication nags, resolution closes it,
471
+ // and a recurrence after repair pages again. Success and plain status
472
+ // pages stay classless — a merged PR needs nobody.
473
+ const page = (publication, key, subject, body) => {
474
+ store.enqueueNotification({ source: { run: publication.run }, dedupeKey: key, kind: "merge", subject, body }, clock());
475
+ };
476
+ const pagePerson = (publication, suffix, subject, body) => {
477
+ store.enqueueEpisode(`merge-attn:${publication.id}`, { source: { run: publication.run }, kind: "merge", subject, body, pushClass: "merge", link: "/review" }, suffix, clock());
478
+ };
479
+ const grant = store.publicationGrantFor(options.repo);
480
+ if (grant === null || grant.merge !== true || grant.mergeMethod == null)
481
+ return report;
482
+ const method = grant.mergeMethod;
483
+ const termsHash = mergeTermsHash(grant);
484
+ for (const publication of store.openedPublications()) {
485
+ if (publication.prNumber === null || publication.headSha === null)
486
+ continue;
487
+ const prNumber = publication.prNumber;
488
+ const headSha = publication.headSha;
489
+ // The local-repo fence (finding 6): repo B's publication never rides
490
+ // repo A's sweep, whatever the remote terms coincide on.
491
+ const ref = store.refForId(publication.taskRef);
492
+ if (ref === null || ref.repo !== options.repo)
493
+ continue;
494
+ if (publication.githubRepo !== grant.githubRepo)
495
+ continue;
496
+ const blocker = store.mergeBlockerFor(publication.id);
497
+ if (blocker !== null) {
498
+ report.skipped++;
499
+ pagePerson(publication, "blocked", "PR #" + prNumber + " holds for a repair", "A CI repair (" + (blocker.taskId ?? "?") + ") is in flight. It merges nothing until you lift it: toolroll publish unblock " + prNumber + ".");
500
+ continue;
501
+ }
502
+ store.createMergeIntent({ publication: publication.id, repo: options.repo, grantTermsHash: termsHash, headSha, method, deleteBranch: grant.mergeDeleteBranch === true }, clock());
503
+ const intent = store.mergeIntentFor(publication.id);
504
+ if (intent === null || intent.state === "merged" || intent.state === "refused" || intent.state === "superseded")
505
+ continue;
506
+ // WAITING-HUMAN never fires itself (E1): the person is paged once,
507
+ // episodically, with the exact ceremony that releases it.
508
+ if (intent.state === "waiting-human") {
509
+ report.skipped++;
510
+ pagePerson(publication, "waiting-human", "PR #" + prNumber + " waits for your go-ahead", "Merges wait for you while this posture holds. When you want it merged: toolroll publish merge " + prNumber + " --as <you> --token <t>. It still merges only when CI is seen green on the exact head.");
511
+ continue;
512
+ }
513
+ // FIRING is one-shot (E1): a live firing row belongs to its owner.
514
+ // A STALE one (deadline passed) recovers by STRUCTURED REMOTE REREAD
515
+ // (F1): an observed terminal settles it; an OPEN same-head PR is
516
+ // NEVER auto-retried - the person gets the recovery ceremony.
517
+ if (intent.state === "firing") {
518
+ if (intent.firingDeadline !== null && intent.firingDeadline <= clock().toISOString()) {
519
+ const reread = await observeOne(store, exec, clock, grant.githubRepo, prNumber);
520
+ if (!reread.ok && reread.why === "pr-merged") {
521
+ store.settleMergeIntent(intent.id, intent.generation, "merged", clock(), {
522
+ receipt: "observed merged remotely after a stale firing claim", from: "firing", bumpGeneration: true,
523
+ });
524
+ report.merged++;
525
+ }
526
+ else if (!reread.ok && reread.why.startsWith("pr-")) {
527
+ store.settleMergeIntent(intent.id, intent.generation, "superseded", clock(), {
528
+ error: reread.why, from: "firing", bumpGeneration: true,
529
+ });
530
+ }
531
+ else if (reread.ok && reread.headSha !== intent.headSha) {
532
+ store.settleMergeIntent(intent.id, intent.generation, "superseded", clock(), {
533
+ error: "the head moved while the firing claim was stale", from: "firing", bumpGeneration: true,
534
+ });
535
+ }
536
+ else {
537
+ report.skipped++;
538
+ pagePerson(publication, "half-fired", "PR #" + prNumber + " may have half-merged", "A merge was issued and its owner went silent; the PR is still open on the same commit. Nothing retries by itself. If you want it fired again: toolroll publish refire " + prNumber + " --as <you> --token <t>.");
539
+ }
540
+ }
541
+ else {
542
+ report.skipped++;
543
+ }
544
+ continue;
545
+ }
546
+ const claim = store.claimMergeIntent(intent.id, who, MERGE_LEASE_MS, clock());
547
+ if (claim === null)
548
+ continue; // someone else's live claim
549
+ const settle = (state, detail = {}) => store.settleMergeIntent(intent.id, claim.generation, state, clock(), { from: "claimed", ...detail });
550
+ // The live grant, re-read AFTER the claim (finding 7): revocation,
551
+ // narrowing, or any term drift supersedes rather than merges.
552
+ const live = store.publicationGrantFor(options.repo);
553
+ if (live === null || live.merge !== true || mergeTermsHash(live) !== termsHash || intent.headSha !== headSha) {
554
+ settle("superseded", { error: "the grant or the head moved since this intent was written" });
555
+ continue;
556
+ }
557
+ const queue = await proveNoMergeQueue(exec, grant.githubRepo, grant.base);
558
+ if (!queue.ok) {
559
+ settle("refused", { error: queue.why });
560
+ report.refused++;
561
+ pagePerson(publication, queue.why, "PR #" + prNumber + " will not auto-merge", queue.why === "merge-queue"
562
+ ? "The base branch requires a merge queue - out of this release's scope. Merge it on GitHub, or lift the queue and run: toolroll publish rearm " + prNumber + "."
563
+ : "The branch's protection could not be READ well enough to prove no merge queue governs it - auto-merge stays paused. Check access, then run: toolroll publish rearm " + prNumber + ".");
564
+ continue;
565
+ }
566
+ // One fresh, winning observation - green on the EXACT publication head.
567
+ const seen = await observeOne(store, exec, clock, grant.githubRepo, prNumber);
568
+ if (!seen.ok) {
569
+ if (seen.why === "draft") {
570
+ settle("refused", { error: "draft" });
571
+ report.refused++;
572
+ pagePerson(publication, "draft", "PR #" + prNumber + " is a draft", "Drafts never merge themselves. Mark it ready on GitHub, then run: toolroll publish rearm " + prNumber + ".");
573
+ }
574
+ else if (seen.why.startsWith("pr-")) {
575
+ settle("superseded", { error: seen.why });
576
+ }
577
+ else {
578
+ settle("pending");
579
+ report.problems.push("PR #" + prNumber + ": " + seen.why);
580
+ }
581
+ continue;
582
+ }
583
+ if (seen.headSha !== headSha || seen.state !== "passing") {
584
+ settle(seen.headSha !== headSha ? "superseded" : "pending", {
585
+ error: seen.headSha !== headSha ? "the head moved" : "CI is " + seen.state + ", not green",
586
+ });
587
+ continue;
588
+ }
589
+ // The linearization point (E1): claimed -> firing as a DURABLE CAS
590
+ // re-proving grant terms, head, and basis-specific authority in one
591
+ // transaction. Before it commits, a revocation wins; after, the
592
+ // issued call is one-shot and no revocation demotes it.
593
+ const fired = store.fireMergeIntent(intent.id, claim.generation, { repo: options.repo, headSha, liveGrantTermsHash: mergeTermsHash(live), graceMs: FIRING_GRACE_MS }, clock());
594
+ if (!fired.ok) {
595
+ if (fired.outcome === "waiting-human") {
596
+ report.skipped++;
597
+ pagePerson(publication, "waiting-human", "PR #" + prNumber + " waits for your go-ahead", fired.why + " - when you want it merged: toolroll publish merge " + prNumber + " --as <you> --token <t>.");
598
+ }
599
+ continue;
600
+ }
601
+ const merged = await exec("gh", [
602
+ "pr", "merge", String(prNumber),
603
+ "--repo", grant.githubRepo,
604
+ "--" + method,
605
+ "--match-head-commit", headSha,
606
+ ...(grant.mergeDeleteBranch === true ? ["--delete-branch"] : []),
607
+ ], { timeoutMs: EXEC_TIMEOUT_MS });
608
+ if (merged.code === 0) {
609
+ store.transact(() => {
610
+ settle("merged", { receipt: "merged " + headSha.slice(0, 12) + " at " + clock().toISOString(), countAttempt: true, from: "firing" });
611
+ store.recordPublicationRemoteState(publication.id, "MERGED", clock());
612
+ store.resolveCiEpisodes(grant.githubRepo, prNumber, null, clock());
613
+ });
614
+ report.merged++;
615
+ store.resolveEpisodes(`merge-attn:${publication.id}`, clock());
616
+ page(publication, "merge:" + publication.id + ":merged", "merged: PR #" + prNumber, (publication.prUrl ?? grant.githubRepo) + " - " + method + " of " + headSha.slice(0, 12) + ", under the merge terms you approved.");
617
+ continue;
618
+ }
619
+ // NEVER classify from the exit code alone (only auth=4 is distinct):
620
+ // re-read structured state and let the PR say what happened.
621
+ if (merged.code === 4) {
622
+ settle("refused", { error: "credential", countAttempt: true, from: "firing" });
623
+ report.refused++;
624
+ pagePerson(publication, "credential", "PR #" + prNumber + ": gh is not signed in", "The merge acts as this machine's GitHub account, and it could not authenticate. Run gh auth login, then: toolroll publish rearm " + prNumber + ".");
625
+ continue;
626
+ }
627
+ const after = await observeOne(store, exec, clock, grant.githubRepo, prNumber);
628
+ if (!after.ok && after.why === "pr-merged") {
629
+ settle("merged", { receipt: "merged (confirmed by re-read after an ambiguous exit)", countAttempt: true, from: "firing" });
630
+ report.merged++;
631
+ }
632
+ else if (!after.ok && (after.why === "draft" || after.why.startsWith("pr-"))) {
633
+ settle(after.why === "draft" ? "refused" : "superseded", { error: after.why, countAttempt: true, from: "firing" });
634
+ if (after.why === "draft")
635
+ report.refused++;
636
+ }
637
+ else if (after.ok && after.headSha !== headSha) {
638
+ settle("superseded", { error: "the head moved during the attempt", countAttempt: true, from: "firing" });
639
+ }
640
+ else if (intent.attempts + 1 >= MERGE_MAX_ATTEMPTS) {
641
+ settle("refused", { error: firstLine(merged.stderr) || "the merge was rejected", countAttempt: true, from: "firing" });
642
+ report.refused++;
643
+ pagePerson(publication, "rejected", "PR #" + prNumber + " would not merge", (firstLine(merged.stderr) || "GitHub rejected the merge") + " - likely branch protection, required reviews, or a conflict. Fix the cause, then run: toolroll publish rearm " + prNumber + ".");
644
+ }
645
+ else {
646
+ settle("pending", { error: firstLine(merged.stderr) || "transport", countAttempt: true, from: "firing" });
647
+ }
648
+ }
649
+ return report;
650
+ }