toolroll 0.9.92 → 0.9.94

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 (341) hide show
  1. package/CHANGELOG.md +87 -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 +4 -0
  5. package/dist/agent-fence.js +4 -1
  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 -3
  12. package/dist/assignment.js +16 -3
  13. package/dist/boot-identity.d.ts +2 -1
  14. package/dist/boot-identity.js +6 -4
  15. package/dist/bounded-spawn.d.ts +57 -0
  16. package/dist/bounded-spawn.js +220 -0
  17. package/dist/browser/terminal.js +42 -42
  18. package/dist/browser/workspace.css +2 -2
  19. package/dist/browser/workspace.js +26 -26
  20. package/dist/browser-crew.js +2 -2
  21. package/dist/browser-shell.d.ts +1 -1
  22. package/dist/browser-shell.js +2 -2
  23. package/dist/browser-workspace.d.ts +48 -7
  24. package/dist/browser-workspace.js +3 -3
  25. package/dist/builder-restart.d.ts +21 -0
  26. package/dist/builder-restart.js +50 -0
  27. package/dist/builder.d.ts +4 -0
  28. package/dist/builder.js +73 -25
  29. package/dist/chat-acceptance.js +1 -1
  30. package/dist/chat-actions.d.ts +12 -2
  31. package/dist/chat-actions.js +25 -2
  32. package/dist/chat-channel.js +11 -0
  33. package/dist/chat-decide.d.ts +7 -2
  34. package/dist/chat-decide.js +37 -9
  35. package/dist/chat-quiet.js +3 -3
  36. package/dist/chat-voice.d.ts +1 -1
  37. package/dist/chat-voice.js +2 -2
  38. package/dist/check-facts.d.ts +126 -0
  39. package/dist/check-facts.js +318 -0
  40. package/dist/check-failure.d.ts +22 -3
  41. package/dist/check-failure.js +57 -13
  42. package/dist/check-levels-ui.d.ts +4 -0
  43. package/dist/check-levels-ui.js +10 -1
  44. package/dist/check-load.d.ts +85 -0
  45. package/dist/check-load.js +147 -0
  46. package/dist/check-only-standing.d.ts +36 -0
  47. package/dist/check-only-standing.js +39 -0
  48. package/dist/check-only.d.ts +15 -0
  49. package/dist/check-only.js +99 -0
  50. package/dist/check-report.d.ts +7 -0
  51. package/dist/check-report.js +4 -3
  52. package/dist/check-stamp.d.ts +46 -0
  53. package/dist/check-stamp.js +93 -0
  54. package/dist/checkout-cleanup.d.ts +36 -5
  55. package/dist/checkout-cleanup.js +166 -14
  56. package/dist/cli-help.d.ts +4 -1
  57. package/dist/cli-help.js +47 -30
  58. package/dist/cli.js +16 -1
  59. package/dist/code-extract.d.ts +44 -0
  60. package/dist/code-extract.js +88 -0
  61. package/dist/code-map-acts.d.ts +24 -0
  62. package/dist/code-map-acts.js +53 -0
  63. package/dist/code-map-admission.d.ts +52 -0
  64. package/dist/code-map-admission.js +295 -0
  65. package/dist/code-map-build.d.ts +63 -0
  66. package/dist/code-map-build.js +415 -0
  67. package/dist/code-map-cli.d.ts +153 -0
  68. package/dist/code-map-cli.js +103 -0
  69. package/dist/code-map-main.d.ts +1 -0
  70. package/dist/code-map-main.js +48 -0
  71. package/dist/code-map-read.d.ts +106 -0
  72. package/dist/code-map-read.js +171 -0
  73. package/dist/code-map-report.d.ts +37 -0
  74. package/dist/code-map-report.js +95 -0
  75. package/dist/code-map-shape.d.ts +172 -0
  76. package/dist/code-map-shape.js +327 -0
  77. package/dist/code-map-store.d.ts +136 -0
  78. package/dist/code-map-store.js +233 -0
  79. package/dist/coding-provider.d.ts +2 -0
  80. package/dist/coding-provider.js +3 -2
  81. package/dist/coding-update.js +3 -2
  82. package/dist/coding-workspace.js +4 -4
  83. package/dist/console-chrome.js +2 -2
  84. package/dist/containment.d.ts +5 -0
  85. package/dist/containment.js +14 -1
  86. package/dist/contracts/chat-actions.d.ts +9 -0
  87. package/dist/contracts/chat-actions.js +2 -0
  88. package/dist/contracts/cli.d.ts +52 -0
  89. package/dist/contracts/cli.js +4 -0
  90. package/dist/contracts/console-api.d.ts +12 -5
  91. package/dist/contracts/console-api.js +12 -5
  92. package/dist/contracts/gateway-tools.d.ts +17 -0
  93. package/dist/contracts/gateway-tools.js +3 -1
  94. package/dist/contracts/lead-tools.d.ts +61 -0
  95. package/dist/contracts/lead-tools.js +10 -1
  96. package/dist/contracts/project-knowledge.d.ts +38 -5
  97. package/dist/contracts/project-knowledge.js +11 -5
  98. package/dist/contracts/project-memory.d.ts +3 -1
  99. package/dist/contracts/project-memory.js +3 -2
  100. package/dist/contracts/release-check.d.ts +25 -1
  101. package/dist/contracts/release-check.js +19 -0
  102. package/dist/contracts/storage.d.ts +14 -0
  103. package/dist/contracts/storage.js +3 -1
  104. package/dist/contracts/store-json.d.ts +2 -0
  105. package/dist/contracts/youiyoux-request.d.ts +1 -1
  106. package/dist/contracts/youiyoux-request.js +1 -1
  107. package/dist/desktop-access.js +6 -2
  108. package/dist/desktop-update.d.ts +2 -2
  109. package/dist/desktop-update.js +13 -9
  110. package/dist/dispatch.js +18 -15
  111. package/dist/dispose.js +8 -2
  112. package/dist/easy-start.js +2 -3
  113. package/dist/eval-hooks.d.ts +55 -0
  114. package/dist/eval-hooks.js +227 -4
  115. package/dist/evals.js +2 -1
  116. package/dist/fast-start.d.ts +136 -0
  117. package/dist/fast-start.js +431 -0
  118. package/dist/fast-terms.d.ts +9 -0
  119. package/dist/fast-terms.js +23 -0
  120. package/dist/fenced-check-coverage.d.ts +62 -0
  121. package/dist/fenced-check-coverage.js +54 -0
  122. package/dist/first-run.js +1 -1
  123. package/dist/flaky-ci.d.ts +31 -0
  124. package/dist/flaky-ci.js +149 -0
  125. package/dist/flaky-cli.d.ts +60 -0
  126. package/dist/flaky-cli.js +60 -0
  127. package/dist/flaky.d.ts +83 -0
  128. package/dist/flaky.js +264 -0
  129. package/dist/flow-card-task.js +3 -0
  130. package/dist/flow-triggers.d.ts +1 -1
  131. package/dist/flow-triggers.js +1 -1
  132. package/dist/github-import.d.ts +62 -0
  133. package/dist/github-import.js +146 -0
  134. package/dist/guides.js +15 -10
  135. package/dist/invoke.d.ts +5 -1
  136. package/dist/invoke.js +49 -14
  137. package/dist/job-object-helper.ps1 +10 -3
  138. package/dist/keymap.d.ts +41 -0
  139. package/dist/keymap.js +51 -0
  140. package/dist/knowledge-cli.js +1 -1
  141. package/dist/knowledge-ui.d.ts +21 -4
  142. package/dist/knowledge-ui.js +75 -7
  143. package/dist/laptop-background.d.ts +16 -0
  144. package/dist/laptop-background.js +108 -2
  145. package/dist/lead-context.js +1 -1
  146. package/dist/lead-contract.d.ts +1 -1
  147. package/dist/lead-contract.js +2 -2
  148. package/dist/lead-progress.js +3 -0
  149. package/dist/lead-status.d.ts +2 -0
  150. package/dist/lead-status.js +6 -2
  151. package/dist/lead-tools.js +51 -2
  152. package/dist/lead.js +1 -1
  153. package/dist/lesson-notes.d.ts +81 -0
  154. package/dist/lesson-notes.js +336 -0
  155. package/dist/limits-ui.js +1 -1
  156. package/dist/local-owner.d.ts +1 -1
  157. package/dist/local-owner.js +11 -0
  158. package/dist/machine-check.d.ts +16 -0
  159. package/dist/machine-check.js +26 -0
  160. package/dist/machine-identity.d.ts +75 -0
  161. package/dist/machine-identity.js +170 -0
  162. package/dist/mcp-core.js +11 -1
  163. package/dist/memory-cli.d.ts +300 -1
  164. package/dist/memory-cli.js +62 -9
  165. package/dist/memory-pass.d.ts +16 -0
  166. package/dist/memory-pass.js +67 -3
  167. package/dist/memory-tidy.d.ts +181 -0
  168. package/dist/memory-tidy.js +523 -0
  169. package/dist/memory-use.d.ts +35 -0
  170. package/dist/memory-use.js +109 -0
  171. package/dist/merge-checklist.d.ts +55 -0
  172. package/dist/merge-checklist.js +182 -0
  173. package/dist/needs-you.d.ts +32 -13
  174. package/dist/needs-you.js +108 -49
  175. package/dist/nightly-github.d.ts +2 -0
  176. package/dist/nightly-github.js +4 -4
  177. package/dist/operate-remote-arguments.js +6 -1
  178. package/dist/operate-remote.js +3 -1
  179. package/dist/operate.d.ts +6 -1
  180. package/dist/operate.js +540 -70
  181. package/dist/phase-routing.js +2 -2
  182. package/dist/plain-schema.d.ts +23 -0
  183. package/dist/plain-schema.js +94 -0
  184. package/dist/plain-words.d.ts +20 -0
  185. package/dist/plain-words.js +66 -0
  186. package/dist/planner.js +2 -1
  187. package/dist/process-liveness.d.ts +2 -0
  188. package/dist/process-liveness.js +61 -17
  189. package/dist/project-delete-ui.d.ts +7 -2
  190. package/dist/project-delete-ui.js +12 -1
  191. package/dist/project-delete.js +13 -2
  192. package/dist/project-knowledge.d.ts +18 -11
  193. package/dist/project-knowledge.js +39 -9
  194. package/dist/project-learning.d.ts +2 -1
  195. package/dist/project-learning.js +15 -45
  196. package/dist/project-memory.d.ts +5 -0
  197. package/dist/project-memory.js +22 -2
  198. package/dist/proof.js +30 -5
  199. package/dist/provider-auth.js +1 -1
  200. package/dist/publish.js +9 -1
  201. package/dist/pull-request-flow.js +5 -0
  202. package/dist/push.d.ts +26 -3
  203. package/dist/push.js +55 -15
  204. package/dist/release-adapters.d.ts +9 -0
  205. package/dist/release-adapters.js +87 -1
  206. package/dist/release-approval.d.ts +191 -0
  207. package/dist/release-approval.js +382 -0
  208. package/dist/release-auto.js +26 -17
  209. package/dist/release-card.d.ts +74 -6
  210. package/dist/release-card.js +179 -23
  211. package/dist/release-carry.d.ts +7 -2
  212. package/dist/release-carry.js +39 -9
  213. package/dist/release-cli.d.ts +4 -0
  214. package/dist/release-cli.js +38 -23
  215. package/dist/release-coverage.js +1 -1
  216. package/dist/release-next.d.ts +48 -0
  217. package/dist/release-next.js +161 -0
  218. package/dist/release.d.ts +50 -1
  219. package/dist/release.js +118 -11
  220. package/dist/remote-destination.d.ts +36 -0
  221. package/dist/remote-destination.js +217 -0
  222. package/dist/remote-main.d.ts +48 -0
  223. package/dist/remote-main.js +84 -5
  224. package/dist/repository-context.d.ts +3 -2
  225. package/dist/repository-context.js +112 -70
  226. package/dist/restart-certification.d.ts +1 -0
  227. package/dist/restart-certification.js +8 -6
  228. package/dist/result-acts.d.ts +29 -21
  229. package/dist/result-acts.js +36 -25
  230. package/dist/result-follow-ups.d.ts +2 -2
  231. package/dist/result-follow-ups.js +18 -4
  232. package/dist/result-review.d.ts +1 -1
  233. package/dist/route-change-check.d.ts +4 -0
  234. package/dist/route-change-check.js +13 -0
  235. package/dist/route-change.d.ts +22 -0
  236. package/dist/route-change.js +89 -0
  237. package/dist/route-hooks.js +3 -2
  238. package/dist/route-results.d.ts +6 -1
  239. package/dist/route-results.js +14 -5
  240. package/dist/run-closeout.d.ts +1 -1
  241. package/dist/run-closeout.js +7 -5
  242. package/dist/runner-process.js +3 -2
  243. package/dist/runner.js +2 -1
  244. package/dist/scope.d.ts +6 -1
  245. package/dist/scope.js +22 -9
  246. package/dist/serve.d.ts +1 -1
  247. package/dist/serve.js +24 -18
  248. package/dist/server/chat.js +14 -12
  249. package/dist/server/chrome.d.ts +1 -1
  250. package/dist/server/chrome.js +26 -35
  251. package/dist/server/console.css +23 -0
  252. package/dist/server/guards.js +4 -0
  253. package/dist/server/home.d.ts +6 -0
  254. package/dist/server/home.js +21 -13
  255. package/dist/server/http.js +1 -1
  256. package/dist/server/kit.js +1 -1
  257. package/dist/server/merge-ready-view.d.ts +10 -0
  258. package/dist/server/merge-ready-view.js +30 -0
  259. package/dist/server/pages-assets.d.ts +1 -0
  260. package/dist/server/pages-assets.js +8 -5
  261. package/dist/server/pages.js +103 -82
  262. package/dist/server/render-chat.js +2 -2
  263. package/dist/server/render-code-map.d.ts +30 -0
  264. package/dist/server/render-code-map.js +137 -0
  265. package/dist/server/render-flaky.d.ts +20 -0
  266. package/dist/server/render-flaky.js +59 -0
  267. package/dist/server/render-history.js +1 -1
  268. package/dist/server/render-new-task.d.ts +26 -1
  269. package/dist/server/render-new-task.js +30 -5
  270. package/dist/server/render-pages.d.ts +1 -175
  271. package/dist/server/render-pages.js +38 -313
  272. package/dist/server/render-projects.d.ts +2 -1
  273. package/dist/server/render-projects.js +9 -7
  274. package/dist/server/render-release.d.ts +19 -0
  275. package/dist/server/render-release.js +84 -0
  276. package/dist/server/render-results.d.ts +2 -2
  277. package/dist/server/render-results.js +71 -73
  278. package/dist/server/render-tasks.d.ts +13 -2
  279. package/dist/server/render-tasks.js +156 -139
  280. package/dist/server/retired-routes.d.ts +17 -0
  281. package/dist/server/retired-routes.js +53 -0
  282. package/dist/server/route-table.d.ts +4 -1
  283. package/dist/server/route-table.js +52 -13
  284. package/dist/server/settings.js +129 -22
  285. package/dist/server/task-views.js +61 -26
  286. package/dist/server/tasks.d.ts +17 -6
  287. package/dist/server/tasks.js +273 -338
  288. package/dist/server/welcome.js +1 -1
  289. package/dist/settings-groups-ui.js +1 -1
  290. package/dist/setup-guide.d.ts +2 -2
  291. package/dist/stop-proof.d.ts +67 -0
  292. package/dist/stop-proof.js +295 -0
  293. package/dist/storage-sweep-process.js +1 -1
  294. package/dist/storage-sweep.d.ts +5 -3
  295. package/dist/storage-sweep.js +30 -9
  296. package/dist/storage-ui.js +28 -17
  297. package/dist/storage.d.ts +27 -2
  298. package/dist/storage.js +39 -2
  299. package/dist/store.d.ts +148 -34
  300. package/dist/store.js +626 -177
  301. package/dist/surface.js +46 -5
  302. package/dist/sweep-quarantine.d.ts +1 -0
  303. package/dist/sweep-quarantine.js +6 -4
  304. package/dist/tamper-scan.d.ts +4 -0
  305. package/dist/tamper-scan.js +90 -0
  306. package/dist/task-aliases.d.ts +5 -0
  307. package/dist/task-aliases.js +9 -0
  308. package/dist/task-control.js +4 -1
  309. package/dist/task-outcome-cli.d.ts +2 -0
  310. package/dist/task-outcome-cli.js +36 -17
  311. package/dist/task-sizing.d.ts +5 -0
  312. package/dist/task-sizing.js +2 -0
  313. package/dist/task-source.d.ts +7 -0
  314. package/dist/task-source.js +14 -0
  315. package/dist/task-status.d.ts +9 -0
  316. package/dist/task-status.js +40 -49
  317. package/dist/teardown-git.d.ts +16 -0
  318. package/dist/teardown-git.js +63 -5
  319. package/dist/telegram-progress.js +5 -3
  320. package/dist/telegram-status.js +6 -4
  321. package/dist/test-infra.d.ts +8 -0
  322. package/dist/test-infra.js +18 -0
  323. package/dist/tidy.js +1 -1
  324. package/dist/toolroll-update-ui.js +2 -2
  325. package/dist/toolroll-update.js +18 -5
  326. package/dist/tui-contract.d.ts +24 -0
  327. package/dist/tui-source.js +67 -8
  328. package/dist/tui.js +51 -51
  329. package/dist/vanished-retry.d.ts +30 -0
  330. package/dist/vanished-retry.js +174 -0
  331. package/dist/work-index.d.ts +2 -0
  332. package/dist/work-index.js +82 -70
  333. package/dist/work-summary.d.ts +2 -2
  334. package/dist/work-summary.js +2 -0
  335. package/dist/workspace-ui.d.ts +4 -2
  336. package/dist/workspace-ui.js +28 -22
  337. package/dist/worktree.d.ts +12 -3
  338. package/dist/worktree.js +68 -13
  339. package/package.json +1 -1
  340. package/dist/server/render-next.d.ts +0 -63
  341. package/dist/server/render-next.js +0 -109
@@ -13,7 +13,7 @@
13
13
  import { DEFAULT_LEAD_NAME } from "./lead-identity.js";
14
14
  import { html } from "./html.js";
15
15
  import { FAILED_CHECK } from "./result-acts.js";
16
- import { CODE_NEED, NEEDS, needSentence, waitSentence } from "./needs-you.js";
16
+ import { CODE_READING, NEEDS, needSentence, waitSentence } from "./needs-you.js";
17
17
  /** A cancelled task with a successor reads "Replaced by <id>" everywhere, never "Cancelled". */
18
18
  export const replacedWords = (successor) => `Replaced by ${successor}`;
19
19
  const REPLACED = /^Replaced by \S+/;
@@ -28,6 +28,8 @@ export const CHECKS_LABEL = "Project checks";
28
28
  export const PR_CI_LABEL = "PR CI";
29
29
  /** The one verb for opening a saved result, on every surface. */
30
30
  export const OPEN_RESULT = "Open result";
31
+ /** A machine-run check under way (a prepared candidate: no agent runs), on every surface that has no richer words. */
32
+ export const MACHINE_CHECK_RUNNING = "Running the check on the prepared version.";
31
33
  export const CHECKED_AT_RELEASE = "Checked at release";
32
34
  export function releaseStateOf(checks) {
33
35
  if (checks?.level !== "off" || checks.status === "passed" || checks.status === "failed")
@@ -162,46 +164,47 @@ function sentenceOf(headline, facts) {
162
164
  const reason = facts.reason?.trim() || null;
163
165
  const sha = short(facts.checks?.head ?? null);
164
166
  switch (headline) {
165
- case "Queued": return reason ?? "Waiting for a worker.";
167
+ case "Queued": return reason ?? "Waiting for a builder.";
166
168
  case "Planning": return reason ?? "The lead is writing the plan.";
167
169
  // One plain sentence per need (needs-you.ts); a recorded reason only where the need keeps one.
168
170
  case "Needs you": return facts.stage === "finished" ? resultAsk(facts) ?? needSentence("review-result", reason, facts.needContext) : needSentence(facts.need ?? "other", reason, facts.needContext);
169
171
  case "Waiting": return waitSentence(facts.wait, reason, facts.needContext);
170
172
  case "Building": return facts.stage === "checking" ? "Checks are running on the change."
171
- : facts.stage === "reviewing" ? "Reviewing the change before it reaches you." : reason ?? "An agent is working on it.";
173
+ : facts.stage === "reviewing" ? "Reviewing the change before it reaches you."
174
+ : facts.machine === true ? MACHINE_CHECK_RUNNING : reason ?? "An agent is working on it.";
172
175
  case "Ready for review":
173
176
  if (facts.report)
174
- return "The report is ready to read. Read it, then mark it complete.";
177
+ return "The report is ready to read. Read it, then accept it.";
175
178
  if (releaseStateOf(facts.checks) === "covered")
176
- return `Checked at release on ${short(facts.checks.release.head) ?? "its release"}. Review the change, then mark it complete.`;
179
+ return `Checked at release on ${short(facts.checks.release.head) ?? "its release"}. Review the change, then accept it.`;
177
180
  if (facts.checks?.level === "off" && facts.checks.status !== "passed")
178
- return "Checked at release. Review the change, then mark it complete.";
181
+ return "Checked at release. Review the change, then accept it.";
179
182
  if (facts.checks?.status === "passed") {
180
183
  // The Checks row names the others and the batch commit; the sentence doesn't repeat them.
181
184
  const peers = facts.checks.batch?.state === "together" ? facts.checks.batch.peers.length : 0;
182
185
  if (peers > 0)
183
- return `Checks passed together with ${peers === 1 ? "1 other result" : `${peers} other results`}. Review the change, then mark it complete.`;
184
- return `${facts.checks.level === "quick" ? "Quick checks" : "Checks"} passed${sha === null ? "" : ` on ${sha}`}. Review the change, then mark it complete.`;
186
+ return `Checks passed together with ${peers === 1 ? "1 other result" : `${peers} other results`}. Review the change, then accept it.`;
187
+ return `${facts.checks.level === "quick" ? "Quick checks" : "Checks"} passed${sha === null ? "" : ` on ${sha}`}. Review the change, then accept it.`;
185
188
  }
186
189
  if (facts.checks?.batch?.state === "waiting")
187
- return "Its full check runs with other results within 10 minutes. Review the change, then mark it complete.";
190
+ return "Its full check runs with other results within 10 minutes. Review the change, then accept it.";
188
191
  if (facts.checks?.status === "unavailable")
189
- return `${facts.checks.cause === "command-changed" ? CHECK_COMMAND_CHANGED : CHECK_UNREADABLE} Review the change, then mark it complete.`;
192
+ return `${facts.checks.cause === "command-changed" ? CHECK_COMMAND_CHANGED : CHECK_UNREADABLE} Review the change, then accept it.`;
190
193
  // Never claim a check that isn't known to have passed.
191
194
  if (facts.checks == null)
192
- return "Review the change, then mark it complete.";
193
- return "Built without a passing project check. Review the change, then mark it complete.";
195
+ return "Review the change, then accept it.";
196
+ return "Built without a passing project check. Review the change, then accept it.";
194
197
  case "Complete": {
195
198
  // Landed work says why once; its merged pull request is that reason, not a second sentence.
196
199
  if (facts.landed)
197
- return `Marked complete automatically: ${facts.landed}.`;
198
- const by = facts.completedBy ? `Marked complete by ${facts.completedBy}.` : "Marked complete.";
200
+ return `Accepted automatically: ${facts.landed}.`;
201
+ const by = facts.completedBy ? `Accepted by ${facts.completedBy}.` : "Accepted.";
199
202
  const pr = facts.pullRequest;
200
203
  return pr?.state === "merged" ? `${by} ${pr.number === null ? "Its pull request" : `Pull request #${pr.number}`} merged.` : by;
201
204
  }
202
205
  case "Failed":
203
206
  if (facts.checks?.status === "failed")
204
- return `${facts.checks.level === "quick" ? "Quick checks" : "Checks"} failed${sha === null ? "" : ` on ${sha}`}. See what broke, then retry or ask for changes.`;
207
+ return `${facts.checks.level === "quick" ? "Quick checks" : "Checks"} failed${sha === null ? "" : ` on ${sha}`}. See what broke, then retry or send it back.`;
205
208
  return reason ?? "The last attempt stopped before it finished. Review it, then retry.";
206
209
  case "Stopped": return reason ?? "Stopped by a person. The work so far is kept.";
207
210
  }
@@ -291,13 +294,13 @@ function detailsOf(headline, facts) {
291
294
  if (evidence != null) {
292
295
  const action = { label: "See what was kept", href: facts.links?.result ?? null };
293
296
  if (evidence.damaged > 0)
294
- row("evidence", "Saved evidence", "Some saved files changed", problem(headline), headline === "Failed" ? {} : { action });
297
+ row("evidence", "Saved files", "Some saved files changed", problem(headline), headline === "Failed" ? {} : { action });
295
298
  else if (evidence.missing > 0)
296
- row("evidence", "Saved evidence", "Some saved files are missing", problem(headline), headline === "Failed" ? {} : { action });
299
+ row("evidence", "Saved files", "Some saved files are missing", problem(headline), headline === "Failed" ? {} : { action });
297
300
  else if (evidence.shortened > 0)
298
- row("evidence", "Saved evidence", "Some output shortened", "note", { action });
301
+ row("evidence", "Saved files", "Some output shortened", "note", { action });
299
302
  else
300
- row("evidence", "Saved evidence", "Complete", "ok");
303
+ row("evidence", "Saved files", "All kept", "ok");
301
304
  }
302
305
  return rows;
303
306
  }
@@ -331,24 +334,9 @@ export function statusOf(facts) {
331
334
  /** The name most surfaces already import. */
332
335
  export const taskStatusOf = statusOf;
333
336
  // ---- reading the existing projections ----------------------------------------
334
- /** Every dispatch or work-index code, as the stage it means. Unknown codes that
335
- * wait on a person stay Needs you; everything else waits in the queue. */
336
- const CODE_STAGE = {
337
- queued: ["queued"], ready: ["queued"], "worker-at-capacity": ["queued"], "retry-scheduled": ["queued"], updating: ["queued"],
338
- "waiting-dependency": ["queued"], "scouting-ready": ["queued"], "planning-ready": ["queued"], "provider-quota": ["queued"],
339
- // A request the planner cannot be given (too long to hand back, say) waits on a person to shorten it.
340
- "planner-source": ["needs-you", "fix-request"],
341
- running: ["building"], reviewing: ["reviewing"], "review-pending": ["building"], "checks-running": ["checking"],
342
- "signed-out": ["needs-you", "sign-in"],
343
- "needs-approval": ["needs-you", "approval"],
344
- "waiting-decision": ["needs-you", "answer"], "decision-queue": ["needs-you", "answer"],
345
- "build-stopping": ["waiting"], "other-computer": ["waiting"],
346
- stopping: ["stopped"], stopped: ["stopped"], paused: ["stopped"], cancelled: ["stopped"],
347
- failed: ["failed"], "waiting-incident": ["failed"], "checks-failed": ["failed"],
348
- "ready-to-check": ["finished"], "ready-to-review": ["finished"], "report-ready": ["finished"], "no-change": ["finished"],
349
- "pr-opened": ["finished"], "merge-observed": ["finished"], "pr-closed": ["finished"], "agent-attested": ["finished"], "accepted-exception": ["finished"],
350
- complete: ["complete"],
351
- };
337
+ /** Codes that read Needs you whoever asks (their reason is a person's by nature). Any other need reads Needs you
338
+ * only when the caller says a person is needed; otherwise it waits in the queue. */
339
+ const ALWAYS_NEEDS = new Set(["planner-source", "signed-out", "needs-approval", "waiting-decision", "decision-queue"]);
352
340
  /** Queue reasons worth a sentence of their own; any other queued or
353
341
  * building reason is scheduler wording, and the default sentence is plainer. */
354
342
  const QUEUED_REASONS = new Set(["worker-at-capacity", "waiting-dependency", "retry-scheduled", "updating", "planning-ready", "scouting-ready", "provider-quota"]);
@@ -359,15 +347,18 @@ export function plainReasonOf(stage, code, detail) {
359
347
  return QUEUED_REASONS.has(code) ? detail ?? null : null;
360
348
  return detail ?? null;
361
349
  }
350
+ /** Every dispatch or work-index code, as the stage it means (needs-you.ts CODE_READING, the one table). Unknown
351
+ * codes that wait on a person stay Needs you; everything else waits in the queue. */
362
352
  export function stageOfCode(code, options = {}) {
363
353
  if (code === "running" && options.planning)
364
354
  return { stage: "planning" };
365
355
  if (code === "held")
366
356
  return options.operatorHold === false ? { stage: "needs-you", need: "hold" } : { stage: "stopped" };
367
- const known = CODE_STAGE[code];
368
- if (known !== undefined)
369
- return known[1] === undefined ? { stage: known[0] } : { stage: known[0], need: known[1] };
370
- return options.needsPerson === false ? { stage: "queued" } : { stage: "needs-you", need: CODE_NEED[code] ?? "other" };
357
+ const known = Object.prototype.hasOwnProperty.call(CODE_READING, code) ? CODE_READING[code] : undefined;
358
+ if (known !== undefined && (known.stage !== "needs-you" || ALWAYS_NEEDS.has(code))) {
359
+ return { stage: known.stage, ...(known.need === undefined ? {} : { need: known.need }), ...(known.wait === undefined ? {} : { wait: known.wait }) };
360
+ }
361
+ return options.needsPerson === false ? { stage: "queued" } : { stage: "needs-you", need: known?.need ?? "other" };
371
362
  }
372
363
  /** The pull request as the task page's follower reports it, or the bare publication row. */
373
364
  export function pullRequestFactOf(publication) {
@@ -473,7 +464,7 @@ const quoted = (statement) => {
473
464
  function unresolvedAsk(row, word) {
474
465
  const what = word === "Not shown yet" && screenshotMissing(row) ? "A screenshot is missing for"
475
466
  : word === "Not shown yet" ? "Nothing shows this requirement is met" : word === "Unverified" ? "This requirement couldn't be verified" : "This requirement isn't met";
476
- return `${what}: ${quoted(row.statement) || row.id || "a requirement"}. Ask for changes, or accept the result with a reason.`;
467
+ return `${what}: ${quoted(row.statement) || row.id || "a requirement"}. Send it back, or accept the result with a reason.`;
477
468
  }
478
469
  /** Requirements from the stored matrix: met, the ones only a person confirms, and the total. */
479
470
  export function requirementsOf(matrix, release) {
@@ -482,7 +473,7 @@ export function requirementsOf(matrix, release) {
482
473
  const words = matrix.map(row => requirementWordOf(row, null, release));
483
474
  const yours = matrix.find((_, index) => words[index] === "You check");
484
475
  const unshown = matrix.filter((row, index) => words[index] === "Not shown yet" && screenshotMissing(row));
485
- const ask = yours !== undefined ? `Check this requirement yourself, then mark it complete: ${quoted(yours.statement)}`
476
+ const ask = yours !== undefined ? `Check this requirement yourself, then accept it: ${quoted(yours.statement)}`
486
477
  : unshown.length > 0 ? unresolvedAsk(unshown[0], "Not shown yet") : null;
487
478
  return { met: words.filter(word => word === "Met").length, total: matrix.length, yours: words.filter(word => word === "You check").length,
488
479
  missed: words.filter(word => word === "Not met" || word === "Not shown yet").length,
@@ -499,12 +490,12 @@ export function completionBlockersOf(f) {
499
490
  const release = releaseStateOf(f.checks);
500
491
  const backing = checkBackingOf(f.checks);
501
492
  if (!f.report && f.checks?.status === "failed") {
502
- blockers.push({ key: "check-failed", message: `Checks failed${f.checks.exitCode === null ? "" : ` (exit ${f.checks.exitCode})`}. Fix them and run checks again, or ask for changes.` });
493
+ blockers.push({ key: "check-failed", message: `Checks failed${f.checks.exitCode === null ? "" : ` (exit ${f.checks.exitCode})`}. Fix them and run checks again, or send it back.` });
503
494
  }
504
495
  else if (!f.report && f.checkRequired && f.checks?.status !== "passed" && release === undefined) {
505
- blockers.push({ key: "check-missing", message: checksUnderway(f.checks) ? "Its project check hasn't finished yet. Mark it complete once it passes."
506
- : f.checks?.status === "unavailable" ? f.checks.cause === "command-changed" ? `${CHECK_COMMAND_CHANGED.replace(/; run checks again\.$/, ".")} Run checks again, then mark it complete.` : `${CHECK_UNREADABLE} Run checks on it, then mark it complete.`
507
- : "No passing project check is recorded for this result. Run checks on it, then mark it complete." });
496
+ blockers.push({ key: "check-missing", message: checksUnderway(f.checks) ? "Its project check hasn't finished yet. Accept it once it passes."
497
+ : f.checks?.status === "unavailable" ? f.checks.cause === "command-changed" ? `${CHECK_COMMAND_CHANGED.replace(/; run checks again\.$/, ".")} Run checks again, then accept it.` : `${CHECK_UNREADABLE} Run checks on it, then accept it.`
498
+ : "No passing project check is recorded for this result. Run checks on it, then accept it." });
508
499
  }
509
500
  if (!f.accepted && f.checks?.status !== "failed") {
510
501
  const refuted = f.verdict === "refuted";
@@ -514,11 +505,11 @@ export function completionBlockersOf(f) {
514
505
  blockers.push({ key: "criteria", message: unresolvedAsk(unresolved[0].row, unresolved[0].word) });
515
506
  else if (unresolved.length > 1) {
516
507
  const ids = unresolved.map(one => one.row.id ?? "?");
517
- blockers.push({ key: "criteria", message: `${unresolved.length} requirements ${unresolved.every(one => one.word === "Unverified") ? "couldn't be verified" : "aren't met yet"} (${ids.slice(0, 6).join(", ")}${ids.length > 6 ? ", …" : ""}). Ask for changes, or accept the result with a reason.` });
508
+ blockers.push({ key: "criteria", message: `${unresolved.length} requirements ${unresolved.every(one => one.word === "Unverified") ? "couldn't be verified" : "aren't met yet"} (${ids.slice(0, 6).join(", ")}${ids.length > 6 ? ", …" : ""}). Send it back, or accept the result with a reason.` });
518
509
  }
519
510
  }
520
511
  if (f.high > 0)
521
- blockers.push({ key: "high", message: `The automatic review found ${f.high === 1 ? "a high-severity problem" : `${f.high} high-severity problems`}. Ask for changes before marking it complete.` });
512
+ blockers.push({ key: "high", message: `The automatic review found ${f.high === 1 ? "a high-severity problem" : `${f.high} high-severity problems`}. Send it back before accepting it.` });
522
513
  return blockers;
523
514
  }
524
515
  /** A refuted report verifies none of its requirements, whatever it marked met (a person's acceptance doesn't change that). */
@@ -46,6 +46,10 @@ export declare const INCLUDE_PARSE_TIMED_OUT = "Git didn't finish reading it in
46
46
  export type GitBinding = {
47
47
  repo?: string | null;
48
48
  };
49
+ /** The shared git folder of a recorded project, read from disk and proven as boundDirs proves it: the project's own
50
+ * real `.git` folder, holding no `commondir`. No pointer file or `commondir` an agent can write decides it, and no git
51
+ * runs (a page visit reads this). Throws with the reason otherwise. */
52
+ export declare function recordedCommonDir(repo: string): string;
49
53
  /** Settings fixed on every write through a view: `worktree add` records absolute paths (relative ones would need a
50
54
  * config write, which a view keeps); no transport at all (a caller that fetches allows its one protocol by name, as the
51
55
  * nightly check's local fetch does with protocol.file.allow); and no split index (its shared index files would be
@@ -55,6 +59,12 @@ export declare const WRITE_VIEW_FLAGS: readonly string[];
55
59
  * program (anything but INERT_LFS). A file that
56
60
  * isn't a plain one, or doesn't parse, counts as naming one. */
57
61
  export declare function lfsExtensionIn(checkout: string, gitFile?: string, limit?: Limit): boolean;
62
+ /** The hardened runners' own reads before the caller's command (the checkout's config listing): their own bounds, never
63
+ * the caller's. A caller's small `maxBuffer` is for its own command's output (builder.ts reads one ls-tree line with
64
+ * 2 KiB): inherited, it failed the config listing once a project's .git/config grew past 2 KiB (one [branch] section per
65
+ * work branch), and every prepared release check then failed as "its config couldn't be read" (runs 3316–3324). */
66
+ export declare const CONFIG_READ_BYTES: number;
67
+ export declare const CONFIG_READ_MS = 30000;
58
68
  /** Every command runs against a view of the checkout's repository (gitView) whose config and info are copies the
59
69
  * plane made, inspected there, so neither can change between the inspection and the run. `mode` "read" is for reading
60
70
  * a checkout an agent may still be writing (the code page): every command also takes readFlags(), and the view holds
@@ -143,4 +153,10 @@ export declare function lazyFetchSetting(listing: string): string | null;
143
153
  * command and protocol settings refused, a partial clone refused) is the config git then runs with.
144
154
  */
145
155
  export declare function lazyFetchSafeGit(inner?: Runner): Runner;
156
+ /**
157
+ * The checkout a folder sits in, for choosing a folder (easy-start): the nearest parent holding a `.git`, then read
158
+ * through checkoutGitSync, so a config that includes another file (a FIFO), or sets a command, is refused and the read
159
+ * is bounded. Null when there is no checkout or it can't be read that way.
160
+ */
161
+ export declare function checkoutTop(dir: string, timeoutMs?: number): string | null;
146
162
  export {};
@@ -477,6 +477,12 @@ function boundDirs(checkout, binding = {}) {
477
477
  }
478
478
  return dirs;
479
479
  }
480
+ /** The shared git folder of a recorded project, read from disk and proven as boundDirs proves it: the project's own
481
+ * real `.git` folder, holding no `commondir`. No pointer file or `commondir` an agent can write decides it, and no git
482
+ * runs (a page visit reads this). Throws with the reason otherwise. */
483
+ export function recordedCommonDir(repo) {
484
+ return boundDirs(repo, { repo }).common;
485
+ }
480
486
  /** What git keeps only per worktree and the plane never links: HEAD (git won't take it as a link) and the pointers. */
481
487
  /** What the plane links of a worktree's own git folder (git writes them in place through the link: the index and its
482
488
  * logs by lockfile, per-worktree refs and logs inside the folders), whether or not they exist yet; a rebase's,
@@ -1061,6 +1067,28 @@ export function lfsExtensionIn(checkout, gitFile = "git", limit) {
1061
1067
  const parsed = spawnSync(gitFile, ["config", "--file", path, "--no-includes", "--name-only", "--list", "-z"], { cwd: "/", encoding: "utf8", timeout: setupTimeout(limit), killSignal: "SIGKILL", env: { PATH: process.env["PATH"] ?? "", GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null", HOME: "/nonexistent" } });
1062
1068
  return parsed.status !== 0 || parsed.stdout.split("\0").filter(Boolean).some(name => lfsCommand(name) || ["include", "includeif"].includes(keyParts(name).section));
1063
1069
  }
1070
+ /** The hardened runners' own reads before the caller's command (the checkout's config listing): their own bounds, never
1071
+ * the caller's. A caller's small `maxBuffer` is for its own command's output (builder.ts reads one ls-tree line with
1072
+ * 2 KiB): inherited, it failed the config listing once a project's .git/config grew past 2 KiB (one [branch] section per
1073
+ * work branch), and every prepared release check then failed as "its config couldn't be read" (runs 3316–3324). */
1074
+ export const CONFIG_READ_BYTES = 16 * 1024 * 1024;
1075
+ export const CONFIG_READ_MS = 30_000;
1076
+ /** What of a caller's options an internal read keeps: where it runs and with what environment and containment —
1077
+ * never its time limit, output limit, stdin or output callbacks. */
1078
+ function internalOptions(options, env) {
1079
+ const { cwd, envAllowlist, omitEnv, fence, fenceGrants, confinement, processGroup, owner, signal } = options ?? {};
1080
+ return { ...(cwd === undefined ? {} : { cwd }), ...(envAllowlist === undefined ? {} : { envAllowlist }), ...(omitEnv === undefined ? {} : { omitEnv }),
1081
+ ...(fence === undefined ? {} : { fence }), ...(fenceGrants === undefined ? {} : { fenceGrants }), ...(confinement === undefined ? {} : { confinement }),
1082
+ ...(processGroup === undefined ? {} : { processGroup }), ...(owner === undefined ? {} : { owner }), ...(signal === undefined ? {} : { signal }),
1083
+ env, timeoutMs: CONFIG_READ_MS, maxBuffer: CONFIG_READ_BYTES };
1084
+ }
1085
+ /** Why the checkout's config listing failed, in words, for the refusal: git's own first line, or what stopped it. */
1086
+ function listingFailure(result) {
1087
+ if (result.timedOut)
1088
+ return `its config couldn't be read (the read ran past ${CONFIG_READ_MS / 1000} s)`;
1089
+ const said = `${result.stderr}`.split("\n").map(line => line.trim()).find(line => line !== "");
1090
+ return `its config couldn't be read${said === undefined ? ` (git exited ${result.code})` : ` (${said.slice(0, 300)})`}`;
1091
+ }
1064
1092
  /** Every command runs against a view of the checkout's repository (gitView) whose config and info are copies the
1065
1093
  * plane made, inspected there, so neither can change between the inspection and the run. `mode` "read" is for reading
1066
1094
  * a checkout an agent may still be writing (the code page): every command also takes readFlags(), and the view holds
@@ -1089,10 +1117,10 @@ export function checkoutGit(inner = run, mode = "build", binding = {}) {
1089
1117
  }
1090
1118
  try {
1091
1119
  const viewed = { ...options, env: { ...(options?.env ?? {}), ...view.env } };
1092
- // The checkout's own config may name no command.
1093
- const own = await inner("git", ["--no-optional-locks", "config", "--list", "--show-scope", "-z"], viewed);
1120
+ // The checkout's own config may name no command. Read with this runner's own bounds (internalOptions).
1121
+ const own = await inner("git", ["--no-optional-locks", "config", "--list", "--show-scope", "-z"], internalOptions(options, viewed.env));
1094
1122
  if (own.code !== 0)
1095
- return refused("its config couldn't be read");
1123
+ return refused(listingFailure(own));
1096
1124
  const key = commandSetting(own.stdout, { lfsChecked: true });
1097
1125
  if (key !== null)
1098
1126
  return refused(`the checkout's git config sets ${key}, which can run a command`);
@@ -1185,9 +1213,11 @@ export function viewedGitSync(cwd, args, options = {}) {
1185
1213
  const env = { PATH: process.env["PATH"] ?? "", HOME: process.env["HOME"] ?? "", LANG: "C", GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null", GIT_TERMINAL_PROMPT: "0", ...view.env };
1186
1214
  const timeoutNow = () => { expire(); return limit === undefined ? options.timeoutMs ?? 60_000 : Math.max(1, Math.min(options.timeoutMs ?? 60_000, limit())); };
1187
1215
  const fixed = { cwd, env, encoding: "utf8", killSignal: "SIGKILL", maxBuffer: options.maxBuffer ?? 1_048_576, stdio: ["ignore", "pipe", "pipe"] };
1188
- const own = spawnSync(gitFile, ["--no-optional-locks", "config", "--list", "--show-scope", "-z"], { ...fixed, timeout: timeoutNow() });
1216
+ // Its own bounds, never the caller's output limit (CONFIG_READ_BYTES); a caller's deadline still holds.
1217
+ const own = spawnSync(gitFile, ["--no-optional-locks", "config", "--list", "--show-scope", "-z"], { ...fixed, maxBuffer: CONFIG_READ_BYTES,
1218
+ timeout: limit === undefined ? CONFIG_READ_MS : Math.max(1, Math.min(CONFIG_READ_MS, limit())) });
1189
1219
  if (own.status !== 0)
1190
- throw new Error("its config couldn't be read");
1220
+ throw new Error(own.error !== undefined ? `its config couldn't be read (${own.error.message.slice(0, 300)})` : "its config couldn't be read");
1191
1221
  const key = commandSetting(own.stdout, { lfsChecked: true });
1192
1222
  if (key !== null)
1193
1223
  throw new Error(`the checkout's git config sets ${key}, which can run a command`);
@@ -1333,3 +1363,31 @@ export function lazyFetchSafeGit(inner = run) {
1333
1363
  // repository's config and its includes as git found them, and an include naming a FIFO could hang the plane.
1334
1364
  return checkoutGit(inner, "read");
1335
1365
  }
1366
+ /**
1367
+ * The checkout a folder sits in, for choosing a folder (easy-start): the nearest parent holding a `.git`, then read
1368
+ * through checkoutGitSync, so a config that includes another file (a FIFO), or sets a command, is refused and the read
1369
+ * is bounded. Null when there is no checkout or it can't be read that way.
1370
+ */
1371
+ export function checkoutTop(dir, timeoutMs = 10_000) {
1372
+ let at;
1373
+ try {
1374
+ at = realpathSync(dir);
1375
+ }
1376
+ catch {
1377
+ return null;
1378
+ }
1379
+ for (;;) {
1380
+ if (existsSync(join(at, ".git"))) {
1381
+ try {
1382
+ return checkoutGitSync(at, ["rev-parse", "--show-toplevel"], { timeoutMs }).trim() || null;
1383
+ }
1384
+ catch {
1385
+ return null;
1386
+ }
1387
+ }
1388
+ const up = dirname(at);
1389
+ if (up === at)
1390
+ return null;
1391
+ at = up;
1392
+ }
1393
+ }
@@ -4,6 +4,7 @@
4
4
  * CLI show: Ready for review once the saved result can be inspected, Complete
5
5
  * once the lead or a person has marked that exact result. Slack, Discord and
6
6
  * Teams send this same card. */
7
+ import { machineChecked } from "./machine-check.js";
7
8
  import { assignmentOf } from "./assignment.js";
8
9
  import { chatResultHref, chatControlHref } from "./chat-controls.js";
9
10
  import { phoneText, projectLabel } from "./telegram-status.js";
@@ -55,8 +56,8 @@ export function telegramProgressCard(store, run, taskId, project, now = new Date
55
56
  }
56
57
  else if (accessBlocked) {
57
58
  facts = { stage: "needs-you", need: "other", reason: holds.some(hold => hold.ownerKind === "backoff")
58
- ? "The worker can't read the project folder. It retries after a short pause; check its folder access."
59
- : "The worker can't read the project folder. Restore its access, then resume." };
59
+ ? "The builder can't read the project folder. It retries after a short pause; check its folder access."
60
+ : "The builder can't read the project folder. Restore its access, then resume." };
60
61
  }
61
62
  else if (run.outcome === null && stop !== null && stop.resumedAt === null) {
62
63
  facts = { stage: "stopped", reason: stop.settledAt === null ? "Stopping. Waiting for the running process to end." : "Paused by a person. Inspect the saved work, then resume." };
@@ -65,7 +66,8 @@ export function telegramProgressCard(store, run, taskId, project, now = new Date
65
66
  facts = { stage: "stopped", reason: `On hold: ${phoneText(operatorHold.reason, 150)}` };
66
67
  }
67
68
  else if (run.outcome === null) {
68
- facts = { stage: run.phase !== null && run.phase !== "agent-running" ? "checking" : run.role === "planner" ? "planning" : "building" };
69
+ facts = { stage: run.phase !== null && run.phase !== "agent-running" ? "checking" : run.role === "planner" ? "planning" : "building",
70
+ ...(machineChecked(store, taskId) ? { machine: true } : {}) };
69
71
  }
70
72
  else if (run.outcome === "parked") {
71
73
  facts = { stage: "needs-you", need: "answer" };
@@ -1,4 +1,5 @@
1
1
  import { publicChatText } from './chat-display.js';
2
+ import { machineChecked } from "./machine-check.js";
2
3
  import { manualReviewOnly } from "./proof.js";
3
4
  /** Read-only phone views over the same dispatch and proof records as chat.
4
5
  * No provider calls, repository access, new workflow state, or inferred success. */
@@ -65,7 +66,8 @@ function headlineFor(store, id, d, read_ = {}) {
65
66
  return taskStatusOf(assignmentStatusFacts(assignment));
66
67
  }
67
68
  const finished = read.stage === "finished" || read.stage === "complete" || read.stage === "failed";
68
- return taskStatusOf({ stage: read.stage, ...(read.need === undefined ? {} : { need: read.need }), reason: finished ? null : plainReasonOf(read.stage, d.code, d.detail),
69
+ return taskStatusOf({ stage: read.stage, ...(read.need === undefined ? {} : { need: read.need }), ...(read.wait === undefined ? {} : { wait: read.wait }), reason: finished ? null : plainReasonOf(read.stage, d.code, d.detail),
70
+ ...(machineChecked(store, id) ? { machine: true } : {}),
69
71
  checks: read.stage === "failed" && d.code === "proof-refuted" ? { status: "failed", exitCode: null, head: null } : null });
70
72
  }
71
73
  function groupOf(d) {
@@ -238,7 +240,7 @@ export function phoneTaskView(store, repos, id, now, root) {
238
240
  const hiddenDependency = blocker !== null && (blocker.repo === null || !repos.includes(blocker.repo));
239
241
  lines.push(hiddenDependency ? "A required task outside this phone view has not finished. Open the dependency in the console." : plain(shared.sentence, 650));
240
242
  if (d.nextAt !== null)
241
- lines.push(`Earliest recorded wake: ${plain(d.nextAt, 40)} (a connected worker is still required).`);
243
+ lines.push(`Earliest recorded wake: ${plain(d.nextAt, 40)} (a connected builder is still required).`);
242
244
  const runs = store.runsFor(ref.id);
243
245
  const result = task.state === "done" ? runs.find(r => (r.role === "builder" || r.role === "scout") && (r.outcome === "built" || r.outcome === "no-change") && r.finishedAt !== null) : undefined;
244
246
  const latest = result ?? runs[0];
@@ -262,14 +264,14 @@ export function phoneTaskView(store, repos, id, now, root) {
262
264
  : approvedCheckStateOf(approvedCheckOf(proof.verdict, proof.reasons, checksFactOf(assignmentChecksForRun(store, root, result.id, now), result.headRevision)));
263
265
  const verifiedWords = root === undefined ? "Open the result to see its checks" : checkNow === "passed" ? "Checks passed at completion"
264
266
  : checkNow === "failed" ? "Checks failed" : checkNow === "running" ? "Checks running" : "The check result couldn't be read";
265
- const proofWords = { verified: verifiedWords, attested: "Checks reported by the agent, not run by Toolroll", short: "Required saved material is missing", refuted: "The saved result conflicts with the approved scope" };
267
+ const proofWords = { verified: verifiedWords, attested: "Checks reported by the agent, not run by Toolroll", short: "Required saved material is missing", refuted: "The saved result doesn't match the approved plan" };
266
268
  lines.push(`Checks: ${proof === null ? "No completion record saved" : manualReviewOnly(proof) ? accepted ? "Accepted by a person; the recorded checks are unchanged" : "A person must inspect this result; no recorded check failed" : proofWords[proof.verdict]}.`);
267
269
  if (manualReviewOnly(proof) && !accepted)
268
270
  lines.push('Reply “Send the result summary” for the checks, notes and screenshots.');
269
271
  if (proof !== null && proof.matrix.length > 0)
270
272
  lines.push(`Requirements: ${proof.matrix.filter(row => row.state === "pass").length}/${proof.matrix.length} satisfied in the saved record.`);
271
273
  if (accepted && !manualReviewOnly(proof))
272
- lines.push("An operator accepted this result; that does not change its recorded checks.");
274
+ lines.push("An approver accepted this result; that does not change its recorded checks.");
273
275
  const publication = store.publicationForRun(result.id);
274
276
  const delivery = publication?.remoteState === "MERGED" ? `#${publication.prNumber ?? "?"} merged` : publication?.remoteState === "CLOSED" ? `#${publication.prNumber ?? "?"} closed without merging` : publication?.state === "opened" ? `#${publication.prNumber ?? "?"} open` : publication?.state === "pushed" || publication?.state === "intended" ? "Opening…" : publication?.state === "failed" ? "Couldn't open; the commit is safe locally" : null;
275
277
  if (delivery !== null)
@@ -24,3 +24,11 @@ export declare function testInfrastructure(files: ReadonlyMap<string, string>, a
24
24
  * the other rules don't already trust: any source file that is neither a test nor product code.
25
25
  */
26
26
  export declare function mightName(open: Unresolved, path: string, found: TestInfrastructure): boolean;
27
+ /** Whether a path is a test file by any extension a test runner could be pointed at, not only those run today. */
28
+ export declare const isTestPath: (path: string) => boolean;
29
+ /**
30
+ * Why a new test file can't carry on what it imports, or null (docs/plans/trusted-release-reports.md, H4). A test file is
31
+ * never infrastructure here, so nothing else looks at an edge between two of them, and importing one runs its top level
32
+ * (its `vi.mock`s and hooks included) in the importer. A computed import might name one, so it refuses too.
33
+ */
34
+ export declare function testImportProblem(path: string, text: string): string | null;
@@ -319,3 +319,21 @@ export function mightName(open, path, found) {
319
319
  const stem = path.replace(/\.[^/.]+$/, "");
320
320
  return path === prefix || stem === prefix.replace(/\.(?:[cm]?js|jsx)$/, "") || path.startsWith(`${prefix}/`) || (open.specifier !== null && open.specifier.endsWith("/") === false && path.startsWith(prefix));
321
321
  }
322
+ /** Whether a path is a test file by any extension a test runner could be pointed at, not only those run today. */
323
+ export const isTestPath = (path) => TEST.test(path);
324
+ /** A specifier that names a test file, whatever it is spelled as: `./a.test.js`, `../b.test`, `@/c.test.ts`. */
325
+ const NAMES_TEST = /\.test(?:\.[cm]?[jt]sx?)?(?:[?#].*)?$/;
326
+ /**
327
+ * Why a new test file can't carry on what it imports, or null (docs/plans/trusted-release-reports.md, H4). A test file is
328
+ * never infrastructure here, so nothing else looks at an edge between two of them, and importing one runs its top level
329
+ * (its `vi.mock`s and hooks included) in the importer. A computed import might name one, so it refuses too.
330
+ */
331
+ export function testImportProblem(path, text) {
332
+ const { literal, computed, soft } = specifiersOf(path, text);
333
+ const named = [...literal, ...soft].find(spec => NAMES_TEST.test(spec));
334
+ if (named !== undefined)
335
+ return `${path} imports another test file (${named}), which would run its code here`;
336
+ if (computed.length > 0)
337
+ return `${path} imports a path it computes, which might name another test file`;
338
+ return null;
339
+ }
package/dist/tidy.js CHANGED
@@ -478,7 +478,7 @@ export function familyRepoOf(store, refs) {
478
478
  }
479
479
  /** "Archived by tidy: <reason>" or "Archived by <person>: <reason>": the one sentence every surface shows. */
480
480
  export function archivedWords(archived) {
481
- return `Archived by ${archived.byKind === "tidy" ? "tidy" : archived.by}: ${archived.reason}`;
481
+ return `${archived.byKind === "tidy" ? "Archived automatically" : `Archived by ${archived.by}`}: ${archived.reason}`;
482
482
  }
483
483
  /** The release journals beside the store's own database file (none for an in-memory one). */
484
484
  export function releaseFactsOf(store) {
@@ -24,7 +24,7 @@ export function updateStepsHtml(j, running, options = {}) {
24
24
  const items = UPDATE_STEPS.map((step, i) => {
25
25
  const state = j.phase === "complete" || i < last ? "done" : i > last ? "waiting" : failed ? "failed" : finished ? "waiting" : "now";
26
26
  // Only where it adds something: which work it waits for, or what went wrong.
27
- const detail = state === "now" && step === "draining" && j.waiting ? html `${j.waiting.on}.${j.waiting.action ? html ` If nothing of it is running, run <code class="k-mono">${j.waiting.action}</code>.` : ""}`
27
+ const detail = state === "now" && step === "draining" && j.waiting ? html `${j.waiting.on}.${j.waiting.action ? html ` To have Toolroll check again, run <code class="k-mono">${j.waiting.action}</code>.` : ""}`
28
28
  : (state === "now" && step === "draining") || (state === "failed" && j.phase === "rolling-back") ? html `${j.detail}` : "";
29
29
  return settingsRecord({ title: STEP_WORDS[step], sub: detail, when: STEP_CHIP[state], attrs: { "data-step": step, "data-state": state } });
30
30
  });
@@ -43,7 +43,7 @@ function outcomeHtml(j) {
43
43
  // Stopped on a finished run it can't show has ended: what is in the way and the one command, nothing more.
44
44
  if (j.phase === "refused" && j.waiting) {
45
45
  return settingsSection({ id: "update-outcome", title: `Update to ${j.to.version} stopped: run #${j.waiting.run} is in the way`,
46
- body: html `<div data-update-outcome="stopped">${infoLine(`${j.waiting.on}. New work resumed.`)}${j.waiting.action ? note(html `If nothing of it is running, run <code class="k-mono">${j.waiting.action}</code>, then update again.`) : note("Update again once it has stopped.")}</div>` });
46
+ body: html `<div data-update-outcome="stopped">${infoLine(`${j.waiting.on}. New work resumed.`)}${j.waiting.action ? note(html `To have Toolroll check again, run <code class="k-mono">${j.waiting.action}</code>, then update again.`) : note("Update again once it has stopped.")}</div>` });
47
47
  }
48
48
  const title = j.phase === "refused" ? `Didn't update to ${j.to.version}` : j.phase === "restored" ? `Update to ${j.to.version} didn't finish` : "The update needs attention";
49
49
  return settingsSection({ id: "update-outcome", title, state: html ` ${chip("Failed", "failed")}`,
@@ -39,7 +39,10 @@ import { installLaunchdService, launchdPlist, stopLaunchdService, writeFileDurab
39
39
  import { LAPTOP_LABEL_DARWIN, LAPTOP_LABEL_LINUX, unitFlag } from "./laptop-service.js";
40
40
  import { processGone, readRunnerProcessRecord, realProcessProbe, recordStandsFor, RUNNER_PROCESS_DIR } from "./runner-process.js";
41
41
  import { NAME } from "./names.js";
42
- import { orphanKey, updateSafeSchema, V117_RENAMED_COLUMNS, V117_RENAMED_TABLES, V118_MERGED_TABLES, V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS, V143_ADDED_COLUMNS, V147_ADDED_COLUMNS, V148_ADDED_COLUMNS } from "./store.js";
42
+ import { V151_ADDED_COLUMNS } from "./storage.js";
43
+ import { orphanKey, updateSafeSchema, V117_RENAMED_COLUMNS, V117_RENAMED_TABLES, V118_MERGED_TABLES, V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS, V143_ADDED_COLUMNS, V147_ADDED_COLUMNS, V148_ADDED_COLUMNS, V150_ADDED_COLUMNS } from "./store.js";
44
+ import { V152_ADDED_COLUMNS } from "./code-map-store.js";
45
+ import { V153_ADDED_COLUMNS } from "./plain-schema.js";
43
46
  import { isNewer, REGISTRY } from "./releases.js";
44
47
  import { markNeverIndex } from "./never-index.js";
45
48
  import { readRuntimeUpdateJournal, RUNTIME_PHASES, RUNTIME_UPDATE_STEPS, stagedStartedAt, updaterStartingOf } from "./contracts/update-journal.js";
@@ -379,12 +382,16 @@ function addedColumns(...versions) {
379
382
  return rules;
380
383
  }
381
384
  /** Rules on tables that later gained declared columns (addedColumns merges into these instead of replacing them). */
382
- const EARLIER_RULES = { flow_trigger: { fromRoutines: "trigger" } };
385
+ const EARLIER_RULES = { flow_trigger: { fromRoutines: "trigger" },
386
+ // v114: a finished run whose process witnesses have all exited keeps one summary row in place of them (v150 adds
387
+ // their machine id).
388
+ run_process: { compacted: { into: "run_process_summary", sum: "witnesses" } } };
383
389
  const HISTORY_RULES = {
384
390
  ...Object.fromEntries(Object.entries(CHAT_MOVES).map(([table, moved]) => [table, { moved }])),
385
391
  action_ledger: { appendOnly: true }, ledger_seal: { appendOnly: true }, ledger_checkpoint: { appendOnly: true },
386
- // v114: a finished run whose process witnesses have all exited keeps one summary row in place of them.
387
- run_process: { compacted: { into: "run_process_summary", sum: "witnesses" } }, run_process_summary: { summarizes: "run_process" },
392
+ // v114: a finished run whose process witnesses have all exited keeps one summary row in place of them (the
393
+ // run_process rule is in EARLIER_RULES, under its v150 column).
394
+ run_process: EARLIER_RULES["run_process"], run_process_summary: { summarizes: "run_process" },
388
395
  // v114: single-destination delivery columns may be dropped only once their values are legacy receipts.
389
396
  notification: { dropped: ["attempts", "last_attempt_at", "last_error", "delivered_at", "receipt", "claim_owner", "claim_expires_at"], carried: { into: "notification_delivery", destination: LEGACY_DESTINATION } },
390
397
  notification_delivery: { receives: LEGACY_DESTINATION },
@@ -414,7 +421,13 @@ const HISTORY_RULES = {
414
421
  // v147: an account's password flag reads set (1), a browser session reads full with no local binding or key, and the
415
422
  // console record reads no key epoch.
416
423
  // v148: a run's execution class and a revision's origin read none (NULL), and a project's suggestion switch reads on.
417
- ...Object.fromEntries(Object.entries(addedColumns(V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS, V143_ADDED_COLUMNS, V147_ADDED_COLUMNS, V148_ADDED_COLUMNS))
424
+ // v149 adds tables only (the learning-loops records): no saved row changes, so no rule is needed for it.
425
+ // v150: a saved process witness reads no machine id (NULL), and is matched by host name as before.
426
+ // v151: a saved cleanup setting's idle limit reads none (NULL): the default.
427
+ // v152: a saved project's code map reads on, with no enrollment yet; the code map's tables are new.
428
+ // v153: a saved task reads no fast start and a saved project no fast-start setting (NULL); its tables are new.
429
+ // v154 adds a table only (check_stamp): no saved row changes, so no rule is needed for it.
430
+ ...Object.fromEntries(Object.entries(addedColumns(V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS, V143_ADDED_COLUMNS, V147_ADDED_COLUMNS, V148_ADDED_COLUMNS, V150_ADDED_COLUMNS, V151_ADDED_COLUMNS, V152_ADDED_COLUMNS, V153_ADDED_COLUMNS))
418
431
  .map(([table, rule]) => [table, { ...EARLIER_RULES[table], ...rule }])),
419
432
  };
420
433
  /** The tables a migration may remove whole (v115's removed features). */
@@ -42,6 +42,8 @@ export type TuiTask = {
42
42
  /** A finished result that can be marked Complete or sent back with a revision. */
43
43
  readonly hasResult: boolean;
44
44
  readonly decisions: readonly TuiDecision[];
45
+ /** "Can I merge?" (merge-checklist.ts, `toolroll task ready`'s lines): the answer, then each check. Empty: no result. */
46
+ readonly ready?: readonly string[];
45
47
  };
46
48
  /** The welcome's steps, as lines (welcome-facts.ts). */
47
49
  export type TuiSetup = {
@@ -56,11 +58,19 @@ export type TuiSnapshot = {
56
58
  readonly projects: readonly string[];
57
59
  /** Whether the local owner has set a password (decides where a refusal leads). */
58
60
  readonly passwordSet: boolean;
61
+ /** One line about flaky tests and project memory waiting, and the commands that show them; null when there's nothing. */
62
+ readonly learning?: string | null;
59
63
  };
60
64
  export type TuiAct = {
61
65
  readonly kind: "new";
62
66
  readonly text: string;
63
67
  readonly repo: string | null;
68
+ }
69
+ /** Build: the terms the person just read (fast-start.ts). */
70
+ | {
71
+ readonly kind: "build";
72
+ readonly preview: string;
73
+ readonly digest: string;
64
74
  } | {
65
75
  readonly kind: "approve";
66
76
  readonly id: string;
@@ -76,11 +86,23 @@ export type TuiAct = {
76
86
  } | {
77
87
  readonly kind: "complete";
78
88
  readonly id: string;
89
+ } | {
90
+ readonly kind: "retry";
91
+ readonly id: string;
79
92
  } | {
80
93
  readonly kind: "answer";
81
94
  readonly decision: number;
82
95
  readonly option: string;
83
96
  };
97
+ /** What Build would approve: the same lines every approval surface reads. */
98
+ export type TuiFastTerms = {
99
+ readonly preview: string;
100
+ readonly digest: string;
101
+ readonly terms: readonly {
102
+ readonly label: string;
103
+ readonly words: string;
104
+ }[];
105
+ };
84
106
  /** A write's answer: its words. `needsPassword`: refused because the local owner may not do it. */
85
107
  export type TuiActResult = {
86
108
  readonly ok: true;
@@ -100,6 +122,8 @@ export interface TuiSource {
100
122
  /** A counter that moves when the plane's work changes (the store's wake sequence). */
101
123
  changed(): number;
102
124
  act(act: TuiAct): Promise<TuiActResult>;
125
+ /** A small task's exact terms, or null when it takes the ordinary road (New task then files it). */
126
+ fastTerms(text: string, repo: string | null): Promise<TuiFastTerms | null>;
103
127
  /** The welcome's steps, or null once setup is done. */
104
128
  setup(): Promise<TuiSetup | null>;
105
129
  }