toolroll 0.9.93 → 0.9.95

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 (436) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/dist/activity-line.d.ts +7 -4
  3. package/dist/activity-line.js +9 -7
  4. package/dist/agent-fence.d.ts +10 -0
  5. package/dist/agent-fence.js +6 -3
  6. package/dist/approval-authority.d.ts +7 -1
  7. package/dist/approval-authority.js +11 -2
  8. package/dist/approval-password.d.ts +31 -0
  9. package/dist/approval-password.js +26 -0
  10. package/dist/assignment-status.d.ts +1 -1
  11. package/dist/assignment-ui.js +3 -1
  12. package/dist/assignment.js +5 -0
  13. package/dist/attest.d.ts +3 -0
  14. package/dist/attest.js +10 -0
  15. package/dist/boot-identity.d.ts +2 -1
  16. package/dist/boot-identity.js +6 -4
  17. package/dist/bounded-spawn.d.ts +57 -0
  18. package/dist/bounded-spawn.js +220 -0
  19. package/dist/browser/terminal.js +42 -42
  20. package/dist/browser/workspace.css +1 -1
  21. package/dist/browser/workspace.js +32 -32
  22. package/dist/browser-crew.js +2 -0
  23. package/dist/browser-shell.d.ts +1 -1
  24. package/dist/browser-shell.js +2 -2
  25. package/dist/browser-workspace.d.ts +50 -2
  26. package/dist/browser-workspace.js +3 -3
  27. package/dist/build-review.js +2 -0
  28. package/dist/builder-restart.d.ts +21 -0
  29. package/dist/builder-restart.js +50 -0
  30. package/dist/builder.d.ts +11 -2
  31. package/dist/builder.js +234 -101
  32. package/dist/card-catalog.d.ts +209 -0
  33. package/dist/card-catalog.js +133 -0
  34. package/dist/card-cli.d.ts +43 -0
  35. package/dist/card-cli.js +44 -0
  36. package/dist/card-render.d.ts +26 -0
  37. package/dist/card-render.js +223 -0
  38. package/dist/card-sources.d.ts +68 -0
  39. package/dist/card-sources.js +234 -0
  40. package/dist/chat-acceptance.js +1 -1
  41. package/dist/chat-actions.d.ts +106 -9
  42. package/dist/chat-actions.js +473 -46
  43. package/dist/chat-approval.d.ts +13 -0
  44. package/dist/chat-approval.js +39 -9
  45. package/dist/chat-card-part.d.ts +11 -0
  46. package/dist/chat-card-part.js +18 -0
  47. package/dist/chat-card.d.ts +46 -0
  48. package/dist/chat-card.js +99 -0
  49. package/dist/chat-channel.d.ts +2 -0
  50. package/dist/chat-channel.js +37 -14
  51. package/dist/chat-controls.d.ts +4 -0
  52. package/dist/chat-controls.js +4 -0
  53. package/dist/chat-decide.d.ts +10 -16
  54. package/dist/chat-decide.js +79 -62
  55. package/dist/chat-delivery.js +13 -4
  56. package/dist/chat-flow.d.ts +0 -27
  57. package/dist/chat-flow.js +31 -2
  58. package/dist/chat-quiet.js +4 -4
  59. package/dist/chat-task-actions.d.ts +3 -2
  60. package/dist/chat-task-actions.js +19 -5
  61. package/dist/chat-terms.d.ts +14 -2
  62. package/dist/chat-terms.js +35 -0
  63. package/dist/chat-voice.d.ts +1 -1
  64. package/dist/chat-voice.js +2 -2
  65. package/dist/check-facts.d.ts +3 -1
  66. package/dist/check-facts.js +57 -11
  67. package/dist/check-failure.d.ts +24 -3
  68. package/dist/check-failure.js +89 -13
  69. package/dist/check-levels-ui.d.ts +4 -0
  70. package/dist/check-levels-ui.js +10 -1
  71. package/dist/check-load.d.ts +85 -0
  72. package/dist/check-load.js +147 -0
  73. package/dist/check-only-standing.d.ts +36 -0
  74. package/dist/check-only-standing.js +39 -0
  75. package/dist/check-only.d.ts +15 -0
  76. package/dist/check-only.js +99 -0
  77. package/dist/check-repair.d.ts +47 -0
  78. package/dist/check-repair.js +240 -0
  79. package/dist/check-report.d.ts +2 -0
  80. package/dist/check-report.js +1 -1
  81. package/dist/check-stamp.d.ts +46 -0
  82. package/dist/check-stamp.js +93 -0
  83. package/dist/checkout-cleanup.d.ts +36 -5
  84. package/dist/checkout-cleanup.js +166 -14
  85. package/dist/claim.d.ts +6 -0
  86. package/dist/claim.js +13 -2
  87. package/dist/cli-help.d.ts +4 -1
  88. package/dist/cli-help.js +49 -30
  89. package/dist/cli.js +15 -1
  90. package/dist/code-extract.d.ts +44 -0
  91. package/dist/code-extract.js +88 -0
  92. package/dist/code-map-acts.d.ts +24 -0
  93. package/dist/code-map-acts.js +53 -0
  94. package/dist/code-map-admission.d.ts +52 -0
  95. package/dist/code-map-admission.js +295 -0
  96. package/dist/code-map-build.d.ts +63 -0
  97. package/dist/code-map-build.js +415 -0
  98. package/dist/code-map-cli.d.ts +153 -0
  99. package/dist/code-map-cli.js +103 -0
  100. package/dist/code-map-main.d.ts +1 -0
  101. package/dist/code-map-main.js +48 -0
  102. package/dist/code-map-read.d.ts +106 -0
  103. package/dist/code-map-read.js +171 -0
  104. package/dist/code-map-report.d.ts +37 -0
  105. package/dist/code-map-report.js +95 -0
  106. package/dist/code-map-shape.d.ts +172 -0
  107. package/dist/code-map-shape.js +327 -0
  108. package/dist/code-map-store.d.ts +136 -0
  109. package/dist/code-map-store.js +233 -0
  110. package/dist/coding-provider.d.ts +3 -1
  111. package/dist/coding-provider.js +12 -7
  112. package/dist/coding-update.js +3 -2
  113. package/dist/coding-workspace.d.ts +1 -1
  114. package/dist/coding-workspace.js +8 -8
  115. package/dist/console-chrome.js +2 -2
  116. package/dist/containment.d.ts +5 -0
  117. package/dist/containment.js +14 -1
  118. package/dist/contracts/chat-actions.d.ts +92 -0
  119. package/dist/contracts/chat-actions.js +20 -0
  120. package/dist/contracts/chat-card.d.ts +91 -0
  121. package/dist/contracts/chat-card.js +54 -0
  122. package/dist/contracts/chat-content.d.ts +191 -0
  123. package/dist/contracts/chat-content.js +4 -0
  124. package/dist/contracts/cli.d.ts +52 -0
  125. package/dist/contracts/cli.js +4 -0
  126. package/dist/contracts/console-api.d.ts +12 -3
  127. package/dist/contracts/console-api.js +12 -3
  128. package/dist/contracts/flow-propose.d.ts +8 -1
  129. package/dist/contracts/flow-propose.js +3 -1
  130. package/dist/contracts/flow-send.d.ts +126 -0
  131. package/dist/contracts/flow-send.js +3 -0
  132. package/dist/contracts/gateway-tools.d.ts +17 -0
  133. package/dist/contracts/gateway-tools.js +3 -1
  134. package/dist/contracts/lead-tools.d.ts +234 -0
  135. package/dist/contracts/lead-tools.js +25 -1
  136. package/dist/contracts/project-knowledge.d.ts +38 -5
  137. package/dist/contracts/project-knowledge.js +11 -5
  138. package/dist/contracts/project-memory.d.ts +3 -1
  139. package/dist/contracts/project-memory.js +3 -2
  140. package/dist/contracts/storage.d.ts +14 -0
  141. package/dist/contracts/storage.js +3 -1
  142. package/dist/contracts/store-json.d.ts +18 -0
  143. package/dist/contracts/youiyoux-request.d.ts +1 -1
  144. package/dist/contracts/youiyoux-request.js +1 -1
  145. package/dist/desktop-access.js +6 -2
  146. package/dist/discord-chat.js +14 -0
  147. package/dist/dispatch.d.ts +1 -1
  148. package/dist/dispatch.js +25 -16
  149. package/dist/dispose.d.ts +19 -0
  150. package/dist/dispose.js +55 -9
  151. package/dist/easy-start.js +2 -3
  152. package/dist/eval-oracle.js +4 -1
  153. package/dist/evals.js +2 -1
  154. package/dist/evidence-pair.d.ts +53 -0
  155. package/dist/evidence-pair.js +128 -0
  156. package/dist/exhaustion.d.ts +17 -4
  157. package/dist/exhaustion.js +44 -4
  158. package/dist/export.js +1 -1
  159. package/dist/failing-first.d.ts +31 -0
  160. package/dist/failing-first.js +44 -0
  161. package/dist/fallback-schema.d.ts +8 -0
  162. package/dist/fallback-schema.js +40 -0
  163. package/dist/fallback.d.ts +138 -0
  164. package/dist/fallback.js +605 -0
  165. package/dist/fast-start.d.ts +136 -0
  166. package/dist/fast-start.js +431 -0
  167. package/dist/fast-terms.d.ts +9 -0
  168. package/dist/fast-terms.js +23 -0
  169. package/dist/fenced-check-coverage.d.ts +62 -0
  170. package/dist/fenced-check-coverage.js +54 -0
  171. package/dist/findings.d.ts +224 -0
  172. package/dist/findings.js +449 -0
  173. package/dist/first-run.js +1 -1
  174. package/dist/flaky-ci.d.ts +31 -0
  175. package/dist/flaky-ci.js +149 -0
  176. package/dist/flaky-cli.d.ts +60 -0
  177. package/dist/flaky-cli.js +60 -0
  178. package/dist/flaky.d.ts +75 -9
  179. package/dist/flaky.js +255 -0
  180. package/dist/fleet-schema.d.ts +17 -0
  181. package/dist/fleet-schema.js +135 -0
  182. package/dist/flow-browser.js +2 -2
  183. package/dist/flow-card-task.js +3 -0
  184. package/dist/flow-code.d.ts +2 -2
  185. package/dist/flow-code.js +9 -5
  186. package/dist/flow-result-card.d.ts +22 -0
  187. package/dist/flow-result-card.js +55 -0
  188. package/dist/flow-send.js +12 -9
  189. package/dist/flow-steps.js +1 -1
  190. package/dist/flow-triggers.d.ts +1 -1
  191. package/dist/flow-triggers.js +2 -2
  192. package/dist/flow-unattended.d.ts +26 -0
  193. package/dist/flow-unattended.js +12 -3
  194. package/dist/flows-ui.js +10 -2
  195. package/dist/follow-through-brief.d.ts +12 -0
  196. package/dist/follow-through-brief.js +31 -0
  197. package/dist/follow-through-cli.d.ts +23 -0
  198. package/dist/follow-through-cli.js +153 -0
  199. package/dist/git-incident.d.ts +16 -0
  200. package/dist/git-incident.js +69 -6
  201. package/dist/github-import.d.ts +62 -0
  202. package/dist/github-import.js +146 -0
  203. package/dist/guides.js +15 -10
  204. package/dist/handover-note.d.ts +33 -0
  205. package/dist/handover-note.js +106 -0
  206. package/dist/invoke.d.ts +25 -2
  207. package/dist/invoke.js +157 -58
  208. package/dist/job-object-helper.ps1 +10 -3
  209. package/dist/keymap.d.ts +41 -0
  210. package/dist/keymap.js +51 -0
  211. package/dist/knowledge-cli.js +1 -1
  212. package/dist/knowledge-ui.d.ts +21 -4
  213. package/dist/knowledge-ui.js +75 -7
  214. package/dist/laptop-background.d.ts +16 -0
  215. package/dist/laptop-background.js +108 -2
  216. package/dist/lead-cli.js +26 -24
  217. package/dist/lead-context.js +1 -1
  218. package/dist/lead-contract.d.ts +1 -1
  219. package/dist/lead-contract.js +3 -2
  220. package/dist/lead-doors.d.ts +4 -0
  221. package/dist/lead-doors.js +2 -2
  222. package/dist/lead-progress.js +5 -0
  223. package/dist/lead-status.d.ts +2 -0
  224. package/dist/lead-status.js +6 -2
  225. package/dist/lead-tools.js +162 -34
  226. package/dist/lead.js +2 -2
  227. package/dist/lesson-notes.d.ts +81 -0
  228. package/dist/lesson-notes.js +336 -0
  229. package/dist/limits-ui.js +1 -1
  230. package/dist/local-owner.d.ts +1 -1
  231. package/dist/local-owner.js +16 -0
  232. package/dist/machine-check.d.ts +16 -0
  233. package/dist/machine-check.js +26 -0
  234. package/dist/machine-identity.d.ts +75 -0
  235. package/dist/machine-identity.js +170 -0
  236. package/dist/mcp-core.js +11 -1
  237. package/dist/memory-cli.d.ts +300 -1
  238. package/dist/memory-cli.js +62 -9
  239. package/dist/memory-pass.d.ts +16 -0
  240. package/dist/memory-pass.js +67 -3
  241. package/dist/memory-tidy.d.ts +181 -0
  242. package/dist/memory-tidy.js +523 -0
  243. package/dist/memory-use.d.ts +35 -0
  244. package/dist/memory-use.js +109 -0
  245. package/dist/merge-checklist.d.ts +55 -0
  246. package/dist/merge-checklist.js +182 -0
  247. package/dist/modes.d.ts +6 -6
  248. package/dist/modes.js +3 -1
  249. package/dist/needs-you.d.ts +31 -9
  250. package/dist/needs-you.js +110 -41
  251. package/dist/nightly-github.d.ts +2 -0
  252. package/dist/nightly-github.js +4 -4
  253. package/dist/operate-remote-arguments.js +7 -1
  254. package/dist/operate-remote.js +3 -1
  255. package/dist/operate.d.ts +6 -1
  256. package/dist/operate.js +565 -64
  257. package/dist/phase-routing.js +2 -2
  258. package/dist/plain-schema.d.ts +23 -0
  259. package/dist/plain-schema.js +94 -0
  260. package/dist/plain-words.d.ts +20 -0
  261. package/dist/plain-words.js +66 -0
  262. package/dist/plan-login.d.ts +14 -0
  263. package/dist/plan-login.js +58 -0
  264. package/dist/planner.d.ts +1 -1
  265. package/dist/planner.js +4 -2
  266. package/dist/process-liveness.d.ts +2 -0
  267. package/dist/process-liveness.js +61 -17
  268. package/dist/project-delete-ui.d.ts +19 -2
  269. package/dist/project-delete-ui.js +29 -1
  270. package/dist/project-delete.js +14 -1
  271. package/dist/project-knowledge.d.ts +18 -11
  272. package/dist/project-knowledge.js +39 -9
  273. package/dist/project-learning.d.ts +2 -1
  274. package/dist/project-learning.js +15 -45
  275. package/dist/project-memory.d.ts +5 -0
  276. package/dist/project-memory.js +22 -2
  277. package/dist/project-tools.d.ts +4 -4
  278. package/dist/project-tools.js +23 -7
  279. package/dist/proof.js +30 -5
  280. package/dist/protected-storage-look.d.ts +1 -0
  281. package/dist/protected-storage-look.js +13 -0
  282. package/dist/protected-storage.d.ts +70 -39
  283. package/dist/protected-storage.js +196 -147
  284. package/dist/provider-auth.js +1 -1
  285. package/dist/provider-capture.d.ts +34 -0
  286. package/dist/provider-capture.js +53 -0
  287. package/dist/publish.js +9 -1
  288. package/dist/pull-request-flow.js +5 -0
  289. package/dist/push.d.ts +18 -2
  290. package/dist/push.js +40 -16
  291. package/dist/release-adapters.d.ts +9 -0
  292. package/dist/release-adapters.js +76 -0
  293. package/dist/release-auto.js +13 -13
  294. package/dist/release-card.d.ts +1 -2
  295. package/dist/release-card.js +11 -0
  296. package/dist/release-carry.d.ts +7 -2
  297. package/dist/release-carry.js +49 -10
  298. package/dist/release-cli.d.ts +4 -0
  299. package/dist/release-cli.js +29 -22
  300. package/dist/release-coverage.js +1 -1
  301. package/dist/release-next.d.ts +48 -0
  302. package/dist/release-next.js +161 -0
  303. package/dist/release.d.ts +20 -1
  304. package/dist/release.js +85 -9
  305. package/dist/remote-destination.d.ts +36 -0
  306. package/dist/remote-destination.js +217 -0
  307. package/dist/remote-main.d.ts +48 -0
  308. package/dist/remote-main.js +84 -5
  309. package/dist/repository-context.d.ts +3 -2
  310. package/dist/repository-context.js +112 -70
  311. package/dist/restart-certification.d.ts +1 -0
  312. package/dist/restart-certification.js +8 -6
  313. package/dist/result-acts.d.ts +27 -19
  314. package/dist/result-acts.js +36 -25
  315. package/dist/result-follow-ups.d.ts +2 -2
  316. package/dist/result-follow-ups.js +18 -4
  317. package/dist/result-review.d.ts +1 -1
  318. package/dist/route-change-check.d.ts +4 -0
  319. package/dist/route-change-check.js +13 -0
  320. package/dist/route-change.d.ts +22 -0
  321. package/dist/route-change.js +89 -0
  322. package/dist/route-hooks.js +3 -2
  323. package/dist/route-results.d.ts +6 -1
  324. package/dist/route-results.js +14 -5
  325. package/dist/run-closeout.d.ts +1 -1
  326. package/dist/run-closeout.js +7 -5
  327. package/dist/runner-process.js +3 -2
  328. package/dist/runner.js +2 -1
  329. package/dist/scope.d.ts +28 -4
  330. package/dist/scope.js +92 -28
  331. package/dist/scout.d.ts +1 -1
  332. package/dist/scout.js +2 -1
  333. package/dist/serve.d.ts +1 -1
  334. package/dist/serve.js +26 -18
  335. package/dist/server/chat.js +16 -14
  336. package/dist/server/chrome.d.ts +1 -1
  337. package/dist/server/chrome.js +26 -35
  338. package/dist/server/console.css +73 -0
  339. package/dist/server/guards.js +3 -0
  340. package/dist/server/home.d.ts +6 -0
  341. package/dist/server/home.js +21 -13
  342. package/dist/server/http.js +1 -1
  343. package/dist/server/kit.js +1 -1
  344. package/dist/server/merge-ready-view.d.ts +10 -0
  345. package/dist/server/merge-ready-view.js +30 -0
  346. package/dist/server/pages-assets.d.ts +1 -0
  347. package/dist/server/pages-assets.js +8 -5
  348. package/dist/server/pages.js +103 -82
  349. package/dist/server/render-chat.js +16 -3
  350. package/dist/server/render-code-map.d.ts +30 -0
  351. package/dist/server/render-code-map.js +137 -0
  352. package/dist/server/render-flaky.d.ts +20 -0
  353. package/dist/server/render-flaky.js +59 -0
  354. package/dist/server/render-follow-through.d.ts +11 -0
  355. package/dist/server/render-follow-through.js +68 -0
  356. package/dist/server/render-history.js +1 -1
  357. package/dist/server/render-new-task.d.ts +26 -1
  358. package/dist/server/render-new-task.js +30 -5
  359. package/dist/server/render-pages.d.ts +1 -175
  360. package/dist/server/render-pages.js +38 -313
  361. package/dist/server/render-projects.d.ts +2 -1
  362. package/dist/server/render-projects.js +9 -7
  363. package/dist/server/render-release.d.ts +2 -0
  364. package/dist/server/render-release.js +3 -3
  365. package/dist/server/render-results.d.ts +2 -0
  366. package/dist/server/render-results.js +68 -68
  367. package/dist/server/render-tasks.d.ts +16 -2
  368. package/dist/server/render-tasks.js +177 -141
  369. package/dist/server/retired-routes.d.ts +17 -0
  370. package/dist/server/retired-routes.js +53 -0
  371. package/dist/server/route-table.d.ts +3 -0
  372. package/dist/server/route-table.js +48 -12
  373. package/dist/server/settings.js +166 -34
  374. package/dist/server/task-views.js +66 -26
  375. package/dist/server/tasks.d.ts +2 -6
  376. package/dist/server/tasks.js +250 -321
  377. package/dist/server/welcome.js +1 -1
  378. package/dist/settings-groups-ui.d.ts +1 -0
  379. package/dist/settings-groups-ui.js +4 -2
  380. package/dist/settings-kit.js +3 -2
  381. package/dist/settings-proofs.js +3 -1
  382. package/dist/slack-chat.js +14 -1
  383. package/dist/steer-mailbox.d.ts +8 -1
  384. package/dist/steer-mailbox.js +164 -6
  385. package/dist/stop-proof.js +6 -4
  386. package/dist/storage-sweep-process.js +1 -1
  387. package/dist/storage-sweep.d.ts +5 -3
  388. package/dist/storage-sweep.js +30 -9
  389. package/dist/storage-ui.js +28 -17
  390. package/dist/storage.d.ts +27 -2
  391. package/dist/storage.js +39 -2
  392. package/dist/store.d.ts +89 -7
  393. package/dist/store.js +386 -114
  394. package/dist/surface.js +67 -6
  395. package/dist/sweep-quarantine.d.ts +1 -0
  396. package/dist/sweep-quarantine.js +6 -4
  397. package/dist/tamper-scan.d.ts +4 -0
  398. package/dist/tamper-scan.js +90 -0
  399. package/dist/task-aliases.d.ts +5 -0
  400. package/dist/task-aliases.js +9 -0
  401. package/dist/task-control.d.ts +2 -1
  402. package/dist/task-control.js +4 -1
  403. package/dist/task-outcome-cli.d.ts +2 -0
  404. package/dist/task-outcome-cli.js +17 -17
  405. package/dist/task-sizing.d.ts +5 -0
  406. package/dist/task-sizing.js +2 -0
  407. package/dist/task-source.d.ts +7 -0
  408. package/dist/task-source.js +14 -0
  409. package/dist/task-status.d.ts +9 -0
  410. package/dist/task-status.js +45 -49
  411. package/dist/teardown-git.d.ts +18 -0
  412. package/dist/teardown-git.js +105 -21
  413. package/dist/telegram-lead.js +23 -6
  414. package/dist/telegram-progress.js +5 -3
  415. package/dist/telegram-status.js +6 -4
  416. package/dist/telegram.js +12 -1
  417. package/dist/test-infra.d.ts +8 -0
  418. package/dist/test-infra.js +18 -0
  419. package/dist/tidy.js +1 -1
  420. package/dist/toolroll-update.js +19 -5
  421. package/dist/tui-contract.d.ts +24 -0
  422. package/dist/tui-source.js +67 -8
  423. package/dist/tui.js +51 -51
  424. package/dist/vanished-retry.d.ts +30 -0
  425. package/dist/vanished-retry.js +174 -0
  426. package/dist/work-index.d.ts +2 -0
  427. package/dist/work-index.js +95 -66
  428. package/dist/work-summary.d.ts +1 -1
  429. package/dist/work-summary.js +7 -2
  430. package/dist/workspace-ui.d.ts +7 -3
  431. package/dist/workspace-ui.js +38 -23
  432. package/dist/worktree.d.ts +12 -3
  433. package/dist/worktree.js +91 -14
  434. package/package.json +2 -1
  435. package/dist/server/render-next.d.ts +0 -63
  436. package/dist/server/render-next.js +0 -109
@@ -13,7 +13,7 @@
13
13
  import { DEFAULT_LEAD_NAME } from "./lead-identity.js";
14
14
  import { html } from "./html.js";
15
15
  import { FAILED_CHECK } from "./result-acts.js";
16
- import { CODE_NEED, NEEDS, needSentence, waitSentence } from "./needs-you.js";
16
+ import { CODE_READING, NEEDS, needSentence, waitSentence } from "./needs-you.js";
17
17
  /** A cancelled task with a successor reads "Replaced by <id>" everywhere, never "Cancelled". */
18
18
  export const replacedWords = (successor) => `Replaced by ${successor}`;
19
19
  const REPLACED = /^Replaced by \S+/;
@@ -28,6 +28,8 @@ export const CHECKS_LABEL = "Project checks";
28
28
  export const PR_CI_LABEL = "PR CI";
29
29
  /** The one verb for opening a saved result, on every surface. */
30
30
  export const OPEN_RESULT = "Open result";
31
+ /** A machine-run check under way (a prepared candidate: no agent runs), on every surface that has no richer words. */
32
+ export const MACHINE_CHECK_RUNNING = "Running the check on the prepared version.";
31
33
  export const CHECKED_AT_RELEASE = "Checked at release";
32
34
  export function releaseStateOf(checks) {
33
35
  if (checks?.level !== "off" || checks.status === "passed" || checks.status === "failed")
@@ -162,46 +164,47 @@ function sentenceOf(headline, facts) {
162
164
  const reason = facts.reason?.trim() || null;
163
165
  const sha = short(facts.checks?.head ?? null);
164
166
  switch (headline) {
165
- case "Queued": return reason ?? "Waiting for a worker.";
167
+ case "Queued": return reason ?? "Waiting for a builder.";
166
168
  case "Planning": return reason ?? "The lead is writing the plan.";
167
169
  // One plain sentence per need (needs-you.ts); a recorded reason only where the need keeps one.
168
170
  case "Needs you": return facts.stage === "finished" ? resultAsk(facts) ?? needSentence("review-result", reason, facts.needContext) : needSentence(facts.need ?? "other", reason, facts.needContext);
169
171
  case "Waiting": return waitSentence(facts.wait, reason, facts.needContext);
170
172
  case "Building": return facts.stage === "checking" ? "Checks are running on the change."
171
- : facts.stage === "reviewing" ? "Reviewing the change before it reaches you." : reason ?? "An agent is working on it.";
173
+ : facts.stage === "reviewing" ? "Reviewing the change before it reaches you."
174
+ : facts.machine === true ? MACHINE_CHECK_RUNNING : reason ?? "An agent is working on it.";
172
175
  case "Ready for review":
173
176
  if (facts.report)
174
- return "The report is ready to read. Read it, then mark it complete.";
177
+ return "The report is ready to read. Read it, then accept it.";
175
178
  if (releaseStateOf(facts.checks) === "covered")
176
- return `Checked at release on ${short(facts.checks.release.head) ?? "its release"}. Review the change, then mark it complete.`;
179
+ return `Checked at release on ${short(facts.checks.release.head) ?? "its release"}. Review the change, then accept it.`;
177
180
  if (facts.checks?.level === "off" && facts.checks.status !== "passed")
178
- return "Checked at release. Review the change, then mark it complete.";
181
+ return "Checked at release. Review the change, then accept it.";
179
182
  if (facts.checks?.status === "passed") {
180
183
  // The Checks row names the others and the batch commit; the sentence doesn't repeat them.
181
184
  const peers = facts.checks.batch?.state === "together" ? facts.checks.batch.peers.length : 0;
182
185
  if (peers > 0)
183
- return `Checks passed together with ${peers === 1 ? "1 other result" : `${peers} other results`}. Review the change, then mark it complete.`;
184
- return `${facts.checks.level === "quick" ? "Quick checks" : "Checks"} passed${sha === null ? "" : ` on ${sha}`}. Review the change, then mark it complete.`;
186
+ return `Checks passed together with ${peers === 1 ? "1 other result" : `${peers} other results`}. Review the change, then accept it.`;
187
+ return `${facts.checks.level === "quick" ? "Quick checks" : "Checks"} passed${sha === null ? "" : ` on ${sha}`}. Review the change, then accept it.`;
185
188
  }
186
189
  if (facts.checks?.batch?.state === "waiting")
187
- return "Its full check runs with other results within 10 minutes. Review the change, then mark it complete.";
190
+ return "Its full check runs with other results within 10 minutes. Review the change, then accept it.";
188
191
  if (facts.checks?.status === "unavailable")
189
- return `${facts.checks.cause === "command-changed" ? CHECK_COMMAND_CHANGED : CHECK_UNREADABLE} Review the change, then mark it complete.`;
192
+ return `${facts.checks.cause === "command-changed" ? CHECK_COMMAND_CHANGED : CHECK_UNREADABLE} Review the change, then accept it.`;
190
193
  // Never claim a check that isn't known to have passed.
191
194
  if (facts.checks == null)
192
- return "Review the change, then mark it complete.";
193
- return "Built without a passing project check. Review the change, then mark it complete.";
195
+ return "Review the change, then accept it.";
196
+ return "Built without a passing project check. Review the change, then accept it.";
194
197
  case "Complete": {
195
198
  // Landed work says why once; its merged pull request is that reason, not a second sentence.
196
199
  if (facts.landed)
197
- return `Marked complete automatically: ${facts.landed}.`;
198
- const by = facts.completedBy ? `Marked complete by ${facts.completedBy}.` : "Marked complete.";
200
+ return `Accepted automatically: ${facts.landed}.`;
201
+ const by = facts.completedBy ? `Accepted by ${facts.completedBy}.` : "Accepted.";
199
202
  const pr = facts.pullRequest;
200
203
  return pr?.state === "merged" ? `${by} ${pr.number === null ? "Its pull request" : `Pull request #${pr.number}`} merged.` : by;
201
204
  }
202
205
  case "Failed":
203
206
  if (facts.checks?.status === "failed")
204
- return `${facts.checks.level === "quick" ? "Quick checks" : "Checks"} failed${sha === null ? "" : ` on ${sha}`}. See what broke, then retry or ask for changes.`;
207
+ return `${facts.checks.level === "quick" ? "Quick checks" : "Checks"} failed${sha === null ? "" : ` on ${sha}`}. See what broke, then retry or send it back.`;
205
208
  return reason ?? "The last attempt stopped before it finished. Review it, then retry.";
206
209
  case "Stopped": return reason ?? "Stopped by a person. The work so far is kept.";
207
210
  }
@@ -291,13 +294,13 @@ function detailsOf(headline, facts) {
291
294
  if (evidence != null) {
292
295
  const action = { label: "See what was kept", href: facts.links?.result ?? null };
293
296
  if (evidence.damaged > 0)
294
- row("evidence", "Saved evidence", "Some saved files changed", problem(headline), headline === "Failed" ? {} : { action });
297
+ row("evidence", "Saved files", "Some saved files changed", problem(headline), headline === "Failed" ? {} : { action });
295
298
  else if (evidence.missing > 0)
296
- row("evidence", "Saved evidence", "Some saved files are missing", problem(headline), headline === "Failed" ? {} : { action });
299
+ row("evidence", "Saved files", "Some saved files are missing", problem(headline), headline === "Failed" ? {} : { action });
297
300
  else if (evidence.shortened > 0)
298
- row("evidence", "Saved evidence", "Some output shortened", "note", { action });
301
+ row("evidence", "Saved files", "Some output shortened", "note", { action });
299
302
  else
300
- row("evidence", "Saved evidence", "Complete", "ok");
303
+ row("evidence", "Saved files", "All kept", "ok");
301
304
  }
302
305
  return rows;
303
306
  }
@@ -331,24 +334,9 @@ export function statusOf(facts) {
331
334
  /** The name most surfaces already import. */
332
335
  export const taskStatusOf = statusOf;
333
336
  // ---- reading the existing projections ----------------------------------------
334
- /** Every dispatch or work-index code, as the stage it means. Unknown codes that
335
- * wait on a person stay Needs you; everything else waits in the queue. */
336
- const CODE_STAGE = {
337
- queued: ["queued"], ready: ["queued"], "worker-at-capacity": ["queued"], "retry-scheduled": ["queued"], updating: ["queued"],
338
- "waiting-dependency": ["queued"], "scouting-ready": ["queued"], "planning-ready": ["queued"], "provider-quota": ["queued"],
339
- // A request the planner cannot be given (too long to hand back, say) waits on a person to shorten it.
340
- "planner-source": ["needs-you", "fix-request"],
341
- running: ["building"], reviewing: ["reviewing"], "review-pending": ["building"], "checks-running": ["checking"],
342
- "signed-out": ["needs-you", "sign-in"],
343
- "needs-approval": ["needs-you", "approval"],
344
- "waiting-decision": ["needs-you", "answer"], "decision-queue": ["needs-you", "answer"],
345
- "build-stopping": ["waiting"], "other-computer": ["waiting"],
346
- stopping: ["stopped"], stopped: ["stopped"], paused: ["stopped"], cancelled: ["stopped"],
347
- failed: ["failed"], "waiting-incident": ["failed"], "checks-failed": ["failed"],
348
- "ready-to-check": ["finished"], "ready-to-review": ["finished"], "report-ready": ["finished"], "no-change": ["finished"],
349
- "pr-opened": ["finished"], "merge-observed": ["finished"], "pr-closed": ["finished"], "agent-attested": ["finished"], "accepted-exception": ["finished"],
350
- complete: ["complete"],
351
- };
337
+ /** Codes that read Needs you whoever asks (their reason is a person's by nature). Any other need reads Needs you
338
+ * only when the caller says a person is needed; otherwise it waits in the queue. */
339
+ const ALWAYS_NEEDS = new Set(["planner-source", "signed-out", "needs-approval", "waiting-decision", "decision-queue"]);
352
340
  /** Queue reasons worth a sentence of their own; any other queued or
353
341
  * building reason is scheduler wording, and the default sentence is plainer. */
354
342
  const QUEUED_REASONS = new Set(["worker-at-capacity", "waiting-dependency", "retry-scheduled", "updating", "planning-ready", "scouting-ready", "provider-quota"]);
@@ -359,15 +347,18 @@ export function plainReasonOf(stage, code, detail) {
359
347
  return QUEUED_REASONS.has(code) ? detail ?? null : null;
360
348
  return detail ?? null;
361
349
  }
350
+ /** Every dispatch or work-index code, as the stage it means (needs-you.ts CODE_READING, the one table). Unknown
351
+ * codes that wait on a person stay Needs you; everything else waits in the queue. */
362
352
  export function stageOfCode(code, options = {}) {
363
353
  if (code === "running" && options.planning)
364
354
  return { stage: "planning" };
365
355
  if (code === "held")
366
356
  return options.operatorHold === false ? { stage: "needs-you", need: "hold" } : { stage: "stopped" };
367
- const known = CODE_STAGE[code];
368
- if (known !== undefined)
369
- return known[1] === undefined ? { stage: known[0] } : { stage: known[0], need: known[1] };
370
- return options.needsPerson === false ? { stage: "queued" } : { stage: "needs-you", need: CODE_NEED[code] ?? "other" };
357
+ const known = Object.prototype.hasOwnProperty.call(CODE_READING, code) ? CODE_READING[code] : undefined;
358
+ if (known !== undefined && (known.stage !== "needs-you" || ALWAYS_NEEDS.has(code))) {
359
+ return { stage: known.stage, ...(known.need === undefined ? {} : { need: known.need }), ...(known.wait === undefined ? {} : { wait: known.wait }) };
360
+ }
361
+ return options.needsPerson === false ? { stage: "queued" } : { stage: "needs-you", need: known?.need ?? "other" };
371
362
  }
372
363
  /** The pull request as the task page's follower reports it, or the bare publication row. */
373
364
  export function pullRequestFactOf(publication) {
@@ -394,6 +385,9 @@ export function pullRequestFactOf(publication) {
394
385
  /** The assignment's stage. The work status, when the caller read one, names
395
386
  * the exact reason (a running planner, a pause, a failed attempt, a sign-in). */
396
387
  export function assignmentStageOf(assignment, work, planning = false) {
388
+ // A finding held for a person outlives the stage the work is in, a saved result included: only a person closes it.
389
+ if (work?.token === "finding-held" && assignment.state !== "cancelled")
390
+ return { stage: "needs-you", need: "finding" };
397
391
  // The assignment's own reading (a build Toolroll can't confirm stopped) comes first.
398
392
  const own = assignment.need ?? null;
399
393
  if (own !== null && (assignment.state === "working" || assignment.state === "needs-decision"))
@@ -416,6 +410,8 @@ export function assignmentStageOf(assignment, work, planning = false) {
416
410
  return { stage: "needs-you", need: "answer" };
417
411
  if (action === "inspect-decisions")
418
412
  return { stage: "needs-you", need: "questions" };
413
+ if (action === "inspect-findings")
414
+ return { stage: "needs-you", need: "finding" };
419
415
  const token = work?.token ?? "";
420
416
  // A failed attempt is Failed. A saved result that still needs a decision
421
417
  // (its scope, a hold, a process exit) is Needs you, even if its checks
@@ -473,7 +469,7 @@ const quoted = (statement) => {
473
469
  function unresolvedAsk(row, word) {
474
470
  const what = word === "Not shown yet" && screenshotMissing(row) ? "A screenshot is missing for"
475
471
  : word === "Not shown yet" ? "Nothing shows this requirement is met" : word === "Unverified" ? "This requirement couldn't be verified" : "This requirement isn't met";
476
- return `${what}: ${quoted(row.statement) || row.id || "a requirement"}. Ask for changes, or accept the result with a reason.`;
472
+ return `${what}: ${quoted(row.statement) || row.id || "a requirement"}. Send it back, or accept the result with a reason.`;
477
473
  }
478
474
  /** Requirements from the stored matrix: met, the ones only a person confirms, and the total. */
479
475
  export function requirementsOf(matrix, release) {
@@ -482,7 +478,7 @@ export function requirementsOf(matrix, release) {
482
478
  const words = matrix.map(row => requirementWordOf(row, null, release));
483
479
  const yours = matrix.find((_, index) => words[index] === "You check");
484
480
  const unshown = matrix.filter((row, index) => words[index] === "Not shown yet" && screenshotMissing(row));
485
- const ask = yours !== undefined ? `Check this requirement yourself, then mark it complete: ${quoted(yours.statement)}`
481
+ const ask = yours !== undefined ? `Check this requirement yourself, then accept it: ${quoted(yours.statement)}`
486
482
  : unshown.length > 0 ? unresolvedAsk(unshown[0], "Not shown yet") : null;
487
483
  return { met: words.filter(word => word === "Met").length, total: matrix.length, yours: words.filter(word => word === "You check").length,
488
484
  missed: words.filter(word => word === "Not met" || word === "Not shown yet").length,
@@ -499,12 +495,12 @@ export function completionBlockersOf(f) {
499
495
  const release = releaseStateOf(f.checks);
500
496
  const backing = checkBackingOf(f.checks);
501
497
  if (!f.report && f.checks?.status === "failed") {
502
- blockers.push({ key: "check-failed", message: `Checks failed${f.checks.exitCode === null ? "" : ` (exit ${f.checks.exitCode})`}. Fix them and run checks again, or ask for changes.` });
498
+ blockers.push({ key: "check-failed", message: `Checks failed${f.checks.exitCode === null ? "" : ` (exit ${f.checks.exitCode})`}. Fix them and run checks again, or send it back.` });
503
499
  }
504
500
  else if (!f.report && f.checkRequired && f.checks?.status !== "passed" && release === undefined) {
505
- blockers.push({ key: "check-missing", message: checksUnderway(f.checks) ? "Its project check hasn't finished yet. Mark it complete once it passes."
506
- : f.checks?.status === "unavailable" ? f.checks.cause === "command-changed" ? `${CHECK_COMMAND_CHANGED.replace(/; run checks again\.$/, ".")} Run checks again, then mark it complete.` : `${CHECK_UNREADABLE} Run checks on it, then mark it complete.`
507
- : "No passing project check is recorded for this result. Run checks on it, then mark it complete." });
501
+ blockers.push({ key: "check-missing", message: checksUnderway(f.checks) ? "Its project check hasn't finished yet. Accept it once it passes."
502
+ : f.checks?.status === "unavailable" ? f.checks.cause === "command-changed" ? `${CHECK_COMMAND_CHANGED.replace(/; run checks again\.$/, ".")} Run checks again, then accept it.` : `${CHECK_UNREADABLE} Run checks on it, then accept it.`
503
+ : "No passing project check is recorded for this result. Run checks on it, then accept it." });
508
504
  }
509
505
  if (!f.accepted && f.checks?.status !== "failed") {
510
506
  const refuted = f.verdict === "refuted";
@@ -514,11 +510,11 @@ export function completionBlockersOf(f) {
514
510
  blockers.push({ key: "criteria", message: unresolvedAsk(unresolved[0].row, unresolved[0].word) });
515
511
  else if (unresolved.length > 1) {
516
512
  const ids = unresolved.map(one => one.row.id ?? "?");
517
- blockers.push({ key: "criteria", message: `${unresolved.length} requirements ${unresolved.every(one => one.word === "Unverified") ? "couldn't be verified" : "aren't met yet"} (${ids.slice(0, 6).join(", ")}${ids.length > 6 ? ", …" : ""}). Ask for changes, or accept the result with a reason.` });
513
+ blockers.push({ key: "criteria", message: `${unresolved.length} requirements ${unresolved.every(one => one.word === "Unverified") ? "couldn't be verified" : "aren't met yet"} (${ids.slice(0, 6).join(", ")}${ids.length > 6 ? ", …" : ""}). Send it back, or accept the result with a reason.` });
518
514
  }
519
515
  }
520
516
  if (f.high > 0)
521
- blockers.push({ key: "high", message: `The automatic review found ${f.high === 1 ? "a high-severity problem" : `${f.high} high-severity problems`}. Ask for changes before marking it complete.` });
517
+ blockers.push({ key: "high", message: `The automatic review found ${f.high === 1 ? "a high-severity problem" : `${f.high} high-severity problems`}. Send it back before accepting it.` });
522
518
  return blockers;
523
519
  }
524
520
  /** A refuted report verifies none of its requirements, whatever it marked met (a person's acceptance doesn't change that). */
@@ -46,6 +46,12 @@ export declare const INCLUDE_PARSE_TIMED_OUT = "Git didn't finish reading it in
46
46
  export type GitBinding = {
47
47
  repo?: string | null;
48
48
  };
49
+ /** The shared git folder of a recorded project, read from disk and proven as boundDirs proves it: the project's own
50
+ * real `.git` folder, holding no `commondir`. No pointer file or `commondir` an agent can write decides it, and no git
51
+ * runs (a page visit reads this). Throws with the reason otherwise. */
52
+ export declare function recordedCommonDir(repo: string): string;
53
+ /** Where git incidents are recorded: beside the fenced views folder (git-incident.ts). */
54
+ export declare function gitIncidentRecord(): string;
49
55
  /** Settings fixed on every write through a view: `worktree add` records absolute paths (relative ones would need a
50
56
  * config write, which a view keeps); no transport at all (a caller that fetches allows its one protocol by name, as the
51
57
  * nightly check's local fetch does with protocol.file.allow); and no split index (its shared index files would be
@@ -55,6 +61,12 @@ export declare const WRITE_VIEW_FLAGS: readonly string[];
55
61
  * program (anything but INERT_LFS). A file that
56
62
  * isn't a plain one, or doesn't parse, counts as naming one. */
57
63
  export declare function lfsExtensionIn(checkout: string, gitFile?: string, limit?: Limit): boolean;
64
+ /** The hardened runners' own reads before the caller's command (the checkout's config listing): their own bounds, never
65
+ * the caller's. A caller's small `maxBuffer` is for its own command's output (builder.ts reads one ls-tree line with
66
+ * 2 KiB): inherited, it failed the config listing once a project's .git/config grew past 2 KiB (one [branch] section per
67
+ * work branch), and every prepared release check then failed as "its config couldn't be read" (runs 3316–3324). */
68
+ export declare const CONFIG_READ_BYTES: number;
69
+ export declare const CONFIG_READ_MS = 30000;
58
70
  /** Every command runs against a view of the checkout's repository (gitView) whose config and info are copies the
59
71
  * plane made, inspected there, so neither can change between the inspection and the run. `mode` "read" is for reading
60
72
  * a checkout an agent may still be writing (the code page): every command also takes readFlags(), and the view holds
@@ -143,4 +155,10 @@ export declare function lazyFetchSetting(listing: string): string | null;
143
155
  * command and protocol settings refused, a partial clone refused) is the config git then runs with.
144
156
  */
145
157
  export declare function lazyFetchSafeGit(inner?: Runner): Runner;
158
+ /**
159
+ * The checkout a folder sits in, for choosing a folder (easy-start): the nearest parent holding a `.git`, then read
160
+ * through checkoutGitSync, so a config that includes another file (a FIFO), or sets a command, is refused and the read
161
+ * is bounded. Null when there is no checkout or it can't be read that way.
162
+ */
163
+ export declare function checkoutTop(dir: string, timeoutMs?: number): string | null;
146
164
  export {};
@@ -191,7 +191,7 @@ function nestedRepository(path) {
191
191
  * repository (boundDirs; `binding.repo` when the caller has the project's), is refused; so is a command whose
192
192
  * repository changed under it while git ran.
193
193
  */
194
- async function inView(cwd, binding, work, after = true, lockTimeoutMs, gitFile = "git") {
194
+ async function inView(cwd, binding, work, after = true, lockTimeoutMs, doing, gitFile = "git") {
195
195
  const root = checkoutRootOf(cwd);
196
196
  if (root === null)
197
197
  return NOT_A_REPOSITORY;
@@ -207,7 +207,7 @@ async function inView(cwd, binding, work, after = true, lockTimeoutMs, gitFile =
207
207
  return await withCheckoutLock(realpathSync(homedir()), key, async () => {
208
208
  let view;
209
209
  try {
210
- view = gitView(root, "write", binding, gitFile);
210
+ view = gitView(root, "write", binding, gitFile, undefined, doing);
211
211
  }
212
212
  catch (error) {
213
213
  return refused(error instanceof Error ? error.message : "the checkout couldn't be read");
@@ -255,6 +255,7 @@ export function teardownGit(runner = run, binding = {}, lockTimeoutMs, gitFile =
255
255
  const forced = removal && args.includes("--force");
256
256
  const inspection = args.includes("status") || args.includes("diff");
257
257
  const target = removal ? resolve(cwd, args.at(-1)) : cwd;
258
+ const doing = teardownDoing(args);
258
259
  const safeArgs = [...args];
259
260
  if (inspection && !safeArgs.includes("--ignore-submodules=all")) {
260
261
  const command = safeArgs.findIndex(arg => arg === "status" || arg === "diff");
@@ -288,8 +289,22 @@ export function teardownGit(runner = run, binding = {}, lockTimeoutMs, gitFile =
288
289
  // the target's view, inspected above, so it judges the target by the target's own settings.
289
290
  return ran(removal && !forced ? { ...view.env, GIT_COMMON_DIR: targetView.env["GIT_COMMON_DIR"] } : view.env);
290
291
  // A removed target has no git directory left to prove: the folder it was removed from is proven instead.
291
- }, !removal, lockTimeoutMs, gitFile);
292
- }, true, lockTimeoutMs, gitFile);
292
+ }, !removal, lockTimeoutMs, doing, gitFile);
293
+ }, true, lockTimeoutMs, teardownDoing(args), gitFile);
294
+ }
295
+ /** What teardown git is doing, for an incident's notice: removing a checkout, or looking one over to tidy it. */
296
+ function teardownDoing(args) {
297
+ return args.includes("worktree") && args.includes("remove") ? "removing an old checkout" : "tidying an old checkout";
298
+ }
299
+ /** What a build's git is doing, for an incident's notice. */
300
+ function checkoutDoing(args) {
301
+ if (args.includes("worktree"))
302
+ return args.includes("remove") ? "removing a checkout" : args.includes("add") ? "making a checkout" : "listing checkouts";
303
+ if (args.some(arg => arg === "commit" || arg === "add" || arg === "merge" || arg === "rebase" || arg === "reset" || arg === "checkout" || arg === "switch"))
304
+ return "saving a build's work";
305
+ if (args.some(arg => arg === "fetch" || arg === "push"))
306
+ return "updating a repository";
307
+ return "reading a build's checkout";
293
308
  }
294
309
  /** A setup call's time limit: its own five seconds, or less when a caller's deadline leaves less (never below 1 ms;
295
310
  * a caller checks the deadline itself before calling). */
@@ -330,7 +345,7 @@ function regularFile(path) {
330
345
  }
331
346
  /** A checkout's git directory and common directory, as git finds them from its `.git` (a folder, or a one-line
332
347
  * `gitdir: ` file); null when the pointer isn't one git would read the same way. */
333
- function gitDirsOf(checkout) {
348
+ function gitDirsOf(checkout, vouched = NO_VOUCHED) {
334
349
  let gitdir = join(checkout, ".git");
335
350
  try {
336
351
  if (!lstatSync(gitdir).isDirectory()) {
@@ -350,11 +365,14 @@ function gitDirsOf(checkout) {
350
365
  // Where the checkout's own files point (a build can write both): read, in the console, only while that folder
351
366
  // answered a look lately, and never vouched for by the checkout pool's spelling (folder-gate.ts). Held back: not one
352
367
  // git would read the same way, so every caller refuses.
353
- if (gitdir !== join(checkout, ".git") && !folderAnswers(gitdir, true))
368
+ // `vouched`: the very folders this view was made from, read again after git ran (settle). They answered moments ago
369
+ // and git just ran in them; the gate's staleness is no evidence about them. Anything they now point elsewhere is
370
+ // gated as ever, so a re-pointed `.git` or `commondir` is never read on the console's thread on its say.
371
+ if (gitdir !== join(checkout, ".git") && !vouched.has(gitdir) && !folderAnswers(gitdir, true))
354
372
  return null;
355
373
  const named = regularFile(join(gitdir, "commondir"))?.trim();
356
374
  const common = named === undefined || named === "" ? gitdir : resolve(gitdir, named);
357
- if (common !== gitdir && !folderAnswers(common, true))
375
+ if (common !== gitdir && !vouched.has(common) && !folderAnswers(common, true))
358
376
  return null;
359
377
  return { gitdir, common };
360
378
  }
@@ -382,7 +400,8 @@ function includeIn(path, gitFile = "git", limit) {
382
400
  return named.toLowerCase();
383
401
  return /^\s*\[\s*include(if)?\b/im.test(text) ? "include" : null;
384
402
  }
385
- function boundDirs(checkout, binding = {}) {
403
+ const NO_VOUCHED = new Set();
404
+ function boundDirs(checkout, binding = {}, vouched = NO_VOUCHED) {
386
405
  const fail = (why) => { throw new Error(`the checkout's .git ${why}`); };
387
406
  let top;
388
407
  try {
@@ -403,12 +422,12 @@ function boundDirs(checkout, binding = {}) {
403
422
  if (held.isDirectory()) {
404
423
  if (existsSync(join(dotgit, "commondir")))
405
424
  fail("names another repository (commondir)");
406
- dirs = { checkout: top, gitdir: dotgit, common: dotgit, linked: false };
425
+ dirs = { checkout: top, gitdir: dotgit, common: dotgit, linked: false, spelled: [dotgit] };
407
426
  }
408
427
  else {
409
428
  if (!held.isFile())
410
429
  fail("is a link");
411
- const named = gitDirsOf(top);
430
+ const named = gitDirsOf(top, vouched);
412
431
  if (named === null)
413
432
  return fail("couldn't be read as git reads it");
414
433
  let gitdir;
@@ -444,7 +463,7 @@ function boundDirs(checkout, binding = {}) {
444
463
  }
445
464
  if (backReal !== dotgit)
446
465
  fail("belongs to a git directory that names another checkout");
447
- dirs = { checkout: top, gitdir, common, linked: true };
466
+ dirs = { checkout: top, gitdir, common, linked: true, spelled: [named.gitdir, named.common, gitdir, common] };
448
467
  }
449
468
  // The common folder is a main checkout's own `.git` folder, a real one.
450
469
  if (basename(dirs.common) !== ".git")
@@ -477,6 +496,12 @@ function boundDirs(checkout, binding = {}) {
477
496
  }
478
497
  return dirs;
479
498
  }
499
+ /** The shared git folder of a recorded project, read from disk and proven as boundDirs proves it: the project's own
500
+ * real `.git` folder, holding no `commondir`. No pointer file or `commondir` an agent can write decides it, and no git
501
+ * runs (a page visit reads this). Throws with the reason otherwise. */
502
+ export function recordedCommonDir(repo) {
503
+ return boundDirs(repo, { repo }).common;
504
+ }
480
505
  /** What git keeps only per worktree and the plane never links: HEAD (git won't take it as a link) and the pointers. */
481
506
  /** What the plane links of a worktree's own git folder (git writes them in place through the link: the index and its
482
507
  * logs by lockfile, per-worktree refs and logs inside the folders), whether or not they exist yet; a rebase's,
@@ -484,7 +509,12 @@ function boundDirs(checkout, binding = {}) {
484
509
  const ADMIN_LINKED = ["index", "logs", "refs", "reftable"];
485
510
  /** What git keeps in a common directory (its path.c common_list, and reftable), linked whether or not it exists yet. */
486
511
  const COMMON_NAMES = ["branches", "gc.pid", "logs", "lost-found", "modules", "objects", "packed-refs", "reftable", "refs", "remotes", "rr-cache", "shallow", "svn", "worktrees"];
487
- function gitView(checkout, kind, binding = {}, gitFile = "git", limit) {
512
+ /** Where git incidents are recorded: beside the fenced views folder (git-incident.ts). */
513
+ export function gitIncidentRecord() {
514
+ return join(dirname(ensureGitViewsRoot(realpathSync(homedir()))), "git-incidents.jsonl");
515
+ }
516
+ /** `doing`: what the plane was doing, in a person's words ("tidying an old checkout"), for an incident's notice. */
517
+ function gitView(checkout, kind, binding = {}, gitFile = "git", limit, doing) {
488
518
  const dirs = boundDirs(checkout, binding);
489
519
  const root = ensureGitViewsRoot(realpathSync(homedir()));
490
520
  const view = realpathSync(mkdtempSync(join(root, `${kind}-`)));
@@ -652,8 +682,10 @@ function gitView(checkout, kind, binding = {}, gitFile = "git", limit) {
652
682
  const settle = () => {
653
683
  const now = pinned(dirs);
654
684
  let bound = false;
685
+ // The same proof as before git ran, of the same folders: re-read past the console's folder gate only where they are
686
+ // the very ones this view was made from (vouched), so a gate whose answer went stale mid-run is never read as a swap.
655
687
  try {
656
- const again = boundDirs(checkout, binding);
688
+ const again = boundDirs(checkout, binding, new Set(dirs.spelled));
657
689
  bound = again.gitdir === dirs.gitdir && again.common === dirs.common;
658
690
  }
659
691
  catch {
@@ -663,7 +695,7 @@ function gitView(checkout, kind, binding = {}, gitFile = "git", limit) {
663
695
  // Too late to prevent: recorded and told (git-incident.ts), never rolled back where the swap leads.
664
696
  if (kind === "write") {
665
697
  gitIncident({ checkout: dirs.checkout, repository: dirs.common, swapped: now.swapped.length > 0 ? now.swapped.map(({ path, leadsTo }) => ({ path, leadsTo })) : [{ path: join(dirs.checkout, ".git"), leadsTo: null }],
666
- record: join(dirname(ensureGitViewsRoot(realpathSync(homedir()))), "git-incidents.jsonl") });
698
+ cause: now.swapped.length > 0 ? "swapped" : "rebound", ...(doing === undefined ? {} : { doing }), record: gitIncidentRecord() });
667
699
  }
668
700
  return "the checkout's repository changed while git ran";
669
701
  }
@@ -677,7 +709,7 @@ function gitView(checkout, kind, binding = {}, gitFile = "git", limit) {
677
709
  catch (error) {
678
710
  if (!(error instanceof AnchorMoved))
679
711
  return "the checkout's git state couldn't be carried back after git ran";
680
- gitIncident({ checkout: dirs.checkout, repository: dirs.common, swapped: [{ path: error.message, leadsTo: null }], record: join(dirname(ensureGitViewsRoot(realpathSync(homedir()))), "git-incidents.jsonl") });
712
+ gitIncident({ checkout: dirs.checkout, repository: dirs.common, swapped: [{ path: error.message, leadsTo: null }], cause: "moved", ...(doing === undefined ? {} : { doing }), record: gitIncidentRecord() });
681
713
  return "the checkout's repository changed while git ran";
682
714
  }
683
715
  if (left.length > 0)
@@ -1061,6 +1093,28 @@ export function lfsExtensionIn(checkout, gitFile = "git", limit) {
1061
1093
  const parsed = spawnSync(gitFile, ["config", "--file", path, "--no-includes", "--name-only", "--list", "-z"], { cwd: "/", encoding: "utf8", timeout: setupTimeout(limit), killSignal: "SIGKILL", env: { PATH: process.env["PATH"] ?? "", GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null", HOME: "/nonexistent" } });
1062
1094
  return parsed.status !== 0 || parsed.stdout.split("\0").filter(Boolean).some(name => lfsCommand(name) || ["include", "includeif"].includes(keyParts(name).section));
1063
1095
  }
1096
+ /** The hardened runners' own reads before the caller's command (the checkout's config listing): their own bounds, never
1097
+ * the caller's. A caller's small `maxBuffer` is for its own command's output (builder.ts reads one ls-tree line with
1098
+ * 2 KiB): inherited, it failed the config listing once a project's .git/config grew past 2 KiB (one [branch] section per
1099
+ * work branch), and every prepared release check then failed as "its config couldn't be read" (runs 3316–3324). */
1100
+ export const CONFIG_READ_BYTES = 16 * 1024 * 1024;
1101
+ export const CONFIG_READ_MS = 30_000;
1102
+ /** What of a caller's options an internal read keeps: where it runs and with what environment and containment —
1103
+ * never its time limit, output limit, stdin or output callbacks. */
1104
+ function internalOptions(options, env) {
1105
+ const { cwd, envAllowlist, omitEnv, fence, fenceGrants, confinement, processGroup, owner, signal } = options ?? {};
1106
+ return { ...(cwd === undefined ? {} : { cwd }), ...(envAllowlist === undefined ? {} : { envAllowlist }), ...(omitEnv === undefined ? {} : { omitEnv }),
1107
+ ...(fence === undefined ? {} : { fence }), ...(fenceGrants === undefined ? {} : { fenceGrants }), ...(confinement === undefined ? {} : { confinement }),
1108
+ ...(processGroup === undefined ? {} : { processGroup }), ...(owner === undefined ? {} : { owner }), ...(signal === undefined ? {} : { signal }),
1109
+ env, timeoutMs: CONFIG_READ_MS, maxBuffer: CONFIG_READ_BYTES };
1110
+ }
1111
+ /** Why the checkout's config listing failed, in words, for the refusal: git's own first line, or what stopped it. */
1112
+ function listingFailure(result) {
1113
+ if (result.timedOut)
1114
+ return `its config couldn't be read (the read ran past ${CONFIG_READ_MS / 1000} s)`;
1115
+ const said = `${result.stderr}`.split("\n").map(line => line.trim()).find(line => line !== "");
1116
+ return `its config couldn't be read${said === undefined ? ` (git exited ${result.code})` : ` (${said.slice(0, 300)})`}`;
1117
+ }
1064
1118
  /** Every command runs against a view of the checkout's repository (gitView) whose config and info are copies the
1065
1119
  * plane made, inspected there, so neither can change between the inspection and the run. `mode` "read" is for reading
1066
1120
  * a checkout an agent may still be writing (the code page): every command also takes readFlags(), and the view holds
@@ -1082,17 +1136,17 @@ export function checkoutGit(inner = run, mode = "build", binding = {}) {
1082
1136
  const once = async () => {
1083
1137
  let view;
1084
1138
  try {
1085
- view = gitView(cwd, mode === "read" ? "read" : "write", binding);
1139
+ view = gitView(cwd, mode === "read" ? "read" : "write", binding, "git", undefined, checkoutDoing(args));
1086
1140
  }
1087
1141
  catch (error) {
1088
1142
  return refused(error instanceof Error ? error.message : "the checkout couldn't be read");
1089
1143
  }
1090
1144
  try {
1091
1145
  const viewed = { ...options, env: { ...(options?.env ?? {}), ...view.env } };
1092
- // The checkout's own config may name no command.
1093
- const own = await inner("git", ["--no-optional-locks", "config", "--list", "--show-scope", "-z"], viewed);
1146
+ // The checkout's own config may name no command. Read with this runner's own bounds (internalOptions).
1147
+ const own = await inner("git", ["--no-optional-locks", "config", "--list", "--show-scope", "-z"], internalOptions(options, viewed.env));
1094
1148
  if (own.code !== 0)
1095
- return refused("its config couldn't be read");
1149
+ return refused(listingFailure(own));
1096
1150
  const key = commandSetting(own.stdout, { lfsChecked: true });
1097
1151
  if (key !== null)
1098
1152
  return refused(`the checkout's git config sets ${key}, which can run a command`);
@@ -1185,9 +1239,11 @@ export function viewedGitSync(cwd, args, options = {}) {
1185
1239
  const env = { PATH: process.env["PATH"] ?? "", HOME: process.env["HOME"] ?? "", LANG: "C", GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null", GIT_TERMINAL_PROMPT: "0", ...view.env };
1186
1240
  const timeoutNow = () => { expire(); return limit === undefined ? options.timeoutMs ?? 60_000 : Math.max(1, Math.min(options.timeoutMs ?? 60_000, limit())); };
1187
1241
  const fixed = { cwd, env, encoding: "utf8", killSignal: "SIGKILL", maxBuffer: options.maxBuffer ?? 1_048_576, stdio: ["ignore", "pipe", "pipe"] };
1188
- const own = spawnSync(gitFile, ["--no-optional-locks", "config", "--list", "--show-scope", "-z"], { ...fixed, timeout: timeoutNow() });
1242
+ // Its own bounds, never the caller's output limit (CONFIG_READ_BYTES); a caller's deadline still holds.
1243
+ const own = spawnSync(gitFile, ["--no-optional-locks", "config", "--list", "--show-scope", "-z"], { ...fixed, maxBuffer: CONFIG_READ_BYTES,
1244
+ timeout: limit === undefined ? CONFIG_READ_MS : Math.max(1, Math.min(CONFIG_READ_MS, limit())) });
1189
1245
  if (own.status !== 0)
1190
- throw new Error("its config couldn't be read");
1246
+ throw new Error(own.error !== undefined ? `its config couldn't be read (${own.error.message.slice(0, 300)})` : "its config couldn't be read");
1191
1247
  const key = commandSetting(own.stdout, { lfsChecked: true });
1192
1248
  if (key !== null)
1193
1249
  throw new Error(`the checkout's git config sets ${key}, which can run a command`);
@@ -1333,3 +1389,31 @@ export function lazyFetchSafeGit(inner = run) {
1333
1389
  // repository's config and its includes as git found them, and an include naming a FIFO could hang the plane.
1334
1390
  return checkoutGit(inner, "read");
1335
1391
  }
1392
+ /**
1393
+ * The checkout a folder sits in, for choosing a folder (easy-start): the nearest parent holding a `.git`, then read
1394
+ * through checkoutGitSync, so a config that includes another file (a FIFO), or sets a command, is refused and the read
1395
+ * is bounded. Null when there is no checkout or it can't be read that way.
1396
+ */
1397
+ export function checkoutTop(dir, timeoutMs = 10_000) {
1398
+ let at;
1399
+ try {
1400
+ at = realpathSync(dir);
1401
+ }
1402
+ catch {
1403
+ return null;
1404
+ }
1405
+ for (;;) {
1406
+ if (existsSync(join(at, ".git"))) {
1407
+ try {
1408
+ return checkoutGitSync(at, ["rev-parse", "--show-toplevel"], { timeoutMs }).trim() || null;
1409
+ }
1410
+ catch {
1411
+ return null;
1412
+ }
1413
+ }
1414
+ const up = dirname(at);
1415
+ if (up === at)
1416
+ return null;
1417
+ at = up;
1418
+ }
1419
+ }
@@ -1,5 +1,7 @@
1
+ import { telegramCardMessage } from "./card-render.js";
2
+ import { cardOfProposal, cardPlainOf, cardsFirst, cardWithin, isCardProposal } from "./chat-card.js";
1
3
  import { CHAT_ACTIONS, sharedActionAllowsChallenge, sharedActionPayload } from "./chat-actions.js";
2
- import { taskInCeiling, mirrorToTaskChat, couldNotAnswerText, channelRepos as telegramConversationRepos, resolveChannelLead as resolveTelegramLead, NO_PHONE_LINK, NO_TASK_LINK, phoneProposalLink, confirmedLink, phoneProposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText } from "./chat-channel.js";
4
+ import { armedQuestion, taskInCeiling, mirrorToTaskChat, couldNotAnswerText, channelRepos as telegramConversationRepos, resolveChannelLead as resolveTelegramLead, NO_PHONE_LINK, NO_TASK_LINK, phoneProposalLink, confirmedLink, phoneProposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText } from "./chat-channel.js";
3
5
  export { channelRepos as telegramConversationRepos, resolveChannelLead as resolveTelegramLead, tooLongText, whichTaskText, replyContextFor, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText, CHAT_ACTION_PARITY as TELEGRAM_ACTION_PARITY, parityGaps } from "./chat-channel.js";
4
6
  /**
5
7
  * The paired phone as one more way to use the same assistant.
@@ -260,7 +262,7 @@ export function applyProposalTap(store, binding, token, message, repos, options,
260
262
  const body = preview.text.split("\n\nConfirm or Dismiss below")[0];
261
263
  if (challenged) {
262
264
  ack("confirm it");
263
- edit(`${body}\n\nThis records that you handled this exact result. Confirm?`, [
265
+ edit(`${body}\n\n${armedQuestion(proposal)}`, [
264
266
  [telegramButton(`✓ Yes, ${CHAT_ACTIONS[sharedAction.operation].label.toLowerCase()}`, yes)],
265
267
  [telegramButton("Cancel", cancel)],
266
268
  ]);
@@ -288,6 +290,7 @@ export function applyProposalTap(store, binding, token, message, repos, options,
288
290
  // The paired phone speaks for its account's full authority (pairing is a full session's act).
289
291
  authority: "full",
290
292
  via: "telegram",
293
+ binding: binding.id,
291
294
  evidenceRoot: options.evidenceRoot,
292
295
  confirm: action.phase === "yes",
293
296
  deferSignal: signal => effects.push({ kind: "signal", run: signal }),
@@ -549,17 +552,26 @@ async function runTelegramConversation(row, args) {
549
552
  const origin = proposal === null ? null : options.phoneOrigin?.() ?? null;
550
553
  const wanted = proposal === null ? null : phoneProposalLink(store, proposal, repos);
551
554
  const keyboard = keyboardWith(part.keyboard, phoneLinkButton(origin, wanted));
555
+ // A Toolroll card: read again now, shown only where every project behind it is still reachable. An unreadable
556
+ // one goes out as its saved plain words.
557
+ const isCard = proposal !== null && isCardProposal(proposal);
558
+ const rich = isCard ? cardOfProposal(proposal) : null;
559
+ const shown = rich !== null && cardWithin(rich, repos) ? telegramCardMessage(rich) : null;
560
+ const links = rich === null ? [] : rich.buttons.flatMap(one => phoneLinkButton(origin, one) ?? []).map(one => [one]);
552
561
  // The lead's own words carry their bold anchors and labelled links as entities, never as Markdown characters.
553
562
  const voiced = part.kind === "reply" ? telegramReply(part.text) : null;
554
- const text = proposal !== null && part.keyboard === null ? handoffCardText(part.text, linkNote(origin, wanted)) : voiced?.text ?? part.text;
563
+ const text = isCard ? shown?.text ?? (rich === null ? part.text : "This card's projects changed, so it wasn't sent.")
564
+ : proposal !== null && part.keyboard === null ? handoffCardText(part.text, linkNote(origin, wanted)) : voiced?.text ?? part.text;
565
+ const entities = isCard ? shown?.entities ?? [] : voiced?.entities ?? [];
566
+ const buttons = isCard ? (shown === null || links.length === 0 ? undefined : links) : keyboard;
555
567
  method = "sendMessage";
556
568
  send = () => transport("sendMessage", {
557
569
  chat_id: chatId,
558
570
  text,
559
- ...(voiced === null || voiced.entities.length === 0 ? {} : { entities: voiced.entities }),
571
+ ...(entities.length === 0 ? {} : { entities }),
560
572
  link_preview_options: { is_disabled: true },
561
573
  ...(part.replyTo === null ? {} : { reply_parameters: { message_id: Number(part.replyTo) } }),
562
- ...(keyboard === undefined ? {} : { reply_markup: { inline_keyboard: keyboard } }),
574
+ ...(buttons === undefined ? {} : { reply_markup: { inline_keyboard: buttons } }),
563
575
  });
564
576
  }
565
577
  // The first time the answer itself fails to go out, the person hears so once,
@@ -626,7 +638,12 @@ async function runTelegramConversation(row, args) {
626
638
  const selected = store.getLeadTurn(turn)?.state === "answered" ? store.listLeadTurnEvidence(turn) : [];
627
639
  for (const image of selected)
628
640
  parts.push({ kind: "image", text: image.caption, taskId: image.taskId, run: image.run, artifact: image.artifact, sha256: image.sha256 });
629
- for (const proposal of proposals) {
641
+ // Cards first, so what a card leads to sits right under it. A card mints no tokens: it confirms nothing.
642
+ for (const proposal of cardsFirst(proposals)) {
643
+ if (isCardProposal(proposal)) {
644
+ parts.push({ kind: "card", text: cardPlainOf(proposal), proposal: proposal.id, keyboard: null });
645
+ continue;
646
+ }
630
647
  const preview = phoneProposalPreview(store, proposal, repos);
631
648
  const keyboard = preview.buttons ? mintCardTokens(store, binding, proposal.id, now).keyboard : null;
632
649
  parts.push({ kind: "card", text: preview.text, proposal: proposal.id, keyboard });