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
@@ -1,8 +1,10 @@
1
1
  /** Task pages: approval, agents, plans, controls, decisions and the task body. */
2
2
  import { archivedWords } from "../tidy.js";
3
+ import { btn } from "./kit.js";
3
4
  import { whenUtc } from "../when-html.js";
4
5
  import { networkWords } from "../egress.js";
5
- import { releaseAsksAgainWords, releaseTermsSentence } from "../scope.js";
6
+ import { chainTermsLines, liveChainFromJson, releaseAsksAgainWords, releaseTermsSentence } from "../scope.js";
7
+ import { SUBSCRIPTION_CAPABLE } from "../keys.js";
6
8
  import { RELEASE_CI_RUNNING_WORDS } from "../release-ci-hold.js";
7
9
  import { writesWords } from "../writes-mode.js";
8
10
  import { checkAccessWords } from "../check-access.js";
@@ -27,7 +29,8 @@ import { taskRoad } from "../first-run.js";
27
29
  import { starterForWork } from "../flow-starters.js";
28
30
  import {} from "../gaps.js";
29
31
  import { readAuthModeStrict } from "../keys.js";
30
- import { failedAttemptSentence, retryNoteOf, START_BUILDER_COMMAND, startBuilderHref } from "../needs-you.js";
32
+ import { failedAttemptSentence, retryNoteOf, START_BUILDER_COMMAND, startBuilderHref, STORAGE_HOLD_PREFIX } from "../needs-you.js";
33
+ import { planVersionWords } from "../plain-words.js";
31
34
  import { chosenWords, makesNoPlan, PHASES as ROUTE_PHASES, TASK_SIZES } from "../phase-routing.js";
32
35
  import { parseExecutionPlanDocument } from "../plan.js";
33
36
  import {} from "../planner-source.js";
@@ -61,10 +64,10 @@ export function contractChangeHtml(change) {
61
64
  case "outOfScope":
62
65
  return html `<li>${tag} <strong>not this</strong>${change.before === null ? "" : html `<div class="contract-before">was: ${change.before}</div>`}${change.after === null ? html `<div class="contract-after">now: <em>no exclusions</em></div>` : html `<div class="contract-after">now: ${change.after}</div>`}</li>`;
63
66
  case "touches":
64
- return html `<li>${tag} <strong>touches</strong> <span class="mono">${change.path}</span></li>`;
67
+ return html `<li>${tag} <strong>files</strong> <span class="mono">${change.path}</span></li>`;
65
68
  case "acceptance": {
66
69
  const criterion = (one) => one === null ? "" : html `${one.statement} <span class="meta">[requires: ${one.evidence.join(", ")}]</span>${one.how === null ? "" : html `<div class="meta">how: ${one.how}</div>`}`;
67
- return html `<li>${tag} <strong>criterion <code>${change.id}</code></strong>${change.kind === "changed" ? html ` <span class="meta">(${change.moved.join(", ")})</span>` : ""}${change.before === null ? "" : html `<div class="contract-before">${change.kind === "removed" ? "removed: " : "was: "}${criterion(change.before)}</div>`}${change.after === null ? "" : html `<div class="contract-after">${change.kind === "added" ? "added: " : "now: "}${criterion(change.after)}</div>`}</li>`;
70
+ return html `<li>${tag} <strong>requirement <code>${change.id}</code></strong>${change.kind === "changed" ? html ` <span class="meta">(${change.moved.join(", ")})</span>` : ""}${change.before === null ? "" : html `<div class="contract-before">${change.kind === "removed" ? "removed: " : "was: "}${criterion(change.before)}</div>`}${change.after === null ? "" : html `<div class="contract-after">${change.kind === "added" ? "added: " : "now: "}${criterion(change.after)}</div>`}</li>`;
68
71
  }
69
72
  }
70
73
  }
@@ -78,25 +81,25 @@ export function contractChangeHtml(change) {
78
81
  */
79
82
  export function planContractHtml(view, mode) {
80
83
  if (view === null) {
81
- return mode === "full" ? html `` : html `<p class="meta contract-note">no contract record for this draft — compare the scope above against what you filed before signing</p>`;
84
+ return mode === "full" ? html `` : html `<p class="meta contract-note">No record compares this plan with your request. Check the plan above against what you asked for before approving.</p>`;
82
85
  }
83
86
  if ("problem" in view) {
84
- return html `<div class="contract-panel contract-problem"><p class="approval-label">filed contract</p><p class="meta">${view.problem} · <a href="/r/${view.run}">run ${view.run}</a></p></div>`;
87
+ return html `<div class="contract-panel contract-problem"><p class="approval-label">Your request</p><p class="meta">${view.problem} · <a href="/r/${view.run}">Planner's record</a></p></div>`;
85
88
  }
86
- const stale = view.current ? "" : html `<p class="meta">The scope was edited after this draft landed — the record below describes the draft as the planner proposed it</p>`;
89
+ const stale = view.current ? "" : html `<p class="meta">The plan was edited after the planner drafted it. The record below is the planner's draft.</p>`;
87
90
  if (!view.filed) {
88
91
  return mode === "full"
89
- ? html `<div class="contract-panel contract-drafted"><p class="approval-label">filed contract</p><p class="meta">No scope was filed before planning — the planner drafted this contract from the title and the repository; review every term as new</p>${stale}</div>`
90
- : html `<p class="meta contract-note">no scope was filed before planning — every term above is the planner's proposal</p>`;
92
+ ? html `<div class="contract-panel contract-drafted"><p class="approval-label">Your request</p><p class="meta">Nothing was filed before planning. The planner wrote this plan from the title and the project, so review every part as new.</p>${stale}</div>`
93
+ : html `<p class="meta contract-note">Nothing was filed before planning: every part above is the planner's.</p>`;
91
94
  }
92
95
  if (view.revision === true) {
93
96
  // A send-back: the terms were copied from the previous version, and the planner updated them with the notes.
94
97
  if (view.changes.length === 0) {
95
- return html `<div class="contract-panel contract-preserved"><p class="approval-label">updated plan</p><p><strong>Same terms as before</strong> <span class="meta">your notes fit the previous plan · <a href="/r/${view.run}">run ${view.run}</a></span></p>${stale}</div>`;
98
+ return html `<div class="contract-panel contract-preserved"><p class="approval-label">updated plan</p><p><strong>Same plan as before</strong> <span class="meta">your notes fit the previous plan · <a href="/r/${view.run}">Planner's record</a></span></p>${stale}</div>`;
96
99
  }
97
100
  return joinHtml([
98
101
  html `<div class="contract-panel contract-amended"${mode === "full" ? html ` id="contract-amendment"` : ""}><p class="approval-label">updated plan</p>`,
99
- html `<p><strong>${view.changes.length} change${view.changes.length === 1 ? "" : "s"} from your notes</strong> <span class="meta">— approving accepts the updated terms ${mode === "full" ? "in the scope" : "above"} · <a href="/r/${view.run}">run ${view.run}</a></span></p>`,
102
+ html `<p><strong>The planner changed ${view.changes.length === 1 ? "1 thing" : `${view.changes.length} things`} from your notes:</strong> <span class="meta">approving accepts the updated plan ${mode === "full" ? "shown in the plan" : "above"} · <a href="/r/${view.run}">Planner's record</a></span></p>`,
100
103
  view.amendment === null ? "" : html `<p class="recap contract-reason"><strong>Why:</strong> ${view.amendment}</p>`,
101
104
  html `<ul class="recap contract-changes">${view.changes.map(contractChangeHtml)}</ul>`,
102
105
  stale,
@@ -104,11 +107,11 @@ export function planContractHtml(view, mode) {
104
107
  ]);
105
108
  }
106
109
  if (view.changes.length === 0) {
107
- return html `<div class="contract-panel contract-preserved"><p class="approval-label">filed contract</p><p><strong>Preserved exactly</strong> <span class="meta">the plan reproduces the filed goal, exclusions, touches, and acceptance criteria — approving binds the terms you filed · <a href="/r/${view.run}">run ${view.run}</a></span></p>${stale}</div>`;
110
+ return html `<div class="contract-panel contract-preserved"><p class="approval-label">Your request</p><p><strong>Kept exactly</strong> <span class="meta">the plan keeps your goal, exclusions, files and requirements word for word · <a href="/r/${view.run}">Planner's record</a></span></p>${stale}</div>`;
108
111
  }
109
112
  return joinHtml([
110
- html `<div class="contract-panel contract-amended"${mode === "full" ? html ` id="contract-amendment"` : ""}><p class="approval-label">filed contract · amendment proposed</p>`,
111
- html `<p><strong>${view.changes.length} change${view.changes.length === 1 ? "" : "s"} to what you filed</strong> <span class="meta">— approving binds the AMENDED terms shown ${mode === "full" ? "in the scope" : "above"}, not the ones you filed · <a href="/r/${view.run}">run ${view.run}</a></span></p>`,
113
+ html `<div class="contract-panel contract-amended"${mode === "full" ? html ` id="contract-amendment"` : ""}><p class="approval-label">Your request · changes proposed</p>`,
114
+ html `<p><strong>The planner changed ${view.changes.length === 1 ? "1 thing" : `${view.changes.length} things`} from your request:</strong> <span class="meta">approving accepts the changed plan ${mode === "full" ? "shown in the plan" : "above"}, not your original words · <a href="/r/${view.run}">Planner's record</a></span></p>`,
112
115
  view.amendment === null
113
116
  ? html `<p class="meta">The planner stated no reason for the amendment</p>`
114
117
  : html `<p class="recap contract-reason"><strong>Why:</strong> ${view.amendment}</p>`,
@@ -165,7 +168,7 @@ export function revisionSentence(revision, acceptance) {
165
168
  return `Fixes what ${build} missed: ${missed.map(clean).join("; ")}.`;
166
169
  }
167
170
  const notes = revision.comments.map(one => clean(one.note)).filter(one => one !== "");
168
- return notes.length === 0 ? `Revises ${build}.` : `Fixes what ${build} missed: ${notes.join("; ")}.`;
171
+ return notes.length === 0 ? `Sends back ${build}.` : `Fixes what ${build} missed: ${notes.join("; ")}.`;
169
172
  }
170
173
  /** A planner's amendment, said plainly and kept in view: approving binds
171
174
  * the amended terms, so this is never folded away. */
@@ -242,11 +245,12 @@ export function approvalSheetHtml(input) {
242
245
  input.localOwner != null ? html `` : html `${reauth(html `<label class="approval-password"><span class="visually-hidden">Your password</span><input type="password" name="token" autocomplete="current-password" placeholder="Your password" aria-describedby="${parts.passwordNote}"></label>`)}`,
243
246
  input.localOwner?.eligible === true ? html ``
244
247
  : input.localOwner != null ? html `<p class="approval-password-note" id="${parts.passwordNote}">${input.localOwner.message} <a href="${input.localOwner.href}">${input.localOwner.action}</a></p>`
245
- : input.freshSince == null ? html `<p class="approval-password-note" id="${parts.passwordNote}">Your password signs this approval.</p>`
248
+ : input.freshSince == null ? html `<p class="approval-password-note" id="${parts.passwordNote}">Type your password to approve.</p>`
246
249
  : html `<p class="approval-password-note" id="${parts.passwordNote}" data-approval-fresh>Your password from ${whenTime(input.freshSince)} is remembered on this computer — leave this blank to approve with it, or type it again.</p>`,
247
250
  html `<button type="submit" data-primary-action>${parts.submit}</button></div>`,
248
251
  html `<p class="approval-after">${parts.after}</p>`,
249
- html `<div class="approval-secondary">${parts.editor ?? html `<a class="approval-link" href="${input.editHref}">Edit plan</a>`}<a class="approval-link" href="${input.notNowHref}">Not now</a></div>`,
252
+ // Approve, Request changes, Reject (docs/plans/plain-toolroll.md P1.3): Reject is the task's own Cancel, one tap away.
253
+ html `<div class="approval-secondary">${parts.editor ?? html `<a class="approval-link" href="${input.editHref}">Request changes</a>`}<a class="approval-link" href="${input.surface === "task" ? "#cancel-task" : input.notNowHref}">${input.surface === "task" ? "Reject" : "Not now"}</a></div>`,
250
254
  parts.details,
251
255
  ]), {
252
256
  attrs: { class: "approve-form approval-sheet", id: input.surface === "task" ? "approve" : null, "data-sticky": input.sticky },
@@ -272,7 +276,7 @@ export function approvalSheetParts(input) {
272
276
  const sentence = input.revision !== null && (repairText === null || input.revision.comments.length > 0) ? revisionSentence(input.revision, scope.acceptance)
273
277
  : repairText === null ? null
274
278
  : repairText[2] !== undefined ? `Fixes what ${builtBy} missed: ${statementsOf(repairText[2])}.`
275
- : repairText[3] !== undefined ? `Collects the evidence ${builtBy} was missing for: ${statementsOf(repairText[3])}. The saved code stays the same.`
279
+ : repairText[3] !== undefined ? `Shows what ${builtBy} couldn't show for: ${statementsOf(repairText[3])}. The saved code stays the same.`
276
280
  : `Fixes the checks that failed in ${builtBy}.`;
277
281
  const plan = input.planDocument === null ? null : parseExecutionPlanDocument(input.planDocument);
278
282
  const milestones = plan !== null && plan.ok ? plan.document.milestones : [];
@@ -312,6 +316,14 @@ export function approvalSheetParts(input) {
312
316
  if (allowed !== null)
313
317
  allowing.push(allowed);
314
318
  allowing.push(scope.budgetMicrousd === null ? "no attempt limit" : `up to ${money(scope.budgetMicrousd)} per attempt`);
319
+ // Fallback agents are terms: who takes over when an agent runs out, with any that spend money said before the yes.
320
+ const chain = chainTermsLines(scope);
321
+ const fallbacks = liveChainFromJson(scope.proposedChainJson ?? null)?.slice(1) ?? [];
322
+ if (fallbacks.length > 0) {
323
+ const paid = fallbacks.filter(one => one.authMode === "api-key" || SUBSCRIPTION_CAPABLE[one.profile.provider] !== true);
324
+ // Each agent named once, the paid one said where it stands in the order.
325
+ allowing.push(`if it runs out of its plan, the work moves to ${fallbacks.map(one => `${agentNameWords(one.profile.provider, one.profile.model)}${paid.includes(one) ? " on an API key, which spends money" : ""}`).join(", then ")}`);
326
+ }
315
327
  const earlier = earlierVersionsWords(input.earlier.active, input.earlier.running);
316
328
  if (earlier !== null)
317
329
  allowing.push(earlier);
@@ -326,7 +338,7 @@ export function approvalSheetParts(input) {
326
338
  : input.deliverable === "report"
327
339
  ? html `An agent investigates without changing the repository. You'll hear when its report is ready.`
328
340
  : html `An agent starts in its own branch. You'll hear when it's ready to review.`;
329
- const submit = rebuild !== null ? html `Approve & build again` : html `Approve & start`;
341
+ const submit = rebuild !== null ? html `Approve and retry` : html `Approve`;
330
342
  // Everything else the digest binds, one tap away.
331
343
  const detail = (title, body, attrs = html ``) => isEmptyHtml(body) ? html `` : html `<section class="approval-detail"${attrs}><h3>${title}</h3>${body}</section>`;
332
344
  const revisionDetail = input.revision === null ? "" : detail("Earlier build", joinHtml([
@@ -340,9 +352,9 @@ export function approvalSheetParts(input) {
340
352
  const amendment = approvalAmendmentHtml(contract);
341
353
  const contractDetail = input.planDocument === null || !isEmptyHtml(amendment) ? html ``
342
354
  : contract === null || contract === undefined ? html `<p class="meta">No record compares this plan with what was filed. Check the rows above against your request.</p>`
343
- : "problem" in contract ? html `<p class="meta">${contract.problem} · <a href="/r/${contract.run}">run ${contract.run}</a></p>`
355
+ : "problem" in contract ? html `<p class="meta">${contract.problem} · <a href="/r/${contract.run}">Planner's record</a></p>`
344
356
  : !contract.filed && contract.revision !== true ? html `<p class="meta">Nothing was filed before planning, so every term is the planner's proposal.</p>`
345
- : html `<p class="meta">${contract.revision === true ? "Same terms as the previous version." : "The plan keeps exactly what you filed."} <a href="/r/${contract.run}">Planning run ${contract.run}</a></p>`;
357
+ : html `<p class="meta">${contract.revision === true ? "Same terms as the previous version." : "The plan keeps exactly what you filed."} <a href="/r/${contract.run}">Planner's record</a></p>`;
346
358
  const criteriaDetail = scope.acceptance.length === 0 ? html ``
347
359
  : html `<ul class="approval-signed-criteria">${scope.acceptance.map(one => html `<li><code>${one.id}</code> ${one.statement} <span class="meta">shown by ${one.evidence.map(kind => EVIDENCE_PLAIN[kind] ?? kind).join(", ")}</span></li>`)}</ul>`;
348
360
  const projection = route?.projection ?? null;
@@ -364,13 +376,13 @@ export function approvalSheetParts(input) {
364
376
  const details = joinHtml([
365
377
  html `<details class="approval-details"><summary>Plan details</summary>`,
366
378
  revisionDetail,
367
- repairText === null ? "" : detail("Signed goal", html `<p>${scope.goal}</p>`),
379
+ repairText === null ? "" : detail("Full goal", html `<p>${scope.goal}</p>`),
368
380
  detail("Plan record", contractDetail),
369
- detail("Signed criteria", criteriaDetail),
381
+ detail("Requirements", criteriaDetail),
370
382
  detail("Why these agents", agentsDetail, html ` id="${input.surface === "task" ? "approval-agents" : "chat-approval-agents"}"`),
371
383
  detail("Runtime limits", limits),
372
384
  scope.candidate ? detail("Saved commit", html `<p><code class="approval-commit">${scope.candidate}</code></p>`) : "",
373
- detail("Seal", html `<p class="meta">Approval binds to this exact wording. <span class="seal mono">signs ${shortDigest(scope.digest)}</span></p>`),
385
+ detail("Plan version", html `<p class="meta">Approving agrees to exactly this plan. <span class="seal mono" title="${planVersionWords(scope.digest)}">${planVersionWords(scope.digest)}</span></p>`),
374
386
  html `</details>`,
375
387
  ]);
376
388
  const passwordNote = input.surface === "task" ? "approval-password-note" : "chat-approval-password-note";
@@ -384,21 +396,25 @@ export function approvalSheetParts(input) {
384
396
  const field = (name, value, rows, label, hint = "") => html `<label class="approval-field"><span class="approval-field-label">${label}</span>${hint === "" ? "" : html `<span class="approval-field-hint">${hint}</span>`}<textarea name="${name}" rows="${Math.min(10, Math.max(rows, value.split("\n").reduce((sum, line) => sum + Math.max(1, Math.ceil(line.length / 80)), 0)))}" form="${editForm}">${value}</textarea></label>`;
385
397
  // Done when, one line per requirement in the signed order; the server
386
398
  // matches them back by position (the digest guard keeps that order).
387
- const requirement = (name, value, label, placeholder = "") => html `<li><input type="text" name="${name}" value="${value}" aria-label="${label}"${placeholder === "" ? "" : html ` placeholder="${placeholder}"`} form="${editForm}"></li>`;
399
+ // Each requirement says how we'll know it is met (#19): a picker over the kinds a plan names, read back by position.
400
+ const howPicker = (name, chosen, label, planned) => html `<select name="${name}" aria-label="${label}" form="${editForm}" class="approval-how">${planned !== null && planned.length > 1 ? html `<option value="keep"${chosen === "keep" ? html ` selected` : ""}>${`As planned: ${planned.map(kind => EVIDENCE_PLAIN[kind] ?? kind).join(", ")}`}</option>` : ""}${HOW_WE_KNOW.map(kind => html `<option value="${kind}"${chosen === kind ? html ` selected` : ""}>${HOW_WE_KNOW_WORDS[kind]}</option>`)}</select>`;
401
+ const requirement = (name, value, label, how, placeholder = "") => html `<li><input type="text" name="${name}" value="${value}" aria-label="${label}"${placeholder === "" ? "" : html ` placeholder="${placeholder}"`} form="${editForm}">${how}</li>`;
388
402
  const requirements = draft?.getAll("requirement") ?? scope.acceptance.map(one => one.statement);
403
+ const hows = draft?.getAll("requirement-how") ?? scope.acceptance.map(one => one.evidence.length === 1 ? one.evidence[0] : "keep");
389
404
  const added = draft?.get("requirement-new") ?? "";
405
+ const addedHow = draft?.get("requirement-new-how") ?? "manual-review";
390
406
  const editor = edit === null ? null : joinHtml([
391
407
  html `<details class="approval-edit" id="plan-editor"${draft === null && !edit.open ? "" : html ` open`}>`,
392
- html `<summary class="approval-link"><span class="approval-edit-open">Edit plan</span><span class="approval-edit-close">Cancel</span></summary>`,
408
+ html `<summary class="approval-link"><span class="approval-edit-open">Request changes</span><span class="approval-edit-close">Cancel</span></summary>`,
393
409
  html `<div class="approval-editor">`,
394
410
  edit.problem === null ? "" : html `<p class="problem" role="alert">${edit.problem}</p>`,
395
411
  field("goal", draft?.get("goal") ?? goalShown, 3, "Goal"),
396
412
  field("touches", draft?.get("touches") ?? scope.touches.join("\n"), 2, "Changes", "Only in these paths, one per line. Leave empty for any file."),
397
413
  field("not", draft?.get("not") ?? scope.outOfScope ?? "", 2, "Won’t touch"),
398
414
  html `<fieldset class="approval-field"><legend class="approval-field-label">Done when</legend>`,
399
- html `<span class="approval-field-hint">Clear a line to drop it.</span><ul class="approval-requirements">`,
400
- requirements.map((one, index) => requirement("requirement", one, `Requirement ${index + 1}`)),
401
- requirement("requirement-new", added, "Add a requirement", "Add a requirement"),
415
+ html `<span class="approval-field-hint">Clear a line to drop it. Beside each, how we'll know it is met.</span><ul class="approval-requirements">`,
416
+ requirements.map((one, index) => requirement("requirement", one, `Requirement ${index + 1}`, howPicker("requirement-how", hows[index] ?? "manual-review", `How we'll know requirement ${index + 1} is met`, scope.acceptance[index]?.evidence ?? null))),
417
+ requirement("requirement-new", added, "Add a requirement", howPicker("requirement-new-how", addedHow, "How we'll know the new requirement is met", null), "Add a requirement"),
402
418
  html `</ul></fieldset>`,
403
419
  html `<div class="approval-edit-act"><button type="submit" form="${editForm}">Save plan</button>`,
404
420
  // A plan with written steps keeps its own editor for them.
@@ -434,6 +450,7 @@ export function approvalSheetParts(input) {
434
450
  row("Changes", changes),
435
451
  row("Won’t touch", html `<p>${scope.outOfScope === null ? "Nothing is ruled out." : scope.outOfScope}</p>`),
436
452
  row("Done when", doneWhen),
453
+ chain.length === 0 ? "" : row("If it runs out", html `<p>${chain[0]}</p><ol class="approval-steps" data-approval-fallback>${chain.slice(1).map(one => html `<li>${one.replace(/^\d+\. /, "")}</li>`)}</ol>`),
437
454
  html `</dl>`,
438
455
  amendment,
439
456
  // The size, said once and plainly beside who builds: "Small change: fast model, no plan".
@@ -442,7 +459,7 @@ export function approvalSheetParts(input) {
442
459
  routeEvidenceHtml(route, "approval-evidence"),
443
460
  consent,
444
461
  ]);
445
- return { lead, rows, editor, editorForm, details, submit, label: rebuild !== null ? "Approve and build again" : "Approve and start", after, allowing, passwordNote };
462
+ return { lead, rows, editor, editorForm, details, submit, label: rebuild !== null ? "Approve and retry" : "Approve", after, allowing, passwordNote };
446
463
  }
447
464
  /** Earlier versions of a task still in flight, in plain words: queued
448
465
  * while they only wait, running once one has started; null when none. */
@@ -457,6 +474,9 @@ export class PermissionsWouldChange extends Error {
457
474
  }
458
475
  /** Evidence kinds in plain words, for the signed criteria in Details. */
459
476
  export const EVIDENCE_PLAIN = { check: "the project check", screenshot: "screenshots", "changed-path": "changed files", "manual-review": "your own check" };
477
+ /** The kinds a requirement's "How we'll know" picker offers, in the order it lists them (#19). */
478
+ export const HOW_WE_KNOW = ["check", "screenshot", "changed-path", "manual-review"];
479
+ export const HOW_WE_KNOW_WORDS = { check: "The project check passes", screenshot: "A screenshot shows it", "changed-path": "The changed files show it", "manual-review": "You check it yourself" };
460
480
  /** The permission choice a sealed profile carries, as the scope form names it. */
461
481
  export function permissionModeOfProfile(profile) {
462
482
  if (profile === null || profile === undefined)
@@ -471,30 +491,36 @@ export function permissionModeOfProfile(profile) {
471
491
  /** Done when, from the in-place editor: each line keeps its requirement's
472
492
  * id by position, and its evidence and guidance while its words stand; a
473
493
  * rewritten one is yours to check, like an added one; an emptied one is
474
- * dropped. */
494
+ * dropped. A "How we'll know" choice beside a line (#19) names its one kind
495
+ * instead; "keep", or no choice at all (an older form), changes nothing. The
496
+ * saved plan is a new version either way: it needs approving like any edit. */
475
497
  export function requirementsFromEditor(body, current) {
476
498
  const used = new Set(current.map(one => one.id));
477
499
  let next = 1;
478
500
  while (used.has(`c${next}`))
479
501
  next++;
480
502
  const plain = (raw) => raw.replace(/\s+/g, " ").trim();
503
+ const hows = body.getAll("requirement-how");
504
+ const howOf = (raw) => raw !== undefined && HOW_WE_KNOW.includes(raw) ? raw : null;
481
505
  const kept = body.getAll("requirement").slice(0, current.length).flatMap((raw, index) => {
482
506
  const statement = plain(raw);
483
507
  const criterion = current[index];
508
+ const how = howOf(hows[index]);
484
509
  if (statement === "")
485
510
  return [];
511
+ const evidence = how === null ? null : [how];
486
512
  return statement === plain(criterion.statement)
487
- ? [{ id: criterion.id, statement: criterion.statement, evidence: [...criterion.evidence], how: criterion.how }]
488
- : [{ id: criterion.id, statement, evidence: ["manual-review"], how: null }];
513
+ ? [{ id: criterion.id, statement: criterion.statement, evidence: evidence ?? [...criterion.evidence], how: criterion.how }]
514
+ : [{ id: criterion.id, statement, evidence: evidence ?? ["manual-review"], how: null }];
489
515
  });
490
516
  const added = plain(body.get("requirement-new") ?? "");
491
- return added === "" ? kept : [...kept, { id: `c${next}`, statement: added, evidence: ["manual-review"], how: null }];
517
+ return added === "" ? kept : [...kept, { id: `c${next}`, statement: added, evidence: [howOf(body.get("requirement-new-how") ?? undefined) ?? "manual-review"], how: null }];
492
518
  }
493
519
  export function consentDoorOf(scope, route) {
494
520
  if (scope === null)
495
- return { open: false, title: "There is no scope to approve yet", why: "write the scope first", road: "scope" };
521
+ return { open: false, title: "There is no plan to approve yet", why: "write the plan first", road: "scope" };
496
522
  if (scope.profileState === "unresolved") {
497
- return { open: false, title: "The agent setup isn’t ready yet", why: scope.unresolvedReason ?? "the scope cannot say exactly what would run", road: "scope" };
523
+ return { open: false, title: "The agent setup isn’t ready yet", why: scope.unresolvedReason ?? "the plan can't say exactly what would run", road: "scope" };
498
524
  }
499
525
  // THE SAME STRICT PROJECTION THE SEAL USES (v48 integrity): before any
500
526
  // nonce, password field, or approve action renders, the stored scope
@@ -509,17 +535,17 @@ export function consentDoorOf(scope, route) {
509
535
  return { open: false, title: "The provider’s auth mode can’t be read", why: `${authority.problem} — restate it, then approve`, road: "agents" };
510
536
  }
511
537
  if (authority.reason === "unrouted") {
512
- return { open: false, title: "This scope predates agent routing", why: "its approval no longer stands, and an approval now must name exactly which agent plans, builds, and revises — re-file the scope (edit and save it) to route it under today’s agents, then approve it", road: "scope" };
538
+ return { open: false, title: "This plan was made before agents were chosen per task", why: "Toolroll is choosing its agents, then you'll approve them. An approval now names exactly which agent plans, builds and fixes it: edit and save the plan to choose today’s agents, then approve it", road: "scope" };
513
539
  }
514
- return { open: false, title: "The agents on file can’t be read", why: `${authority.problem} — re-file the scope (edit and save it) so it is routed again under today’s agents`, road: "scope" };
540
+ return { open: false, title: "The agents on file can’t be read", why: `${authority.problem}. Edit and save the plan to choose today’s agents again`, road: "scope" };
515
541
  }
516
542
  if (route === null || route === undefined)
517
543
  return { open: true };
518
544
  if (route.kind === "unreadable") {
519
- return { open: false, title: "The agents on file can’t be read", why: `${route.problem} — re-file the scope (edit and save it) so it is routed again under today’s agents`, road: "scope" };
545
+ return { open: false, title: "The agents on file can’t be read", why: `${route.problem}. Edit and save the plan to choose today’s agents again`, road: "scope" };
520
546
  }
521
547
  if (route.kind === "legacy") {
522
- return { open: false, title: "This scope predates agent routing", why: "its approval no longer stands, and an approval now must name exactly which agent plans, builds, and revises — re-file the scope (edit and save it) to route it under today’s agents, then approve it", road: "scope" };
548
+ return { open: false, title: "This plan was made before agents were chosen per task", why: "Toolroll is choosing its agents, then you'll approve them. An approval now names exactly which agent plans, builds and fixes it: edit and save the plan to choose today’s agents, then approve it", road: "scope" };
523
549
  }
524
550
  if (route.projection !== null && route.projection.problems.length > 0) {
525
551
  return { open: false, title: "The agents on file can’t run", why: `${route.projection.problems.join("; ")} — change the agents, then approve`, road: "agents" };
@@ -530,7 +556,7 @@ export function consentDoorOf(scope, route) {
530
556
  * the reason and the one act that opens it. */
531
557
  export function consentClosedHtml(taskId, door, surface) {
532
558
  const href = `${taskHref(taskId)}#${door.road === "agents" ? "agents" : "scope"}`;
533
- const act = door.road === "agents" ? "Change the agents →" : "Edit and re-file the scope →";
559
+ const act = door.road === "agents" ? "Change the agents →" : "Edit the plan →";
534
560
  if (surface === "chat") {
535
561
  return html `<section class="card chat-action-card consent-closed" id="task-chat-action"><span class="eyebrow">approval needs attention</span><h2>${door.title}</h2><p class="meta">${door.why}</p><a class="button-link" href="${href}">${act}</a></section>`;
536
562
  }
@@ -541,12 +567,12 @@ export function consentClosedHtml(taskId, door, surface) {
541
567
  }
542
568
  export function profileWords(scope) {
543
569
  if (scope.profileState === "unresolved") {
544
- return html `<p class="meta"><strong>Filed but unapprovable</strong> — ${scope.unresolvedReason ?? "the scope cannot say exactly what would run"}. Restate the scope to fix it.</p>`;
570
+ return html `<p class="meta"><strong>Filed, but it can't be approved yet</strong> — ${scope.unresolvedReason ?? "the plan can't say exactly what would run"}. Edit the plan to fix it.</p>`;
545
571
  }
546
572
  const profile = scope.profile ?? null;
547
573
  if (profile === null) {
548
574
  return (scope.digestVersion ?? 1) < 2
549
- ? html `<p class="meta">Approved before routing was bound — pinned at upgrade to the configuration of that day</p>`
575
+ ? html `<p class="meta">Approved before agents were chosen per task: it keeps the setup of that day</p>`
550
576
  : html ``;
551
577
  }
552
578
  const repair = profile.repairModel === "inherit" ? "same model" : profile.repairModel;
@@ -559,7 +585,7 @@ export function profileWords(scope) {
559
585
  : profile.provider === "gemini"
560
586
  ? html `<p class="meta">Runs on <span class="mono">gemini · ${profile.model}</span> — ${profile.approvalArgv === "yolo" ? "Full access via --approval-mode yolo; every tool auto-approved" : "Auto via --approval-mode auto_edit; edits auto-approved, other tools refused"}, no turn limit (${Math.round(profile.timeoutSeconds / 60)} min ${profile.timeoutKind === "idle" ? "without progress" : "per attempt"}), spend reported in tokens only; repairs on ${repair}, ${Math.round(profile.repairTimeoutSeconds / 60)} min</p>`
561
587
  : isAcpProfile(profile)
562
- ? html `<p class="meta">Runs on <span class="mono">${ACP_AGENTS[profile.provider].name} (via ACP) · ${profile.model}</span> — full access inside the OS fence; the agent runs any tool it chooses and nothing asks. No turn limit: stops after ${Math.round(profile.timeoutSeconds / 60)} min, or ${Math.round(profile.idleTimeoutSeconds / 60)} min silent; repairs on ${repair}, ${Math.round(profile.repairTimeoutSeconds / 60)} min, or ${Math.round(profile.repairIdleTimeoutSeconds / 60)} min silent.</p>`
588
+ ? html `<p class="meta">Runs on <span class="mono">${ACP_AGENTS[profile.provider].name} (via ACP) · ${profile.model}</span> — full access inside Toolroll's sandbox; the agent runs any tool it chooses and nothing asks. No turn limit: stops after ${Math.round(profile.timeoutSeconds / 60)} min, or ${Math.round(profile.idleTimeoutSeconds / 60)} min silent; repairs on ${repair}, ${Math.round(profile.repairTimeoutSeconds / 60)} min, or ${Math.round(profile.repairIdleTimeoutSeconds / 60)} min silent.</p>`
563
589
  : html `<p class="meta">Runs on <span class="mono">${profile.provider} · ${profile.model}</span> — ${profile.sandboxMode === "danger-full-access" ? "FULL permissions via --dangerously-bypass-approvals-and-sandbox; nothing asks" : "workspace-write sandbox"}, no turn limit (${Math.round(profile.timeoutSeconds / 60)} min ${profile.timeoutKind === "idle" ? "without progress" : "per attempt"}); repairs on ${repair}, ${Math.round(profile.repairTimeoutSeconds / 60)} min</p>`;
564
590
  // What the build may reach is part of what is approved (egress.ts): said on every approval form.
565
591
  // Its sandbox terms (project-sandbox.ts): what it reads always, and any widening, part of what the yes agrees to.
@@ -748,7 +774,7 @@ export function agentsCardHtml(taskId, view, canEdit) {
748
774
  routeEvidenceHtml(view),
749
775
  p === null
750
776
  ? view.kind === "unreadable"
751
- ? html `<p class="agents-halted">Nothing runs for this task until its scope is filed again and approved.</p>`
777
+ ? html `<p class="agents-halted">Nothing runs for this task until its plan is saved again and approved.</p>`
752
778
  : ""
753
779
  : joinHtml([
754
780
  p.halted ? html `<p class="agents-halted">Paused: a provider these agents need is reported unavailable. Nothing else is used in its place — choose another agent below, or restore the provider and report readiness again.</p>` : "",
@@ -794,7 +820,7 @@ export function approvalProofWords(proof) {
794
820
  if (proof.method === "chat")
795
821
  return " (from your paired chat)";
796
822
  if (proof.method === "carried")
797
- return " (carried from the release approval)";
823
+ return " (from the release approval)";
798
824
  // Easy-start C5: this computer's owner approving attended work in local mode, with no password.
799
825
  if (proof.method === "local-owner")
800
826
  return " (on this computer, no password)";
@@ -807,11 +833,11 @@ export function approveRefusalWords(reason) {
807
833
  if (reason === "machine-proof")
808
834
  return "not approved: only a person can approve this. Approve it on the task page or from your paired chat";
809
835
  if (reason === "archived")
810
- return "not approved: this task is put away. Restore it first";
836
+ return "not approved: this task is archived. Restore it first";
811
837
  if (reason === "unrouted")
812
- return "not approved: this scope predates agent routing and its old approval no longer stands — edit and re-file the scope so it is routed under today’s agents, then approve it";
838
+ return "not approved: this plan was made before agents were chosen per task, so its old approval no longer stands. Edit and save the plan to choose today’s agents, then approve it";
813
839
  if (reason === "profile-unresolved")
814
- return "not approved: the scope cannot name an exact agent for every role — fix the agent setup and re-file it";
840
+ return "not approved: the plan can't name an exact agent for every role. Fix the agent setup, then save the plan again";
815
841
  if (reason === "password-now")
816
842
  return "not approved: this needs your password typed now — protected work, raised permissions, or a plan an agent wrote can't use a remembered one";
817
843
  // A wrong password, someone who isn't an approver (or not for this project), a revoked login: one answer for all, so
@@ -941,7 +967,7 @@ export function taskControlHtml(control, taskId, csrf, surface, inert = false, s
941
967
  return html ``;
942
968
  const guarded = csrf !== "" && !inert;
943
969
  const runForm = (path, className, button) => postForm(`${taskHref(taskId)}/${path}`, button, { attrs: { class: className }, returnTo: surface, hidden: { run: control.run } });
944
- const role = (word) => word.role === "planner" ? "plan attempt" : word.role === "scout" ? "scouting attempt" : word.role === "reviewer" ? "review" : "build";
970
+ const role = (word) => word.role === "planner" ? "plan attempt" : word.role === "scout" ? "research attempt" : word.role === "reviewer" ? "review" : "build";
945
971
  if (control.kind === "stop" && surface === "chat") {
946
972
  return joinHtml([
947
973
  html `<section class="card task-control" id="task-control" data-task-control="stop" data-control-run="${control.run}"><details class="chat-stop-confirm"><summary>Stop task</summary>`,
@@ -953,7 +979,7 @@ export function taskControlHtml(control, taskId, csrf, surface, inert = false, s
953
979
  if (control.kind === "stop") {
954
980
  return joinHtml([
955
981
  html `<section class="card task-control" id="task-control" data-task-control="stop" data-control-run="${control.run}" aria-label="stop this attempt">`,
956
- html `<div class="task-control-copy"><details><summary>Stop details · ${role(control)} #${control.run}</summary><p class="meta">Stopping ends only this attempt's own processes. Its branch, uncommitted work, evidence, and decisions stay preserved; other tasks keep running.</p></details></div>`,
982
+ html `<div class="task-control-copy"><details><summary>Stop details · ${role(control)} #${control.run}</summary><p class="meta">Stopping ends only this attempt's own processes. Its branch, uncommitted work, saved files and decisions are kept; other tasks keep running.</p></details></div>`,
957
983
  stopElsewhere || !guarded || livePause === null || livePause.run !== control.run ? "" : pauseFormsHtml(taskId, livePause),
958
984
  stopElsewhere ? "" : guarded
959
985
  ? runForm("stop", "inline task-control-form task-stop-form", html `<button type="submit" class="danger task-control-button">Stop</button>`)
@@ -974,7 +1000,7 @@ export function taskControlHtml(control, taskId, csrf, surface, inert = false, s
974
1000
  const settled = control.stop.settledAt === null ? "" : ` · settled ${stopWhen(control.stop.settledAt)} (${control.stop.settlement ?? "?"})`;
975
1001
  const kept = control.stop.settlement === "finished"
976
1002
  ? html `The attempt reached its own ending (${control.outcome ?? "?"}) before the stop took effect; that outcome stands.`
977
- : html `${control.committed ? "Its commit is on the branch as a reviewable artifact; a" : "A"}ny uncommitted work is preserved${control.worktree === null ? "" : html ` in <span class="mono">${control.worktree}</span>`}. Resuming takes a fresh claim, re-proves the signed scope, and requires fresh proof — nothing is approved by resuming.`;
1003
+ : html `${control.committed ? "Its commit is on the branch as a reviewable artifact; a" : "A"}ny uncommitted work is preserved${control.worktree === null ? "" : html ` in <span class="mono">${control.worktree}</span>`}. Resuming checks the approved plan again and needs new checks — resuming approves nothing.`;
978
1004
  return joinHtml([
979
1005
  html `<section class="card task-control" id="task-control" data-task-control="paused" data-control-run="${control.run}" aria-label="paused attempt">`,
980
1006
  html `<div class="task-control-copy"><span class="eyebrow">paused</span><strong>${role(control)} #${control.run} was stopped by <span class="mono">${control.stop.requestedBy}</span></strong>`,
@@ -991,23 +1017,19 @@ export function taskControlHtml(control, taskId, csrf, surface, inert = false, s
991
1017
  * does and does not do, the gate that would still keep work from
992
1018
  * starting, and the password typed again. */
993
1019
  export function resumeCeremonyPage(chrome, data) {
994
- const back = html `<p class="meta"><a href="${data.returnTo === "chat" ? taskChatHref(data.taskId) : taskHref(data.taskId)}">Keep paused</a></p>`;
995
1020
  const control = data.control;
1021
+ // One card, one button (docs/plans/plain-toolroll.md P1.3): the same post, nonce, run and password rule as before.
1022
+ const blocked = !data.approved ? "The plan isn't approved as it stands, so nothing builds until it is approved again."
1023
+ : data.gate === null ? null : `${data.gate.summary}: ${data.gate.detail}`;
996
1024
  return screen("resume", joinHtml([
997
- html `<h1>Resume task?</h1><p class="resume-target"><strong>${data.taskTitle}</strong> · ${data.taskId} · Run #${control.run}</p>`,
998
- html `<div class="card resume-ceremony">`,
999
- html `<p class="row">Lifts this stop’s hold. Other holds stay in place.</p>`,
1000
- html `<p class="row">Continues saved work${control.committed ? " and its commit" : ""} under the current approved scope. The next attempt needs fresh evidence; the earlier handoff cannot count as a new result.</p>`,
1001
- html `<p class="row">Resuming grants no new approval or publishing permission. Scope, budget, agents, verification, and review limits still apply.</p>`,
1002
- html `<details><summary>Stop record and saved work</summary><p>Run #${control.run} was stopped by <span class="mono">${control.stop.requestedBy}</span> at ${stopWhen(control.stop.requestedAt)}${control.stop.settledAt === null ? "" : ` and settled ${stopWhen(control.stop.settledAt)} (${control.stop.settlement ?? "?"})`}.</p>${control.worktree === null ? "" : html `<p class="mono">${control.worktree}</p>`}</details>`,
1003
- data.approved ? "" : html `<p class="row problem-words"><strong>The scope is not approved as it stands</strong> — resuming lifts the pause, but no worker spends until the scope is approved again</p>`,
1004
- data.gate === null ? "" : html `<p class="row"><strong>Before work starts:</strong> ${data.gate.summary} — ${data.gate.detail}</p>`,
1005
- html `</div>`,
1025
+ html `<h1>Resume build #${control.run}?</h1>`,
1006
1026
  postForm(`${taskHref(data.taskId)}/resume`, joinHtml([
1007
- html `${reauth(html `<label>Confirm with your password<input type="password" name="token" autocomplete="current-password"></label>`)}`,
1008
- html `<button type="submit">Resume task</button>`,
1027
+ html `<p class="resume-target"><strong>${data.taskTitle}</strong></p>`,
1028
+ html `<p class="row">It continues from the saved work${control.committed ? " and its commit" : ""}. Resuming approves nothing new.</p>`,
1029
+ blocked === null ? "" : html `<p class="row problem-words">${blocked}</p>`,
1030
+ html `${reauth(html `<label>Your password<input type="password" name="token" autocomplete="current-password"></label>`)}`,
1031
+ html `<button type="submit">Resume</button>`,
1009
1032
  ], "\n"), { attrs: { class: "card resume-form" }, returnTo: data.returnTo, hidden: { nonce: data.nonceValue, run: control.run } }),
1010
- back,
1011
1033
  ], "\n"), { chrome });
1012
1034
  }
1013
1035
  export function taskViewSwitch(taskId, active) {
@@ -1032,6 +1054,7 @@ export function statusActionHref(status, taskId, runId, prUrl, repo) {
1032
1054
  case "open-review": return repo === undefined ? reviewHref(taskId) : reviewHref(taskId, runId);
1033
1055
  case "open-run": return runId === null ? taskHref(taskId) : `/r/${runId}`;
1034
1056
  case "open-pr": return safePrUrl(prUrl) ?? (repo === undefined ? reviewHref(taskId) : reviewHref(taskId, runId));
1057
+ case "open-findings": return `${taskHref(taskId)}#findings`;
1035
1058
  case "open-task":
1036
1059
  default: return taskHref(taskId);
1037
1060
  }
@@ -1041,7 +1064,7 @@ export function statusActionHref(status, taskId, runId, prUrl, repo) {
1041
1064
  /** A not-yet-finished task's shared headline from its dispatch diagnosis (task-status.ts). */
1042
1065
  export function dispatchHeadline(d) {
1043
1066
  const read = stageOfDispatch(d);
1044
- return taskStatusOf({ stage: read.stage, ...(read.need === undefined ? {} : { need: read.need }) }).headline;
1067
+ return taskStatusOf({ stage: read.stage, ...(read.need === undefined ? {} : { need: read.need }), ...(read.wait === undefined ? {} : { wait: read.wait }) }).headline;
1045
1068
  }
1046
1069
  export function statusLineHtml(status, extra = "") {
1047
1070
  return html `<span class="status-line" data-work-status="${status.token}" data-tone="${status.tone}"><i class="status-dot" aria-hidden="true"></i><span class="status-label">${status.label}</span>${extra}</span>`;
@@ -1169,7 +1192,7 @@ export function planRevisionLedgerHtml(ledger, taskId, csrf) {
1169
1192
  : joinHtml([
1170
1193
  html `<div class="plan-revision-pending"><span class="eyebrow">decision needed</span><h3>The agent recommends a plan change</h3>`,
1171
1194
  html `<p>${pendingRevision.reason}</p>`,
1172
- html `<p class="meta">Revision ${pendingRevision.revision}${pendingRevision.authorityKind === "authority-change" ? ` changes ${pendingRevision.changedFields.map(changedAuthority).join(" and ")} from what you approved, so work is paused until you decide.` : " only changes the route, not the approved outcome."}${pendingRevision.evidenceLink === null ? "" : ` Evidence: ${pendingRevision.evidenceLink}.`}</p>`,
1195
+ html `<p class="meta">Revision ${pendingRevision.revision}${pendingRevision.authorityKind === "authority-change" ? ` changes ${pendingRevision.changedFields.map(changedAuthority).join(" and ")} from what you approved, so work is paused until you decide.` : " only changes the route, not the approved outcome."}${pendingRevision.evidenceLink === null ? "" : ` Why: ${pendingRevision.evidenceLink}.`}</p>`,
1173
1196
  executionPlanHtml(pendingRevision.document, true),
1174
1197
  csrf === "" || pendingRevision.id === null
1175
1198
  ? ""
@@ -1211,7 +1234,7 @@ export function executionPlanHtml(document, compact = false) {
1211
1234
  html `<div class="execution-milestones"><span class="approval-label">milestones</span>${items(plan.milestones, true)}</div>`,
1212
1235
  html `<div class="execution-support"><div><span class="approval-label">dependencies</span>${items(plan.dependencies)}</div>`,
1213
1236
  html `<div><span class="approval-label">risks &amp; mitigations</span>${items(plan.risks)}</div></div>`,
1214
- html `<div class="execution-proof"><span class="approval-label">proof of done</span>${items(plan.proof)}</div>`,
1237
+ html `<div class="execution-proof"><span class="approval-label">how we'll know it's done</span>${items(plan.proof)}</div>`,
1215
1238
  html `</div>`,
1216
1239
  ]);
1217
1240
  }
@@ -1234,7 +1257,7 @@ export function revisionLineageWords(lineage) {
1234
1257
  const chain = lineage.ancestors.length > 1 ? ` · lineage ${lineage.ancestors.join(" → ")}` : "";
1235
1258
  words.push(`revises ${lineage.sourceTask}${lineage.sourceRun === null ? "" : ` (build #${lineage.sourceRun})`}${chain}`);
1236
1259
  if (!lineage.sourceHadScope) {
1237
- words.push("the source had no scope — nothing was inherited; the placeholder rubric waits for a real one");
1260
+ words.push("the earlier version had no plan, so nothing was copied; the placeholder requirements wait for real ones");
1238
1261
  }
1239
1262
  else if (lineage.terms !== null) {
1240
1263
  const terms = lineage.terms;
@@ -1318,7 +1341,7 @@ export function taskBodyParts(data) {
1318
1341
  const peek = html `<p class="meta">What is changing right now</p><div id="run-peek"><p class="meta">Watching… the first look lands within 15 seconds</p></div><p class="meta" id="run-peek-stamp"></p>`;
1319
1342
  const transcript = liveRun.provider !== "claude"
1320
1343
  ? html `<p class="meta">The live transcript needs the claude harness for now — this build runs on ${liveRun.provider}</p>`
1321
- : html `<p class="meta">What the agent is saying · display only — this is not evidence, and the machine running the agent could alter it</p><pre id="live-transcript" class="mono" style="max-height:18rem;overflow:auto;white-space:pre-wrap"></pre><p class="meta" id="live-transcript-state"></p>`;
1344
+ : html `<p class="meta">What the agent is saying</p><pre id="live-transcript" class="mono" style="max-height:18rem;overflow:auto;white-space:pre-wrap"></pre><p class="meta" id="live-transcript-state"></p>`;
1322
1345
  return html `<div class="card attempt-live" data-live-run="${liveRun.id}">${head}${peek}${transcript}${door}</div>`;
1323
1346
  })();
1324
1347
  // One truthful answer to the first question on a queued task: "will this
@@ -1338,9 +1361,9 @@ export function taskBodyParts(data) {
1338
1361
  if (task.state !== "done") {
1339
1362
  const diagnosis = data.dispatch ?? null;
1340
1363
  if (diagnosis === null)
1341
- return box("problem", "Dispatch unknown", "Refresh this task before relying on its scheduler state.", "unknown");
1364
+ return box("problem", "Can't tell yet", "Reload this task to see whether it can start.", "unknown");
1342
1365
  if (diagnosis.code === "running" && liveRun !== undefined) {
1343
- return box("ok", diagnosis.summary, html `Worker <span class="mono">${liveRun.runner}</span> owns <a href="/r/${liveRun.id}">build #${liveRun.id}</a>.`, diagnosis.code);
1366
+ return box("ok", diagnosis.summary, html `Builder <span class="mono">${liveRun.runner}</span> is on <a href="/r/${liveRun.id}">build #${liveRun.id}</a>.`, diagnosis.code);
1344
1367
  }
1345
1368
  const blocker = diagnosis.blockerTaskId === null
1346
1369
  ? null
@@ -1348,11 +1371,11 @@ export function taskBodyParts(data) {
1348
1371
  const action = (() => {
1349
1372
  switch (diagnosis.action) {
1350
1373
  case "start-worker": return "";
1351
- case "write-scope": return html ` <a href="#scope">Write the success contract</a> or use <strong>plan first</strong>.`;
1374
+ case "write-scope": return html ` <a href="#scope">Write the plan</a> or use <strong>plan first</strong>.`;
1352
1375
  case "select-agent": return html ` <a href="#scope">Choose an available provider and model</a>.`;
1353
- case "approve-scope": return html ` <a href="#approve">Review and sign the exact scope</a>.`;
1376
+ case "approve-scope": return html ` <a href="#approve">Review the plan and approve it</a>.`;
1354
1377
  case "answer-decision": return html ` <a href="#decisions">Answer the waiting question</a>.`;
1355
- case "unhold": return html ` Use <strong>Remove hold</strong> when it can continue.`;
1378
+ case "unhold": return diagnosis.code === "storage-blocked" ? html ` Use <strong>Try again</strong> once it's allowed.` : html ` Use <strong>Remove hold</strong> when it can continue.`;
1356
1379
  // The reason is the detail itself; Retry is the status card's own act, said once.
1357
1380
  case "retry-task": return "";
1358
1381
  case "repair-capability": return html ` <a href="/caps">Repair the requirement</a>.`;
@@ -1463,7 +1486,7 @@ export function taskBodyParts(data) {
1463
1486
  // c2" — never pretending the machine always disagreed.
1464
1487
  const machineNote = !reviewConflict(proof.proofMatrix, proof.machineVerdict, proof.proofVerdict)
1465
1488
  ? ""
1466
- : html `<p class="meta">An independent review found conflicting evidence.</p>`;
1489
+ : html `<p class="meta">An independent review found the report doesn't match the changes.</p>`;
1467
1490
  const chainHtml = repairChainHtml(proof.repairChain);
1468
1491
  // v51: semantic coverage sits beside the matrix, never inside the
1469
1492
  // verdict word — what the independent reviewer settled, under the
@@ -1480,7 +1503,7 @@ export function taskBodyParts(data) {
1480
1503
  // Damaged evidence (repair 2026-09-14): the box wears the receipt's
1481
1504
  // own problem words — never "ok" with a verdict that predates the damage.
1482
1505
  if (resultStatus.token === "evidence-damaged") {
1483
- return withReview(box("problem", resultStatus.label, html `<a href="/r/${proof.runId}">Build #${proof.runId}</a> finished. ${resultStatus.detail}${publicationWords}`, "evidence-damaged"), html `<div class="proof-review-actions"><a class="button-link" href="/r/${proof.runId}">Review the evidence problems</a></div>`, proofDetails);
1506
+ return withReview(box("problem", resultStatus.label, html `<a href="/r/${proof.runId}">Build #${proof.runId}</a> finished. ${resultStatus.detail}${publicationWords}`, "evidence-damaged"), html `<div class="proof-review-actions"><a class="button-link" href="/r/${proof.runId}">Review what doesn't match</a></div>`, proofDetails);
1484
1507
  }
1485
1508
  // A recorded pass the check read now doesn't stand behind (unreadable, failed since, or not read here) never
1486
1509
  // reads "passed": the box says what holds now (task-status.ts approvedCheckOf).
@@ -1501,7 +1524,7 @@ export function taskBodyParts(data) {
1501
1524
  const machineToken = proof.proofVerdict === "refuted" ? dispatchStatusToken("refuted") : dispatchStatusToken("short");
1502
1525
  return withReview(box(accepted ? "ok" : "problem", resultStatus.label, html `<a href="/r/${proof.runId}">Build #${proof.runId}</a> finished. ${verificationExplanation(proof.proofVerdict, proof.proofReasons)}${accepted ? html ` An approver accepted it by hand; that acceptance leaves the machine's verdict above unchanged.` : ""}${publicationWords}`, machineToken), html `<div class="proof-review-actions"><a class="button-link" href="${reviewHref(task.id)}">${accepted ? "Review the recorded exception" : resultStatus.action?.label ?? "Review evidence"}</a>${acceptForm}</div>`, proofDetails);
1503
1526
  }
1504
- return box("problem", "Dispatch unknown", "Refresh this task before relying on its scheduler state.", "unknown");
1527
+ return box("problem", "Can't tell yet", "Reload this task to see whether it can start.", "unknown");
1505
1528
  })();
1506
1529
  // Operator steering (arc 1, v120): notes for the agent, each wearing the
1507
1530
  // same words the CLI and the lead use — read (and when), waiting for the
@@ -1517,15 +1540,17 @@ export function taskBodyParts(data) {
1517
1540
  ? ""
1518
1541
  : joinHtml([
1519
1542
  postForm(`${taskHref(task.id)}/steer`, html `<input type="text" name="note" placeholder="guidance for the agent" aria-label="steering note" style="width:100%;max-width:28rem">${data.steeringRun == null ? "" : html `<input type="hidden" name="run" value="${String(data.steeringRun)}"><input type="hidden" name="key" value="${randomUUID()}"><input type="hidden" name="return" value="task">`}<button type="submit">${data.steeringRun == null ? "Save for the next attempt" : "Steer"}</button>`, { attrs: { class: "row" } }),
1520
- html `<p class="meta">${steerDestinationWords(steeringLive)} A note can't widen the approved scope.</p>`,
1543
+ html `<p class="meta">${steerDestinationWords(steeringLive)} A note can't widen the approved plan.</p>`,
1521
1544
  ]);
1522
1545
  const steeringCard = steerRows === "" && steerForm === "" ? "" : html `<h2>Steering</h2>${steerRows}${steerForm}`;
1523
1546
  const holds = data.holds.length === 0
1524
1547
  ? ""
1525
1548
  : joinHtml([
1526
1549
  html `<h2>Holds</h2>`,
1527
- joinHtml(data.holds.map(hold => html `<p class="row">${holdOwnerWords(hold.ownerKind)} — ${hold.reason}${hold.until === null ? "" : html ` <span class="meta">until ${whenTime(hold.until)}</span>`}</p>`), "\n"),
1528
- html `<p class="meta">Only your hold can be lifted here — waits caused by questions, incidents, or retry delays clear on their own</p>`,
1550
+ joinHtml(data.holds.map(
1551
+ // The storage check's hold (dispose.ts holdForStorage) says what it waits for, in the check's own words.
1552
+ hold => html `<p class="row">${hold.ownerKind === "backoff" && hold.reason.startsWith(STORAGE_HOLD_PREFIX) ? `waiting for macOS permission — ${hold.reason.slice(STORAGE_HOLD_PREFIX.length)}` : `${holdOwnerWords(hold.ownerKind)} — ${hold.reason}`}${hold.until === null ? "" : html ` <span class="meta">until ${whenTime(hold.until)}</span>`}</p>`), "\n"),
1553
+ html `<p class="meta">Only your hold can be lifted here. Waits for a question, repeated failures or a retry clear on their own.</p>`,
1529
1554
  ]);
1530
1555
  const approval = approvalOf(scope);
1531
1556
  // A refused save from the sheet's in-place editor reopens that editor with
@@ -1533,22 +1558,22 @@ export function taskBodyParts(data) {
1533
1558
  const inlineScopeDraft = data.scopeDraft?.has("requirement-new") === true;
1534
1559
  const scopeDraft = inlineScopeDraft ? undefined : data.scopeDraft;
1535
1560
  const scopeCard = scope === null
1536
- ? html `<p class="meta">No scope proposed — nothing builds this until one is approved</p>`
1561
+ ? html `<p class="meta">No plan yet. Nothing builds until one is approved.</p>`
1537
1562
  : joinHtml([
1538
1563
  html `<div class="card">`,
1539
1564
  html `<p><strong>Goal</strong></p><p class="recap">${scope.goal}</p>`,
1540
1565
  scope.outOfScope === null ? "" : html `<p><strong>Not this</strong></p><p class="recap">${scope.outOfScope}</p>`,
1541
- scope.touches.length === 0 ? "" : html `<p class="scope-paths"><strong>Touches</strong> ${scope.touches.join(", ")}</p>`,
1566
+ scope.touches.length === 0 ? "" : html `<p class="scope-paths"><strong>Files</strong> ${scope.touches.join(", ")}</p>`,
1542
1567
  html `<p><strong>Quality</strong> ${qualityModeTitle(scope.qualityMode ?? "default")}</p>`,
1543
1568
  acceptanceCeremonyHtml(scope.acceptance),
1544
1569
  scope.releaseTerms == null ? "" : html `<p><strong>Release</strong></p><p class="recap">${releaseTermsSentence(scope.releaseTerms)}</p>${data.releaseCiRunning === true ? html `<p class="meta">${RELEASE_CI_RUNNING_WORDS}</p>` : ""}${scope.releaseTerms.policy.carry ? html `<details><summary>What asks again</summary><ul>${joinHtml(releaseAsksAgainWords(scope.releaseTerms).map(line => html `<li>${line}</li>`), "")}</ul></details>` : ""}`,
1545
1570
  approval.approved && data.autoRelease != null
1546
- ? html `<p class="meta scope-seal">${autoReleaseWords(data.autoRelease)} · <span class="seal">signs ${shortDigest(scope.digest)}</span></p>`
1571
+ ? html `<p class="meta scope-seal">${autoReleaseWords(data.autoRelease)}</p>${planVersionDetailsHtml(scope.digest)}`
1547
1572
  : approval.approved && data.release?.kind === "carried"
1548
- ? html `<p class="meta scope-seal">Carried from ${data.release.approver ?? "a person"}'s release approval · <a href="${taskHref(data.release.anchorTask)}">Release ${data.release.version}</a> · <span class="seal">signs ${shortDigest(scope.digest)}</span></p>`
1573
+ ? html `<p class="meta scope-seal">Approved with ${data.release.approver ?? "a person"}'s release approval · <a href="${taskHref(data.release.anchorTask)}">Release ${data.release.version}</a></p>${planVersionDetailsHtml(scope.digest)}`
1549
1574
  : approval.approved
1550
- ? html `<p class="meta scope-seal">Approved by ${approval.by}${approvalProofWords(data.approvalProof)} · ${whenTime(approval.at)} · <span class="seal">signs ${shortDigest(scope.digest)}</span><span class="so-sr-only"> — approval binds to this exact wording</span></p>`
1551
- : html `<p class="meta"><span class="seal">signs ${shortDigest(scope.digest)}</span> — approval binds to this exact wording</p><p class="meta">Not approved${approval.reason === "changed" ? " — approved once, then rewritten" : ""}</p>`,
1575
+ ? html `<p class="meta scope-seal">Approved by ${approval.by}${approvalProofWords(data.approvalProof)} · ${whenTime(approval.at)}</p>${planVersionDetailsHtml(scope.digest)}`
1576
+ : html `<p class="meta">${approval.reason === "changed" ? "Edited after approval" : "Not approved"}</p>${planVersionDetailsHtml(scope.digest)}`,
1552
1577
  html `</div>`,
1553
1578
  ], "\n");
1554
1579
  // The scout's report (mate arc §10): title, summary, the document inert,
@@ -1557,20 +1582,20 @@ export function taskBodyParts(data) {
1557
1582
  // blank — the same rule as the revision brief.
1558
1583
  const reportCard = data.report === null || data.report === undefined
1559
1584
  ? data.deliverable === "report"
1560
- ? html `<div class="card"><p><strong>Scout task</strong> <span class="meta">delivers a report, never a branch — a read-only session investigates the goal and its report appears here when it finishes</span></p></div>`
1585
+ ? html `<div class="card"><p><strong>Research task</strong> <span class="meta">delivers a report, never a branch. A read-only session looks into the goal, and its report appears here when it finishes.</span></p></div>`
1561
1586
  : ""
1562
1587
  : !data.report.ok
1563
- ? html `<div class="card"><p><strong>The report</strong></p><p class="meta">${data.report.problem} · <a href="/r/${data.report.run}">run ${data.report.run}</a></p></div>`
1588
+ ? html `<div class="card"><p><strong>The report</strong></p><p class="meta">${data.report.problem} · <a href="/r/${data.report.run}">build #${data.report.run}</a></p></div>`
1564
1589
  : joinHtml([
1565
1590
  html `<div class="card report">`,
1566
- html `<p><strong>${data.report.report.title}</strong> <span class="meta">the scout's report · <a href="/r/${data.report.run}">run ${data.report.run}</a></span></p>`,
1591
+ html `<p><strong>${data.report.report.title}</strong> <span class="meta">the research report · <a href="/r/${data.report.run}">build #${data.report.run}</a></span></p>`,
1567
1592
  html `<p class="report-summary">${data.report.report.summary}</p>`,
1568
1593
  reportItemsHtml(data.report.report.items, data.report.shots, data.report.run),
1569
1594
  html `<pre class="recap plan-doc">${data.report.report.report}</pre>`,
1570
1595
  ...(data.report.report.followUps.length === 0
1571
1596
  ? []
1572
1597
  : [
1573
- html `<p><strong>Follow-ups the scout proposes</strong> <span class="meta">each files as a task in this repository; its scope still needs your approval</span></p>`,
1598
+ html `<p><strong>Follow-ups the research proposes</strong> <span class="meta">each files as a task in this project; its plan still needs your approval</span></p>`,
1574
1599
  ...data.report.report.followUps.map((one, index) => joinHtml([
1575
1600
  html `<div class="follow-up"><p><strong>${one.title}</strong></p><p class="meta">${one.goal}</p>`,
1576
1601
  data.csrf === "" || data.repo === null
@@ -1615,7 +1640,7 @@ export function taskBodyParts(data) {
1615
1640
  : joinHtml([
1616
1641
  approval.approved ? html `<details class="card planner-plan planner-plan-collapsed"><summary class="execution-plan-head">` : html `<section class="card planner-plan"><div class="execution-plan-head">`,
1617
1642
  html `<div><span class="eyebrow">execution plan</span><h2>${approval.approved ? `${planMilestoneCount ?? "Full"} step${planMilestoneCount === 1 ? "" : "s"} · open to review` : "How the agent will tackle this"}</h2>`,
1618
- approval.approved ? html `<p class="meta">The agent can adapt this route when evidence changes; your approved outcome stays fixed.</p>` : "",
1643
+ approval.approved ? html `<p class="meta">The agent can adapt this route as it learns more; your approved outcome stays fixed.</p>` : "",
1619
1644
  html `</div><span class="plan-lock">${planStanding}</span>${approval.approved ? html `</summary><div class="planner-plan-body">` : html `</div>`}`,
1620
1645
  executionPlanHtml(displayedPlanDocument ?? data.planDocument),
1621
1646
  approval.approved || sheetStatesContract ? "" : planContractHtml(data.planContract ?? null, "full"),
@@ -1683,7 +1708,7 @@ export function taskBodyParts(data) {
1683
1708
  deliverable: data.deliverable ?? "branch",
1684
1709
  rebuild: data.assignment?.need != null && "key" in data.assignment.need && data.assignment.need.key === "replan" ? data.assignment.need.build : null,
1685
1710
  editHref: data.csrf !== "" && data.planSha != null && data.planDocument !== null ? "#plan-edit" : "#scope",
1686
- notNowHref: "/work",
1711
+ notNowHref: "/tasks",
1687
1712
  sticky: approvalLeads,
1688
1713
  earlier: { active: data.assignment?.earlierActive ?? 0, running: data.assignment?.earlierRunning ?? 0 },
1689
1714
  edit: data.csrf === "" ? null
@@ -1693,21 +1718,21 @@ export function taskBodyParts(data) {
1693
1718
  const approveForm = scope === null || approval.approved || data.plan === "requested"
1694
1719
  ? ""
1695
1720
  : data.revision !== null && data.revision !== undefined && "problem" in data.revision
1696
- ? html `<div class="card approve-form" id="approve"><p><strong>This task is waiting on you: approval is blocked.</strong></p><p class="meta">${data.revision.problem} — a revision approves only against a brief that verifies</p></div>`
1721
+ ? html `<div class="card approve-form" id="approve"><p><strong>This task is waiting on you: approval is blocked.</strong></p><p class="meta">${data.revision.problem}. A revision can be approved only when its saved notes can be read.</p></div>`
1697
1722
  : scope.profileState === "unresolved"
1698
- ? html `<div class="card approve-form" id="approve"><p><strong>This task is waiting on you: its scope cannot be approved yet.</strong></p>${profileWords(scope)}<p class="ceremony-road"><a class="button-link" href="#scope">Edit the scope to fix it →</a></p></div>`
1723
+ ? html `<div class="card approve-form" id="approve"><p><strong>This task is waiting on you: its plan can't be approved yet.</strong></p>${profileWords(scope)}<p class="ceremony-road"><a class="button-link" href="#scope">Edit the plan to fix it →</a></p></div>`
1699
1724
  : !consentDoor.open
1700
1725
  ? consentClosedHtml(task.id, consentDoor, "task")
1701
1726
  : approvalSheetHtml(sheetInput);
1702
1727
  const scopeForm = joinHtml([
1703
- html `<details${scope === null || scopeDraft !== undefined ? html ` open` : ""}><summary>${scope === null ? "Write the scope" : "Edit the scope"}${approval.approved ? " (editing voids the approval)" : ""}</summary>`,
1728
+ html `<details${scope === null || scopeDraft !== undefined ? html ` open` : ""}><summary>${scope === null ? "Write the plan" : "Edit the plan"}${approval.approved ? " (it asks for approval again)" : ""}</summary>`,
1704
1729
  postForm(`${taskHref(task.id)}/scope`, joinHtml([
1705
1730
  "",
1706
1731
  scopeDraft === undefined || data.problem === null ? "" : html `<div class="problem" role="alert" id="scope-error">${data.problem}</div>`,
1707
1732
  html `<label>Goal<textarea name="goal" rows="3"${scopeDraft === undefined ? "" : html ` autofocus aria-describedby="scope-error"`}>${scopeDraft?.get("goal") ?? scope?.goal ?? ""}</textarea></label>`,
1708
1733
  html `<label>Not this<textarea name="not" rows="2">${scopeDraft?.get("not") ?? scope?.outOfScope ?? ""}</textarea></label>`,
1709
- html `<label>Touches <span class="meta">(one per line)</span><textarea name="touches" rows="2">${scopeDraft?.get("touches") ?? (scope?.touches ?? []).join("\n")}</textarea></label>`,
1710
- html `<label>Acceptance <span class="meta">(required — one criterion per line: <code>statement | evidence,kinds | how</code>; evidence kinds are check, screenshot, changed-path, manual-review; id is optional and auto-numbered)</span><textarea name="acceptance" rows="3" placeholder="The button opens the settings panel | screenshot">${scopeDraft?.get("acceptance") ?? acceptanceToLines(scope?.acceptance ?? []).join("\n")}</textarea></label>`,
1734
+ html `<label>Files <span class="meta">(one per line)</span><textarea name="touches" rows="2">${scopeDraft?.get("touches") ?? (scope?.touches ?? []).join("\n")}</textarea></label>`,
1735
+ html `<label>Done when <span class="meta">(required — one requirement per line: <code>what | how we'll know | note</code>; how we'll know is check, screenshot, changed-path or manual-review; ids number themselves)</span><textarea name="acceptance" rows="3" placeholder="The button opens the settings panel | screenshot">${scopeDraft?.get("acceptance") ?? acceptanceToLines(scope?.acceptance ?? []).join("\n")}</textarea></label>`,
1711
1736
  (() => {
1712
1737
  const defaults = data.spendDefaults ?? null;
1713
1738
  const budgetPrefill = scope?.budgetMicrousd != null
@@ -1726,13 +1751,13 @@ export function taskBodyParts(data) {
1726
1751
  ? scope.profile.sandboxMode === "danger-full-access" ? "bypassPermissions" : "auto"
1727
1752
  : null;
1728
1753
  const selected = scopeDraft?.get("permission-mode") === "bypassPermissions" ? "bypassPermissions" : scopeDraft?.get("permission-mode") === "auto" ? "auto" : data.permissionMode ?? profileMode ?? data.permissionDefault ?? "auto";
1729
- return html `<fieldset class="permission-field"><legend>Agent permissions</legend>${permissionModeChoices("permission-mode", selected)}<p class="meta permission-note">This task’s choice is sealed into its scope. Full access prevents permission prompts or sandbox limits from pausing supported unattended agents.</p></fieldset>`;
1754
+ return html `<fieldset class="permission-field"><legend>Agent permissions</legend>${permissionModeChoices("permission-mode", selected)}<p class="meta permission-note">This choice is part of the task's plan. Full access keeps permission prompts and sandbox limits from pausing agents that support it.</p></fieldset>`;
1730
1755
  })(),
1731
1756
  (() => {
1732
1757
  const selected = scopeDraft?.get("quality-mode") === "strict" ? "strict" : scopeDraft?.get("quality-mode") === "default" ? "default" : scope?.qualityMode ?? data.qualityMode ?? data.qualityDefault ?? "default";
1733
- return html `<fieldset class="permission-field"><legend>Quality</legend>${qualityModeChoices("quality-mode", selected)}<p class="meta permission-note">This choice is signed into the scope. Inspect the saved work and actual check results when it is ready. Publication and deployment need their own authorization.</p></fieldset>`;
1758
+ return html `<fieldset class="permission-field"><legend>Quality</legend>${qualityModeChoices("quality-mode", selected)}<p class="meta permission-note">This choice is part of the plan. Look at the saved work and the actual check results when it's ready. Publishing and deploying need their own approval.</p></fieldset>`;
1734
1759
  })(),
1735
- html `<button type="submit">Save scope</button>`,
1760
+ html `<button type="submit">Save plan</button>`,
1736
1761
  "",
1737
1762
  ], "\n"), { attrs: { class: "scope-editor" }, hidden: { sawDigest: scopeDraft?.get("sawDigest") ?? scope?.digest ?? "" } }),
1738
1763
  html `</details>`,
@@ -1835,46 +1860,46 @@ export function taskBodyParts(data) {
1835
1860
  // the scope's standing with its seal, publication, spend — the key
1836
1861
  // facts a reader scans before anything else, in one row grammar.
1837
1862
  const workerRow = liveRun !== undefined
1838
- ? prop("worker", html `${liveRun.runner} · <a href="/r/${liveRun.id}">build #${liveRun.id}</a> running`)
1863
+ ? prop("builder", html `${liveRun.runner} · <a href="/r/${liveRun.id}">build #${liveRun.id}</a> running`)
1839
1864
  : data.runs[0] !== undefined
1840
1865
  ? prop("last attempt", html `<a href="/r/${data.runs[0].id}">${runNoun(data.runs[0])} #${data.runs[0].id}</a> · ${data.runs[0].role === "planner" && data.runs[0].reason === "plan-drafted" ? "planned" : data.runs[0].reason === "interrupted" ? "interrupted" : data.runs[0].id === liveHistoryRunId ? "running" : data.runs[0].outcome ?? "never finished"} · ${data.runs[0].runner}`)
1841
1866
  : "";
1842
1867
  const queueRow = data.position !== null && data.position !== undefined && task.state === "queued"
1843
- ? prop("queue", html `${data.position.position} of ${data.position.total}${data.position.column === null ? " in the shared queue" : html ` in ${data.position.column}'s queue`} · <a href="/board?view=order">reorder</a>`)
1868
+ ? prop("queue", html `${data.position.position} of ${data.position.total}${data.position.column === null ? " in the shared queue" : html ` in ${data.position.column}'s queue`} · <a href="/tasks?view=order">reorder</a>`)
1844
1869
  : "";
1845
1870
  const scopeRow = scope === null
1846
- ? prop("scope", "none yet")
1871
+ ? prop("plan", "none yet")
1847
1872
  : approval.approved && data.autoRelease != null
1848
- ? prop("approved scope", html `<span class="seal">signs ${shortDigest(scope.digest)}</span> · ${autoReleaseWords(data.autoRelease)}`)
1873
+ ? prop("approved plan", html `${autoReleaseWords(data.autoRelease)}`)
1849
1874
  : approval.approved && data.release?.kind === "carried"
1850
- ? prop("approved scope", html `<span class="seal">signs ${shortDigest(scope.digest)}</span> · carried from ${data.release.approver ?? "a person"}'s release approval · <a href="${taskHref(data.release.anchorTask)}">Release ${data.release.version}</a>`)
1875
+ ? prop("approved plan", html `approved with ${data.release.approver ?? "a person"}'s release approval · <a href="${taskHref(data.release.anchorTask)}">Release ${data.release.version}</a>`)
1851
1876
  : approval.approved
1852
- ? prop("approved scope", html `<span class="seal">signs ${shortDigest(scope.digest)}</span> · ${qualityModeTitle(scope.qualityMode ?? "default")} · approved by ${approval.by}${approvalProofWords(data.approvalProof)} · ${whenTime(approval.at)}`)
1853
- : prop("scope", approval.reason === "changed" ? "rewritten since its approval — needs a new yes" : "not approved");
1854
- const strikesRow = data.strikes > 0 ? prop("strikes", `${data.strikes} failed attempt(s)`) : "";
1877
+ ? prop("approved plan", html `Approved by ${approval.by}${approvalProofWords(data.approvalProof)} · ${whenTime(approval.at)} · ${qualityModeTitle(scope.qualityMode ?? "default")}`)
1878
+ : prop("plan", approval.reason === "changed" ? "Edited after approval: it needs a new approval" : "not approved");
1879
+ const strikesRow = data.strikes > 0 ? prop("failed attempts", `${data.strikes} in a row`) : "";
1855
1880
  const propsCard = html `<div class="card props">${workerRow}${queueRow}${scopeRow}${publishesRow}${economics}${strikesRow}</div>`;
1856
1881
  // The same facts for the rebuilt page, row for row.
1857
1882
  const facts = [];
1858
1883
  const lastRun = data.runs[0];
1859
1884
  if (liveRun !== undefined)
1860
- facts.push({ label: "Worker", parts: [`${liveRun.runner} · `, { label: `build #${liveRun.id}`, href: `/r/${liveRun.id}` }, " running"] });
1885
+ facts.push({ label: "Builder", parts: [`${liveRun.runner} · `, { label: `build #${liveRun.id}`, href: `/r/${liveRun.id}` }, " running"] });
1861
1886
  else if (lastRun !== undefined)
1862
1887
  facts.push({ label: "Last attempt", parts: [{ label: `${runNoun(lastRun)} #${lastRun.id}`, href: `/r/${lastRun.id}` },
1863
1888
  ` · ${lastRun.role === "planner" && lastRun.reason === "plan-drafted" ? "planned" : lastRun.reason === "interrupted" ? "interrupted" : lastRun.id === liveHistoryRunId ? "running" : lastRun.outcome ?? "never finished"} · ${lastRun.runner}`] });
1864
1889
  if (data.position !== null && data.position !== undefined && task.state === "queued") {
1865
- facts.push({ label: "Queue", parts: [`${data.position.position} of ${data.position.total}${data.position.column === null ? " in the shared queue" : ` in ${data.position.column}'s queue`} · `, { label: "Reorder", href: "/board?view=order" }] });
1890
+ facts.push({ label: "Queue", parts: [`${data.position.position} of ${data.position.total}${data.position.column === null ? " in the shared queue" : ` in ${data.position.column}'s queue`} · `, { label: "Reorder", href: "/tasks?view=order" }] });
1866
1891
  }
1867
- facts.push(scope === null ? { label: "Scope", parts: ["none yet"] }
1868
- : approval.approved && data.autoRelease != null ? { label: "Approved scope", parts: [autoReleaseWords(data.autoRelease)] }
1869
- : approval.approved && data.release?.kind === "carried" ? { label: "Approved scope", parts: [`Carried from ${data.release.approver ?? "a person"}'s release approval · `, { label: `Release ${data.release.version}`, href: taskHref(data.release.anchorTask) }] }
1870
- : approval.approved ? { label: "Approved scope", parts: [{ seal: `signs ${scope.digest.length <= 12 ? scope.digest : `${scope.digest.slice(0, 12)}…`}` }, ` · ${qualityModeTitle(scope.qualityMode ?? "default")} · approved by ${approval.by}${approvalProofWords(data.approvalProof)}`, ...(approval.at === null ? [] : [" · ", { at: approval.at }])] }
1871
- : { label: "Scope", parts: [approval.reason === "changed" ? "rewritten since its approval — needs a new yes" : "not approved"] });
1892
+ facts.push(scope === null ? { label: "Plan", parts: ["none yet"] }
1893
+ : approval.approved && data.autoRelease != null ? { label: "Approved plan", parts: [autoReleaseWords(data.autoRelease)] }
1894
+ : approval.approved && data.release?.kind === "carried" ? { label: "Approved plan", parts: [`Approved with ${data.release.approver ?? "a person"}'s release approval · `, { label: `Release ${data.release.version}`, href: taskHref(data.release.anchorTask) }] }
1895
+ : approval.approved ? { label: "Approved plan", parts: [`Approved by ${approval.by}${approvalProofWords(data.approvalProof)}`, ...(approval.at === null ? [] : [" · ", { at: approval.at }]), ` · ${qualityModeTitle(scope.qualityMode ?? "default")}`] }
1896
+ : { label: "Plan", parts: [approval.reason === "changed" ? "Edited after approval: it needs a new approval" : "not approved"] });
1872
1897
  if (data.release?.kind === "anchor")
1873
1898
  facts.push({ label: "Release", parts: [`Approved for release ${data.release.version}`, ...(data.release.landed.length === 0 ? [] : [` · later commits: ${data.release.landed.map(one => one.sha.slice(0, 7)).join(", ")}`]), ...(data.release.carrying ? [] : [` · later commits ask again`])] });
1874
1899
  facts.push({ label: "Publishes as", parts: [publishesAs] });
1875
1900
  // v102: who asked for it, and what the project's approval rules need.
1876
1901
  if (data.filer != null)
1877
- facts.push({ label: "Filed by", parts: [data.filer.name === null ? "automation" : data.filer.kind === "coordinator" ? `${data.filer.name}, through a coordinator` : data.filer.name] });
1902
+ facts.push({ label: "Filed by", parts: [data.filer.name === null ? "automation" : data.filer.kind === "coordinator" ? `${data.filer.name}, through your lead` : data.filer.name] });
1878
1903
  if (data.approvalRules != null) {
1879
1904
  const votes = data.approvalRules.votes;
1880
1905
  facts.push({ label: "Approval rules", parts: [data.approvalRules.needsTwo && !approval.approved
@@ -1882,7 +1907,7 @@ export function taskBodyParts(data) {
1882
1907
  : data.approvalRules.words] });
1883
1908
  }
1884
1909
  // v103: everything an auditor asks about this task, printable or as JSON.
1885
- facts.push({ label: "Audit", parts: [{ label: "Evidence pack", href: `${taskHref(task.id)}/evidence` }] });
1910
+ facts.push({ label: "Audit", parts: [{ label: "Audit record", href: `${taskHref(task.id)}/evidence` }] });
1886
1911
  if (data.budgetHold != null)
1887
1912
  facts.push({ label: "Budget", parts: [`${data.budgetHold} · `, { label: "Spend", href: "/spend" }] });
1888
1913
  if (data.policyHold != null)
@@ -1967,7 +1992,7 @@ export function taskBodyParts(data) {
1967
1992
  : postForm(`${taskHref(task.id)}/block`, html `<select name="on" aria-label="task to wait for">${candidates.map(one => html `<option value="${one.id}">${one.id} — ${one.title}</option>`)}</select><button type="submit">Wait for this task</button><span class="meta"> — this task starts only after it finishes</span>`, { attrs: { class: "row" } });
1968
1993
  const waitsForCard = waitRows === "" && waitAdd === ""
1969
1994
  ? ""
1970
- : html `<h2>Waits for</h2>${waitRows === "" ? html `<p class="meta">Nothing — it starts when a worker is free</p>` : waitRows}${waitAdd}`;
1995
+ : html `<h2>Waits for</h2>${waitRows === "" ? html `<p class="meta">Nothing. It starts when a builder is free.</p>` : waitRows}${waitAdd}`;
1971
1996
  // The acts bar (task page pass): every verb in one row under the title,
1972
1997
  // the one that resolves this task's state first and primary — retry on a
1973
1998
  // stalled task, build-next in the queue, plan-first with no scope. The
@@ -1979,13 +2004,13 @@ export function taskBodyParts(data) {
1979
2004
  // A failed task's status card carries Retry itself (the same requeue); here it would be a second ink act.
1980
2005
  const cardRetries = task.state === "failed" && data.assignment != null && data.csrf !== "";
1981
2006
  const primaryAct = stopControlsActive || cardRetries ? null : stalled && !data.claimed
1982
- ? { html: act("requeue", "Retry, keeping the branch and workspace"), why: "Resolves the incidents, clears the failed attempts and queues the task again. The branch and workspace are kept.", whyClass: "retry" }
2007
+ ? { html: act("requeue", "Retry, keeping the branch and workspace"), why: "Clears the repeated failures and queues the task again. The branch and workspace are kept.", whyClass: "retry" }
1983
2008
  : canPlan
1984
- ? { html: act("plan", "plan first"), why: "plan first sends an agent to read the repository, ask you questions, and propose a scope — nothing builds until you approve it", whyClass: "plan" }
2009
+ ? { html: act("plan", "plan first"), why: "plan first sends an agent to read the project, ask you questions and propose a plan. Nothing builds until you approve it.", whyClass: "plan" }
1985
2010
  : data.plan === "requested"
1986
2011
  ? null
1987
2012
  : task.state === "queued" && !data.claimed && (data.position?.position ?? 1) > 1
1988
- ? { html: act("next", "build this next"), why: "moves it to the front of its queue — the next free worker looks here first; approval is still required", whyClass: "next" }
2013
+ ? { html: act("next", "build this next"), why: "moves it to the front of its queue: the next free builder takes it first. It still needs approval.", whyClass: "next" }
1989
2014
  : null;
1990
2015
  const holdAct = postForm(`${taskHref(task.id)}/hold`, html `<input type="text" name="reason" class="inline" placeholder="reason (optional)" aria-label="hold reason"><button type="submit">Hold the next attempt</button>`, { attrs: { class: "inline act-hold" } });
1991
2016
  const canHold = task.state === "queued" || task.state === "running" || task.state === "failed";
@@ -2000,7 +2025,9 @@ export function taskBodyParts(data) {
2000
2025
  // to "plan first" adds a second, unnecessary decision at the exact
2001
2026
  // moment the page should have one obvious action.
2002
2027
  stopControlsActive || canPlan || !canHold ? "" : holdAct,
2003
- data.holds.some(hold => hold.ownerKind === "operator") ? act("unhold", "Remove hold") : "",
2028
+ data.holds.some(hold => hold.ownerKind === "operator") ? act("unhold", "Remove hold")
2029
+ // The storage check couldn't finish (dispose.ts holdForStorage): one tap tries again now.
2030
+ : data.holds.some(hold => hold.ownerKind === "backoff" && hold.ownerId === `storage:${hold.taskRef}`) ? act("unhold", "Try again") : "",
2004
2031
  html `</div>`,
2005
2032
  primaryAct === null ? "" : html `<p class="meta acts-why acts-why-${primaryAct.whyClass}">${primaryAct.why}</p>`,
2006
2033
  data.claimed && !stopControlsActive
@@ -2039,7 +2066,7 @@ export function taskBodyParts(data) {
2039
2066
  ? ` · ${originWords(data.origin)}`
2040
2067
  : data.filedVia === null || data.filedVia === undefined
2041
2068
  ? ""
2042
- : ` · filed via ${data.filedVia}`}${data.deliverable === "report" ? html ` · <span class="badge">Scout</span>` : ""}</p>`;
2069
+ : ` · filed via ${data.filedVia}`}${data.deliverable === "report" ? html ` · <span class="badge">Research</span>` : ""}</p>`;
2043
2070
  // A pull request that can be merged (or opened) owns the one primary action; the result stays in its section.
2044
2071
  const pullRequestActs = data.csrf !== "" && (data.pullRequest?.view?.canMerge === true || (data.pullRequest?.view == null && data.pullRequest?.offer != null));
2045
2072
  const pullRequestFact = data.pullRequest?.fact != null && data.pullRequest.view?.runId === data.assignment?.receipt?.runId ? data.pullRequest.fact : undefined;
@@ -2083,7 +2110,7 @@ export function taskBodyParts(data) {
2083
2110
  return joinHtml([
2084
2111
  html `<div class="card"><p><strong>External work</strong> <span class="meta">${link} · ${state}</span></p>`,
2085
2112
  reopenable
2086
- ? postForm(`${taskHref(task.id)}/reopen`, html `${reauth(html `<input type="password" name="token" placeholder="your password" aria-label="your password" autocomplete="current-password">`)}<button type="submit">Reopen — the approved scope stands</button>`, { attrs: { class: "row" } })
2113
+ ? postForm(`${taskHref(task.id)}/reopen`, html `${reauth(html `<input type="password" name="token" placeholder="your password" aria-label="your password" autocomplete="current-password">`)}<button type="submit">Reopen — the approved plan stands</button>`, { attrs: { class: "row" } })
2087
2114
  : "",
2088
2115
  html `</div>`,
2089
2116
  ]);
@@ -2097,8 +2124,8 @@ export function taskBodyParts(data) {
2097
2124
  // The quarantine speaks here too (round-2 finding 5): the planner
2098
2125
  // is as fenced as the builder on a coordinator filing, so "plan
2099
2126
  // first" would recommend a road that refuses.
2100
- ? html `<div class="card"><p><strong>This task is waiting on you: an agent filed it, and it has no scope.</strong></p><p class="meta">Filed by <span class="mono">${data.coordinator.label}</span> — nothing plans, claims, or runs until you write a scope below and sign it. Your signature runs their request.</p></div>`
2101
- : html `<div class="card task-scope-needed"><p><strong>No approved scope yet</strong></p><p class="meta"><strong>Plan first</strong> drafts it from the repository, or <a href="#scope">write it yourself</a>.</p></div>`
2127
+ ? html `<div class="card"><p><strong>This task is waiting on you: an agent filed it, and it has no plan.</strong></p><p class="meta">Filed by <span class="mono">${data.coordinator.label}</span> — nothing plans or runs until you write a plan below and approve it. Your approval runs their request.</p></div>`
2128
+ : html `<div class="card task-scope-needed"><p><strong>No approved plan yet</strong></p><p class="meta"><strong>Plan first</strong> drafts it from the repository, or <a href="#scope">write it yourself</a>.</p></div>`
2102
2129
  : "";
2103
2130
  const approvalHtml = dependencyChoiceNeeded || approveForm === "" ? "" : approvalLeads
2104
2131
  ? html `<section class="task-plan-review" aria-label="Approve the plan">${approveForm}</section>`
@@ -2135,9 +2162,9 @@ export function taskBodyParts(data) {
2135
2162
  ? ""
2136
2163
  : section("proposals",
2137
2164
  // Confirm and dismiss come back to this task.
2138
- html `<h2>Proposed by coordinators</h2>${coordinatorProposalsSection(data.coordinatorProposals.rows, data.coordinatorProposals.decisions, data.coordinatorProposals.now, false, taskHref(data.task.id))}`, true, data.coordinatorProposals.rows.length),
2165
+ html `<h2>Proposed by your lead</h2>${coordinatorProposalsSection(data.coordinatorProposals.rows, data.coordinatorProposals.decisions, data.coordinatorProposals.now, false, taskHref(data.task.id))}`, true, data.coordinatorProposals.rows.length),
2139
2166
  section("decisions", decisions, true, data.decisions.length),
2140
- section("incidents", incidents, true, data.incidents.length),
2167
+ section("repeated failures", incidents, true, data.incidents.length, "incidents"),
2141
2168
  data.publication === null || data.publication === undefined
2142
2169
  ? ""
2143
2170
  : html `<p class="row"><span class="meta">published</span> ${safePrUrl(data.publication.prUrl) === null ? html `<span class="mono">PR #${data.publication.prNumber ?? "?"}</span>` : html `<a href="${safePrUrl(data.publication.prUrl)}" class="mono">PR #${data.publication.prNumber ?? "?"}</a>`} <span class="meta">${data.publication.state}${data.publication.remoteState !== null ? ` · ${data.publication.remoteState.toLowerCase()} on GitHub` : ""}${data.publication.lastCheckState !== null
@@ -2147,10 +2174,14 @@ export function taskBodyParts(data) {
2147
2174
  data.assignment == null ? section("attempts", runs, true, data.runs.length) : section("Build activity", data.runs.length === 0 ? "" : runRows, false, data.runs.length, "attempts"),
2148
2175
  section("usage", spendCard, false),
2149
2176
  section("steering", steeringCard, (data.steering ?? []).length > 0, (data.steering ?? []).length),
2150
- section("scope",
2177
+ data.followThrough == null ? "" : section("findings", data.followThrough.findings, data.followThrough.openFindings > 0, data.followThrough.findingCount),
2178
+ data.followThrough == null ? "" : section("failed checks", data.followThrough.repairs, true),
2179
+ data.followThrough == null ? "" : section("before and after", data.followThrough.evidence, false),
2180
+ data.followThrough == null ? "" : section("handover note", data.followThrough.handover, false),
2181
+ section("plan",
2151
2182
  // The recipe road rides with the scope it reuses (UI polish
2152
2183
  // 2026-09-13), off the title-to-action path.
2153
- joinHtml([html `<h2>Scope</h2>`, scopeCard, releaseCardHtml(task.id, data.release ?? null, data.canStopRelease === true), data.repo !== null && data.scope !== null ? html `<p class="meta"><a href="/recipes/from-task?task=${encodeURIComponent(task.id)}">Reuse this scope as a recipe →</a></p>` : "", agentsCardHtml(task.id, data.route, data.canEditRoute === true), revisionInApproval ? "" : revisionCard, data.completion != null ? "" : repairChainHtml(data.repairChain ?? null), scopeForm], "\n"), scopeDraft !== undefined || (data.plan !== "requested" && approveForm === "" && !(scope === null && canPlan))),
2184
+ joinHtml([html `<h2>Plan</h2>`, scopeCard, releaseCardHtml(task.id, data.release ?? null, data.canStopRelease === true), html `<p class="meta">${data.repo !== null && data.scope !== null ? html `<a href="/recipes/from-task?task=${encodeURIComponent(task.id)}">Reuse this plan as a recipe →</a> · ` : ""}<a href="${taskHref(data.rootId ?? task.id)}/evidence">Audit record →</a></p>`, agentsCardHtml(task.id, data.route, data.canEditRoute === true), revisionInApproval ? "" : revisionCard, data.completion != null ? "" : repairChainHtml(data.repairChain ?? null), scopeForm], "\n"), scopeDraft !== undefined || (data.plan !== "requested" && approveForm === "" && !(scope === null && canPlan)), undefined, "scope"),
2154
2185
  dependencyChoiceNeeded ? "" : section("waits for", waitsForCard, (data.waitsFor ?? []).length > 0, (data.waitsFor ?? []).length),
2155
2186
  section("holds", holds, true, data.holds.length),
2156
2187
  cancelAct,
@@ -2263,7 +2294,7 @@ export function taskBodyParts(data) {
2263
2294
  : data.route?.legacy != null ? `${data.route.legacy.provider} · ${data.route.legacy.model}` : "The project's default"] });
2264
2295
  if (scope !== null)
2265
2296
  work.push({ label: "Checks level", parts: [qualityModeTitle(scope.qualityMode ?? "default")] });
2266
- for (const label of ["Worker", "Last attempt", "Queue", "Budget", "Policy", "Strikes"]) {
2297
+ for (const label of ["Builder", "Last attempt", "Queue", "Budget", "Policy", "Strikes"]) {
2267
2298
  const fact = facts.find(one => one.label === label);
2268
2299
  if (fact !== undefined)
2269
2300
  work.push(fact);
@@ -2286,9 +2317,9 @@ export function taskBodyParts(data) {
2286
2317
  links.push({ label: "Pull request", parts: [prHref === null ? `#${pr.prNumber ?? "?"}` : { label: `#${pr.prNumber ?? "?"}`, href: prHref }, ` · ${pr.label}`] });
2287
2318
  }
2288
2319
  const review = [];
2289
- const approvedScope = facts.find(one => one.label === "Approved scope" || one.label === "Scope");
2320
+ const approvedScope = facts.find(one => one.label === "Approved plan" || one.label === "Plan");
2290
2321
  if (approvedScope !== undefined)
2291
- review.push({ ...approvedScope, label: approvedScope.label === "Approved scope" ? "Approvals" : "Scope" });
2322
+ review.push({ ...approvedScope, label: approvedScope.label === "Approved plan" ? "Approvals" : "Plan" });
2292
2323
  review.push({ label: "Who reviews", parts: [facts.find(one => one.label === "Approval rules")?.parts[0] ?? "Any approver in this project"] });
2293
2324
  review.push({ label: "Publishes as", parts: [publishesAs] });
2294
2325
  if (data.resultRecord != null)
@@ -2367,10 +2398,8 @@ export function taskBodyParts(data) {
2367
2398
  ...(sectionIds.has("waits-for") ? [{ group: "work", label: "Dependencies", kind: "goto", tab: "plan", target: "waits-for" }] : []),
2368
2399
  ...(sectionIds.has("holds") ? [{ group: "work", label: "Holds", kind: "goto", tab: "plan", target: "holds" }] : []),
2369
2400
  ...(data.csrf !== "" ? [{ group: "work", label: "Task options", kind: "goto", tab: "plan", target: "task-diagnostics" }] : []),
2370
- ...(data.canPutAway === true && data.csrf !== "" && data.archived == null && !building ? [{ group: "work", label: "Put away", kind: "post", action: `${taskHref(task.id)}/archive`, fields: { return: taskHref(task.id) } }] : []),
2401
+ ...(data.canPutAway === true && data.csrf !== "" && data.archived == null && !building ? [{ group: "work", label: "Archive", kind: "post", action: `${taskHref(task.id)}/archive`, fields: { return: taskHref(task.id) } }] : []),
2371
2402
  { group: "share", label: "Copy link", kind: "copy" },
2372
- ...(facts.some(one => one.label === "Audit") ? [{ group: "share", label: "Evidence pack", kind: "link", href: `${taskHref(rootOf)}/evidence` }] : []),
2373
- ...(data.repo !== null && data.scope !== null ? [{ group: "share", label: "Reuse as recipe", kind: "link", href: `/recipes/from-task?task=${encodeURIComponent(task.id)}` }] : []),
2374
2403
  ...(cancelForm !== "" ? [{ group: "danger", label: "Cancel task", kind: "goto", tab: "plan", target: "cancel-task" }] : []),
2375
2404
  ];
2376
2405
  const view = {
@@ -2408,12 +2437,15 @@ export function taskBodyParts(data) {
2408
2437
  approve: data.archived != null || sheetInput === null || dependencyChoiceNeeded || data.csrf === "" ? null : approveKitOf(sheetInput, data.approvalFresh ?? null),
2409
2438
  approved: scope !== null && approval.approved ? approvedKitOf(scope, approval, data.approvalProof ?? null) : null,
2410
2439
  more,
2440
+ audit: facts.some(one => one.label === "Audit") ? { label: "Full record", href: `${taskHref(rootOf)}/evidence` } : null,
2411
2441
  recheckStopped: data.csrf !== "" && data.assignment?.primaryAction?.code === "recheck-stopped" && data.assignment.primaryAction.target.runId !== null
2412
2442
  ? { action: `${taskHref(data.assignment.rootId)}/recheck-stopped`, run: data.assignment.primaryAction.target.runId } : null,
2413
2443
  rebuild,
2414
2444
  // The plan, progress and plan changes are thread entries now; the rest still needs a person here.
2415
2445
  lead: [
2416
2446
  { key: "history", html: data.history === undefined ? "" : htmlString(data.history) }, { key: "control", html: htmlString(stop === null ? controlHtml : taskControlDetailsHtml(data.control, task.id, data.csrf, "task", false, true)) }, { key: "problem", html: fragmentString(problemHtml) },
2447
+ { key: "route-change", html: data.routeChange == null ? "" : htmlString(html `<div class="card"><p>${data.routeChange}</p></div>`) },
2448
+ { key: "fast-grown", html: data.fastGrown == null || data.csrf === "" ? "" : htmlString(html `<div class="card"><p>${data.fastGrown}</p>${postForm(`${taskHref(task.id)}/keep-grown`, btn("Approve it", { variant: "primary", type: "submit" }))}</div>`) },
2417
2449
  { key: "pull-request", html: fragmentString(pullRequestCard) },
2418
2450
  { key: "needs-scope", html: fragmentString(needsScopeCard) },
2419
2451
  { key: "mirror", html: fragmentString(mirrorCard) }, { key: "attempt", html: fragmentString(attemptPanel) },
@@ -2541,7 +2573,7 @@ export function releaseCardHtml(taskId, release, canStop) {
2541
2573
  if (release === null)
2542
2574
  return html ``;
2543
2575
  if (release.kind === "carried")
2544
- return html `<div class="card" id="release"><p><strong>Carried from ${release.approver ?? "a person"}'s release approval</strong></p><p class="meta"><a href="${taskHref(release.anchorTask)}">Release ${release.version}</a> · pull request #${release.pr}</p></div>`;
2576
+ return html `<div class="card" id="release"><p><strong>Approved with ${release.approver ?? "a person"}'s release approval</strong></p><p class="meta"><a href="${taskHref(release.anchorTask)}">Release ${release.version}</a> · pull request #${release.pr}</p></div>`;
2545
2577
  const until = new Intl.DateTimeFormat("en-US", { weekday: "short", hour: "numeric", minute: "2-digit" }).format(new Date(release.expiresAt));
2546
2578
  const landed = release.landed.length === 0 ? html `<p class="meta">No later commits yet.</p>`
2547
2579
  : html `<ul>${joinHtml(release.landed.map(one => html `<li><a href="${taskHref(one.task)}">${one.sha.slice(0, 7)}</a> · ${one.files.map(file => `${file.path} (+${file.added} −${file.deleted})`).join(", ")}</li>`), "")}</ul>`;
@@ -2553,7 +2585,7 @@ export function releaseCardHtml(taskId, release, canStop) {
2553
2585
  release.obligations.length === 0 ? "" : html `<details><summary>Still to pass (${release.obligations.length})</summary><ul>${joinHtml(release.obligations.map(one => html `<li>${one}</li>`), "")}</ul></details>`,
2554
2586
  release.nightly === null ? "" : html `<p class="meta">Nightly check of main: ${release.nightly.outcome ?? "running"} at ${release.nightly.sha.slice(0, 7)}${release.openMainFailures > 0 ? ` · ${release.openMainFailures} open failure${release.openMainFailures === 1 ? "" : "s"}` : ""}</p>`,
2555
2587
  ...release.githubNightly.map(line => html `<p class="meta">${line}</p>`),
2556
- release.carrying && canStop ? postForm(`${taskHref(taskId)}/stop-carrying`, html `<button type="submit" class="secondary">Stop carrying later commits</button>`, { attrs: { class: "inline" } }) : "",
2588
+ release.carrying && canStop ? postForm(`${taskHref(taskId)}/stop-carrying`, html `<button type="submit" class="secondary">Ask again for later commits</button>`, { attrs: { class: "inline" } }) : "",
2557
2589
  html `</div>`,
2558
2590
  ], "\n");
2559
2591
  }
@@ -2571,7 +2603,7 @@ export function approveKitOf(input, freshSince) {
2571
2603
  fields: { nonce: input.nonce, digest: input.digest, ...(rebuild === null ? {} : { rebuild: String(rebuild) }) },
2572
2604
  label: parts.label,
2573
2605
  heading: rebuild !== null ? "The plan changed" : input.deliverable === "report" ? "Investigation ready" : "Plan ready",
2574
- text: `Approving signs plan version ${input.scope.digest.slice(0, 12)} and ${after}.${allowing}`,
2606
+ text: `Approving ${after}.${allowing}`,
2575
2607
  kind: "standard",
2576
2608
  facts: { passwordless: passwordlessRendering() || input.localOwner?.eligible === true, remembered: until !== null },
2577
2609
  rememberedUntil: until,
@@ -2586,7 +2618,11 @@ export function approvedKitOf(scope, approval, proof) {
2586
2618
  return null;
2587
2619
  const basis = scope.approvalBasis ?? null;
2588
2620
  const rule = basis === "mode" ? "your operating mode for this project" : basis === "grant" ? "its flow, which runs on its own" : basis === "schedule" ? "the schedule you approved"
2589
- : basis === "auto-green" || basis === "auto-check" ? "this project’s release approval (CI and the release check passed)" : basis === "carried" ? "the release approval it was carried under" : null;
2621
+ : basis === "auto-green" || basis === "auto-check" ? "this project’s release approval (CI and the release check passed)" : basis === "carried" ? "the release approval it came with" : null;
2590
2622
  const how = approvalProofWords(proof).replace(/^ \(|\)$/g, "");
2591
2623
  return { by: approval.by, at: approval.at, automatic: rule !== null, rule, how: how === "" ? (rule === null ? "password typed" : "") : how, version: scope.digest.slice(0, 12) };
2592
2624
  }
2625
+ /** The plan version, in Details: a short handle a person can compare, never on the approval line itself. */
2626
+ export function planVersionDetailsHtml(digest) {
2627
+ return html `<details class="scope-version"><summary class="meta">Details</summary><p class="meta"><span class="mono">${planVersionWords(digest)}</span></p></details>`;
2628
+ }