toolroll 0.9.93 → 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 (328) hide show
  1. package/CHANGELOG.md +68 -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-ui.js +1 -1
  11. package/dist/assignment.js +5 -0
  12. package/dist/boot-identity.d.ts +2 -1
  13. package/dist/boot-identity.js +6 -4
  14. package/dist/bounded-spawn.d.ts +57 -0
  15. package/dist/bounded-spawn.js +220 -0
  16. package/dist/browser/terminal.js +42 -42
  17. package/dist/browser/workspace.css +1 -1
  18. package/dist/browser/workspace.js +32 -32
  19. package/dist/browser-shell.d.ts +1 -1
  20. package/dist/browser-shell.js +2 -2
  21. package/dist/browser-workspace.d.ts +45 -2
  22. package/dist/browser-workspace.js +3 -3
  23. package/dist/builder-restart.d.ts +21 -0
  24. package/dist/builder-restart.js +50 -0
  25. package/dist/builder.d.ts +4 -0
  26. package/dist/builder.js +53 -24
  27. package/dist/chat-acceptance.js +1 -1
  28. package/dist/chat-actions.d.ts +12 -2
  29. package/dist/chat-actions.js +25 -2
  30. package/dist/chat-channel.js +11 -0
  31. package/dist/chat-decide.d.ts +7 -2
  32. package/dist/chat-decide.js +37 -9
  33. package/dist/chat-quiet.js +3 -3
  34. package/dist/chat-voice.d.ts +1 -1
  35. package/dist/chat-voice.js +2 -2
  36. package/dist/check-facts.d.ts +3 -1
  37. package/dist/check-facts.js +57 -11
  38. package/dist/check-failure.d.ts +22 -3
  39. package/dist/check-failure.js +57 -13
  40. package/dist/check-levels-ui.d.ts +4 -0
  41. package/dist/check-levels-ui.js +10 -1
  42. package/dist/check-load.d.ts +85 -0
  43. package/dist/check-load.js +147 -0
  44. package/dist/check-only-standing.d.ts +36 -0
  45. package/dist/check-only-standing.js +39 -0
  46. package/dist/check-only.d.ts +15 -0
  47. package/dist/check-only.js +99 -0
  48. package/dist/check-report.d.ts +2 -0
  49. package/dist/check-report.js +1 -1
  50. package/dist/check-stamp.d.ts +46 -0
  51. package/dist/check-stamp.js +93 -0
  52. package/dist/checkout-cleanup.d.ts +36 -5
  53. package/dist/checkout-cleanup.js +166 -14
  54. package/dist/cli-help.d.ts +4 -1
  55. package/dist/cli-help.js +47 -30
  56. package/dist/cli.js +11 -1
  57. package/dist/code-extract.d.ts +44 -0
  58. package/dist/code-extract.js +88 -0
  59. package/dist/code-map-acts.d.ts +24 -0
  60. package/dist/code-map-acts.js +53 -0
  61. package/dist/code-map-admission.d.ts +52 -0
  62. package/dist/code-map-admission.js +295 -0
  63. package/dist/code-map-build.d.ts +63 -0
  64. package/dist/code-map-build.js +415 -0
  65. package/dist/code-map-cli.d.ts +153 -0
  66. package/dist/code-map-cli.js +103 -0
  67. package/dist/code-map-main.d.ts +1 -0
  68. package/dist/code-map-main.js +48 -0
  69. package/dist/code-map-read.d.ts +106 -0
  70. package/dist/code-map-read.js +171 -0
  71. package/dist/code-map-report.d.ts +37 -0
  72. package/dist/code-map-report.js +95 -0
  73. package/dist/code-map-shape.d.ts +172 -0
  74. package/dist/code-map-shape.js +327 -0
  75. package/dist/code-map-store.d.ts +136 -0
  76. package/dist/code-map-store.js +233 -0
  77. package/dist/coding-provider.d.ts +2 -0
  78. package/dist/coding-provider.js +3 -2
  79. package/dist/coding-update.js +3 -2
  80. package/dist/coding-workspace.js +4 -4
  81. package/dist/console-chrome.js +2 -2
  82. package/dist/containment.d.ts +5 -0
  83. package/dist/containment.js +14 -1
  84. package/dist/contracts/chat-actions.d.ts +9 -0
  85. package/dist/contracts/chat-actions.js +2 -0
  86. package/dist/contracts/cli.d.ts +52 -0
  87. package/dist/contracts/cli.js +4 -0
  88. package/dist/contracts/console-api.d.ts +10 -3
  89. package/dist/contracts/console-api.js +10 -3
  90. package/dist/contracts/gateway-tools.d.ts +17 -0
  91. package/dist/contracts/gateway-tools.js +3 -1
  92. package/dist/contracts/lead-tools.d.ts +61 -0
  93. package/dist/contracts/lead-tools.js +10 -1
  94. package/dist/contracts/project-knowledge.d.ts +38 -5
  95. package/dist/contracts/project-knowledge.js +11 -5
  96. package/dist/contracts/project-memory.d.ts +3 -1
  97. package/dist/contracts/project-memory.js +3 -2
  98. package/dist/contracts/storage.d.ts +14 -0
  99. package/dist/contracts/storage.js +3 -1
  100. package/dist/contracts/store-json.d.ts +2 -0
  101. package/dist/contracts/youiyoux-request.d.ts +1 -1
  102. package/dist/contracts/youiyoux-request.js +1 -1
  103. package/dist/desktop-access.js +6 -2
  104. package/dist/dispatch.js +18 -15
  105. package/dist/dispose.js +8 -2
  106. package/dist/easy-start.js +2 -3
  107. package/dist/evals.js +2 -1
  108. package/dist/fast-start.d.ts +136 -0
  109. package/dist/fast-start.js +431 -0
  110. package/dist/fast-terms.d.ts +9 -0
  111. package/dist/fast-terms.js +23 -0
  112. package/dist/fenced-check-coverage.d.ts +62 -0
  113. package/dist/fenced-check-coverage.js +54 -0
  114. package/dist/first-run.js +1 -1
  115. package/dist/flaky-ci.d.ts +31 -0
  116. package/dist/flaky-ci.js +149 -0
  117. package/dist/flaky-cli.d.ts +60 -0
  118. package/dist/flaky-cli.js +60 -0
  119. package/dist/flaky.d.ts +75 -9
  120. package/dist/flaky.js +255 -0
  121. package/dist/flow-card-task.js +3 -0
  122. package/dist/flow-triggers.d.ts +1 -1
  123. package/dist/flow-triggers.js +1 -1
  124. package/dist/github-import.d.ts +62 -0
  125. package/dist/github-import.js +146 -0
  126. package/dist/guides.js +15 -10
  127. package/dist/invoke.d.ts +5 -1
  128. package/dist/invoke.js +49 -14
  129. package/dist/job-object-helper.ps1 +10 -3
  130. package/dist/keymap.d.ts +41 -0
  131. package/dist/keymap.js +51 -0
  132. package/dist/knowledge-cli.js +1 -1
  133. package/dist/knowledge-ui.d.ts +21 -4
  134. package/dist/knowledge-ui.js +75 -7
  135. package/dist/laptop-background.d.ts +16 -0
  136. package/dist/laptop-background.js +108 -2
  137. package/dist/lead-context.js +1 -1
  138. package/dist/lead-contract.d.ts +1 -1
  139. package/dist/lead-contract.js +2 -2
  140. package/dist/lead-progress.js +3 -0
  141. package/dist/lead-status.d.ts +2 -0
  142. package/dist/lead-status.js +6 -2
  143. package/dist/lead-tools.js +51 -2
  144. package/dist/lead.js +1 -1
  145. package/dist/lesson-notes.d.ts +81 -0
  146. package/dist/lesson-notes.js +336 -0
  147. package/dist/limits-ui.js +1 -1
  148. package/dist/local-owner.d.ts +1 -1
  149. package/dist/local-owner.js +11 -0
  150. package/dist/machine-check.d.ts +16 -0
  151. package/dist/machine-check.js +26 -0
  152. package/dist/machine-identity.d.ts +75 -0
  153. package/dist/machine-identity.js +170 -0
  154. package/dist/mcp-core.js +11 -1
  155. package/dist/memory-cli.d.ts +300 -1
  156. package/dist/memory-cli.js +62 -9
  157. package/dist/memory-pass.d.ts +16 -0
  158. package/dist/memory-pass.js +67 -3
  159. package/dist/memory-tidy.d.ts +181 -0
  160. package/dist/memory-tidy.js +523 -0
  161. package/dist/memory-use.d.ts +35 -0
  162. package/dist/memory-use.js +109 -0
  163. package/dist/merge-checklist.d.ts +55 -0
  164. package/dist/merge-checklist.js +182 -0
  165. package/dist/needs-you.d.ts +27 -8
  166. package/dist/needs-you.js +96 -39
  167. package/dist/nightly-github.d.ts +2 -0
  168. package/dist/nightly-github.js +4 -4
  169. package/dist/operate-remote-arguments.js +6 -1
  170. package/dist/operate-remote.js +3 -1
  171. package/dist/operate.d.ts +6 -1
  172. package/dist/operate.js +402 -52
  173. package/dist/phase-routing.js +2 -2
  174. package/dist/plain-schema.d.ts +23 -0
  175. package/dist/plain-schema.js +94 -0
  176. package/dist/plain-words.d.ts +20 -0
  177. package/dist/plain-words.js +66 -0
  178. package/dist/planner.js +2 -1
  179. package/dist/process-liveness.d.ts +2 -0
  180. package/dist/process-liveness.js +61 -17
  181. package/dist/project-delete-ui.d.ts +7 -2
  182. package/dist/project-delete-ui.js +12 -1
  183. package/dist/project-delete.js +8 -1
  184. package/dist/project-knowledge.d.ts +18 -11
  185. package/dist/project-knowledge.js +39 -9
  186. package/dist/project-learning.d.ts +2 -1
  187. package/dist/project-learning.js +15 -45
  188. package/dist/project-memory.d.ts +5 -0
  189. package/dist/project-memory.js +22 -2
  190. package/dist/proof.js +30 -5
  191. package/dist/provider-auth.js +1 -1
  192. package/dist/publish.js +9 -1
  193. package/dist/pull-request-flow.js +5 -0
  194. package/dist/push.d.ts +18 -2
  195. package/dist/push.js +40 -16
  196. package/dist/release-adapters.d.ts +9 -0
  197. package/dist/release-adapters.js +76 -0
  198. package/dist/release-auto.js +13 -13
  199. package/dist/release-card.d.ts +1 -2
  200. package/dist/release-card.js +11 -0
  201. package/dist/release-carry.d.ts +7 -2
  202. package/dist/release-carry.js +39 -9
  203. package/dist/release-cli.d.ts +4 -0
  204. package/dist/release-cli.js +29 -22
  205. package/dist/release-coverage.js +1 -1
  206. package/dist/release-next.d.ts +48 -0
  207. package/dist/release-next.js +161 -0
  208. package/dist/release.d.ts +20 -1
  209. package/dist/release.js +76 -8
  210. package/dist/remote-destination.d.ts +36 -0
  211. package/dist/remote-destination.js +217 -0
  212. package/dist/remote-main.d.ts +48 -0
  213. package/dist/remote-main.js +84 -5
  214. package/dist/repository-context.d.ts +3 -2
  215. package/dist/repository-context.js +112 -70
  216. package/dist/restart-certification.d.ts +1 -0
  217. package/dist/restart-certification.js +8 -6
  218. package/dist/result-acts.d.ts +27 -19
  219. package/dist/result-acts.js +36 -25
  220. package/dist/result-follow-ups.d.ts +2 -2
  221. package/dist/result-follow-ups.js +18 -4
  222. package/dist/result-review.d.ts +1 -1
  223. package/dist/route-change-check.d.ts +4 -0
  224. package/dist/route-change-check.js +13 -0
  225. package/dist/route-change.d.ts +22 -0
  226. package/dist/route-change.js +89 -0
  227. package/dist/route-hooks.js +3 -2
  228. package/dist/route-results.d.ts +6 -1
  229. package/dist/route-results.js +14 -5
  230. package/dist/run-closeout.d.ts +1 -1
  231. package/dist/run-closeout.js +7 -5
  232. package/dist/runner-process.js +3 -2
  233. package/dist/runner.js +2 -1
  234. package/dist/scope.d.ts +6 -1
  235. package/dist/scope.js +22 -9
  236. package/dist/serve.d.ts +1 -1
  237. package/dist/serve.js +24 -18
  238. package/dist/server/chat.js +14 -12
  239. package/dist/server/chrome.d.ts +1 -1
  240. package/dist/server/chrome.js +26 -35
  241. package/dist/server/console.css +23 -0
  242. package/dist/server/guards.js +3 -0
  243. package/dist/server/home.d.ts +6 -0
  244. package/dist/server/home.js +21 -13
  245. package/dist/server/http.js +1 -1
  246. package/dist/server/kit.js +1 -1
  247. package/dist/server/merge-ready-view.d.ts +10 -0
  248. package/dist/server/merge-ready-view.js +30 -0
  249. package/dist/server/pages-assets.d.ts +1 -0
  250. package/dist/server/pages-assets.js +8 -5
  251. package/dist/server/pages.js +103 -82
  252. package/dist/server/render-chat.js +2 -2
  253. package/dist/server/render-code-map.d.ts +30 -0
  254. package/dist/server/render-code-map.js +137 -0
  255. package/dist/server/render-flaky.d.ts +20 -0
  256. package/dist/server/render-flaky.js +59 -0
  257. package/dist/server/render-history.js +1 -1
  258. package/dist/server/render-new-task.d.ts +26 -1
  259. package/dist/server/render-new-task.js +30 -5
  260. package/dist/server/render-pages.d.ts +1 -175
  261. package/dist/server/render-pages.js +38 -313
  262. package/dist/server/render-projects.d.ts +2 -1
  263. package/dist/server/render-projects.js +9 -7
  264. package/dist/server/render-release.d.ts +2 -0
  265. package/dist/server/render-release.js +3 -3
  266. package/dist/server/render-results.d.ts +2 -0
  267. package/dist/server/render-results.js +67 -67
  268. package/dist/server/render-tasks.d.ts +13 -2
  269. package/dist/server/render-tasks.js +153 -136
  270. package/dist/server/retired-routes.d.ts +17 -0
  271. package/dist/server/retired-routes.js +53 -0
  272. package/dist/server/route-table.d.ts +3 -0
  273. package/dist/server/route-table.js +45 -12
  274. package/dist/server/settings.js +129 -22
  275. package/dist/server/task-views.js +61 -26
  276. package/dist/server/tasks.d.ts +2 -6
  277. package/dist/server/tasks.js +174 -321
  278. package/dist/server/welcome.js +1 -1
  279. package/dist/settings-groups-ui.js +1 -1
  280. package/dist/setup-guide.d.ts +2 -2
  281. package/dist/stop-proof.js +6 -4
  282. package/dist/storage-sweep-process.js +1 -1
  283. package/dist/storage-sweep.d.ts +5 -3
  284. package/dist/storage-sweep.js +30 -9
  285. package/dist/storage-ui.js +28 -17
  286. package/dist/storage.d.ts +27 -2
  287. package/dist/storage.js +39 -2
  288. package/dist/store.d.ts +80 -5
  289. package/dist/store.js +296 -87
  290. package/dist/surface.js +45 -4
  291. package/dist/sweep-quarantine.d.ts +1 -0
  292. package/dist/sweep-quarantine.js +6 -4
  293. package/dist/tamper-scan.d.ts +4 -0
  294. package/dist/tamper-scan.js +90 -0
  295. package/dist/task-aliases.d.ts +5 -0
  296. package/dist/task-aliases.js +9 -0
  297. package/dist/task-control.js +4 -1
  298. package/dist/task-outcome-cli.d.ts +2 -0
  299. package/dist/task-outcome-cli.js +17 -17
  300. package/dist/task-sizing.d.ts +5 -0
  301. package/dist/task-sizing.js +2 -0
  302. package/dist/task-source.d.ts +7 -0
  303. package/dist/task-source.js +14 -0
  304. package/dist/task-status.d.ts +9 -0
  305. package/dist/task-status.js +40 -49
  306. package/dist/teardown-git.d.ts +16 -0
  307. package/dist/teardown-git.js +63 -5
  308. package/dist/telegram-progress.js +5 -3
  309. package/dist/telegram-status.js +6 -4
  310. package/dist/test-infra.d.ts +8 -0
  311. package/dist/test-infra.js +18 -0
  312. package/dist/tidy.js +1 -1
  313. package/dist/toolroll-update.js +17 -5
  314. package/dist/tui-contract.d.ts +24 -0
  315. package/dist/tui-source.js +67 -8
  316. package/dist/tui.js +51 -51
  317. package/dist/vanished-retry.d.ts +30 -0
  318. package/dist/vanished-retry.js +174 -0
  319. package/dist/work-index.d.ts +2 -0
  320. package/dist/work-index.js +76 -62
  321. package/dist/work-summary.js +2 -0
  322. package/dist/workspace-ui.d.ts +4 -2
  323. package/dist/workspace-ui.js +28 -22
  324. package/dist/worktree.d.ts +12 -3
  325. package/dist/worktree.js +68 -13
  326. package/package.json +1 -1
  327. package/dist/server/render-next.d.ts +0 -63
  328. package/dist/server/render-next.js +0 -109
package/dist/operate.js CHANGED
@@ -21,7 +21,7 @@ import { parseProtectedPaths } from "./approval-policy.js";
21
21
  import { rulesSummary } from "./approval-rules-ui.js";
22
22
  import { evidencePack, exportDay, ledgerExportChunks, standaloneEvidenceHtml } from "./evidence-pack.js";
23
23
  import { matchesOutsideCheckpoint } from "./ledger-chain.js";
24
- import { CLEANUP_CHOICES, bytesWords, cleanupWords, diskBytes, parseCleanup, storageReport } from "./storage.js";
24
+ import { CLEANUP_CHOICES, bytesWords, cleanupWords, diskBytes, idleCleanupWords, idleWords, parseCleanup, parseIdle, storageReport } from "./storage.js";
25
25
  import { removeStaleTestTemp, tempRoots, testTempFolders } from "./test-temp.js";
26
26
  import { storageSweepSupervisor } from "./storage-sweep-process.js";
27
27
  import { idleStorageSweep, sweepSchedule, sweepScheduleWords } from "./storage-sweep-schedule.js";
@@ -67,6 +67,9 @@ import { releaseCheckPurposeFor, releaseTermsFor } from "./release-carry.js";
67
67
  import { approveReleaseCard, journalMatchesPlane, releaseApprovalCard, releaseCardLines, releaseTaskOf, waitingJournals } from "./release-approval.js";
68
68
  import { journalFile } from "./release.js";
69
69
  import { refreshTrustedMains, settleReleaseChecks } from "./nightly-check.js";
70
+ import { refreshCodeMaps } from "./code-map-build.js";
71
+ import { sweepDeliveries } from "./code-map-admission.js";
72
+ import { runMapCommand } from "./code-map-cli.js";
70
73
  import { githubNightlyRepos, readGithubNightlies } from "./nightly-github.js";
71
74
  import { exec as releaseExec, githubReads } from "./release-adapters.js";
72
75
  import { releaseTools } from "./release-git.js";
@@ -76,11 +79,17 @@ import { followTeams } from "./teams.js";
76
79
  import { followSlack } from "./slack.js";
77
80
  import { validateScopeText } from "./task-text.js";
78
81
  import { runMemoryCommand } from "./memory-cli.js";
82
+ import { runFlakyCommand } from "./flaky-cli.js";
83
+ import { backfillMemoryUse } from "./memory-use.js";
84
+ import { scheduledTidy } from "./memory-tidy.js";
85
+ import { CI_READ_EVERY_MS, ciCursorKey, ciRepositoriesOf, observeCiAttempts } from "./flaky-ci.js";
79
86
  import { runKnowledgeApply, runKnowledgeCommand } from "./knowledge-cli.js";
80
87
  import { FLOWS_VALUE_FLAGS, runFlowsCommand } from "./flows-cli.js";
81
88
  import { runAssignmentCommand } from "./assignment-adapters.js";
82
89
  import { applyProjectProfile, runProjectCommand } from "./project-cli.js";
83
90
  import { pullRequestLines, runTaskMergeCommand, runTaskOutcomeCommand } from "./task-outcome-cli.js";
91
+ import { withTaskAlias } from "./task-aliases.js";
92
+ import { checklistLines, mergeChecklistOf } from "./merge-checklist.js";
84
93
  import { taskReviewBrief, renderReviewBrief } from "./task-review-brief.js";
85
94
  /**
86
95
  * The commands that actually move work: authoring tasks, and the claim loop.
@@ -112,10 +121,10 @@ import { homedir, hostname } from "node:os";
112
121
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
113
122
  import { releaseStalledUpdate, waitingUpdate } from "./toolroll-update.js";
114
123
  import { dur } from "./design/kit.js";
115
- import { openStore, openStoreNoMigrate, initLocalMode, enrollOwnerPassword, installationCreatedBy, laptopPortOf, recordLaptopPort, localModeOf, localOwnerBinding, archiveTaskFamily, restoreTaskFamily, shipChecksForTask, elapsedSeconds, CONCURRENT_WRITER_WAIT_MS, isDatabaseBusy, databasePath, BUILT_IN, parseCapabilityKey, verifiedAuthor, isDigestTime, predecessorWatches, RESULT_SCREENSHOTS, lifecycleOf, reportLifecycle, beatLifecycle, } from "./store.js";
124
+ import { checkoutIdle, setCheckoutIdle, openStore, openStoreNoMigrate, initLocalMode, enrollOwnerPassword, installationCreatedBy, laptopPortOf, recordLaptopPort, localModeOf, localOwnerBinding, archiveTaskFamily, restoreTaskFamily, shipChecksForTask, elapsedSeconds, CONCURRENT_WRITER_WAIT_MS, isDatabaseBusy, databasePath, BUILT_IN, parseCapabilityKey, verifiedAuthor, isDigestTime, predecessorWatches, RESULT_SCREENSHOTS, lifecycleOf, reportLifecycle, beatLifecycle, } from "./store.js";
116
125
  import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
117
126
  import { claimInstance, clearInstance, openInstanceSocket } from "./console-instance.js";
118
- import { backgroundOff, backgroundOn, backgroundShow, laptopStatus, laptopStop } from "./laptop-background.js";
127
+ import { backgroundOff, backgroundOn, backgroundShow, laptopStatus, laptopStop, restartOwnBuilder } from "./laptop-background.js";
119
128
  import { authorizePlanUnderMode } from "./plan-auto.js";
120
129
  import { ghDispatchAdapter, mirrorTaskId, syncPass } from "./sync.js";
121
130
  import { LIVE_FILE_BOUND, LIVE_RETAIN_MS, sweepLiveLogs } from "./live.js";
@@ -185,7 +194,7 @@ function planContractLines(store, evidenceRoot, taskId) {
185
194
  ...describeContractChanges(record.changes).map(line => ` ${line}`),
186
195
  ];
187
196
  }
188
- import { verdictWords as proofVerdictWords, matrixWords, semanticCoverage, coverageWords } from "./proof.js";
197
+ import { verdictWords as proofVerdictWords, matrixWords, plainReasonWords, semanticCoverage, coverageWords } from "./proof.js";
189
198
  import { probeRepo } from "./probe.js";
190
199
  import { diagnoseTaskDispatch, diagnosisIsDispatchable, scopeApprovedForDispatch, } from "./dispatch.js";
191
200
  import { createDecisionServer } from "./serve.js";
@@ -237,6 +246,17 @@ import { resolvePhaseAgent, resolveScopeProfile, resolveRouteCandidates, routeOf
237
246
  import { applyModelChoices, stateDigest } from "./model-choices.js";
238
247
  import { isTaskSize, legOf, projectRoute, routeDigestOf, routeWords, TASK_SIZES, PHASES as ROUTE_PHASES } from "./phase-routing.js";
239
248
  import { observeProviderReadiness, reportProviderReadinessAuthed } from "./runner.js";
249
+ import { retryVanishedWhenStopped } from "./vanished-retry.js";
250
+ import { requestMissingRechecks } from "./check-only.js";
251
+ import { recordBuilderWish } from "./builder-restart.js";
252
+ import { expireFastStarts, fastRefusalWords, fastStartBuild, keepGrownChange, prepareFastStart } from "./fast-start.js";
253
+ import { fastTermsLines } from "./fast-terms.js";
254
+ import { resumeAllowedReleases } from "./release-next.js";
255
+ import { fileGithubItem, readGithubItem } from "./github-import.js";
256
+ import { runRelease } from "./release.js";
257
+ import { releaseAdapters } from "./release-adapters.js";
258
+ import { routeChangeOf, routeLegacyPlans } from "./route-change.js";
259
+ import { passwordlessOn } from "./passwordless.js";
240
260
  import { parseDemoUrl, projectDemoUrl, saveProjectDemo } from "./project-demo.js";
241
261
  import { effectiveConcurrency, maySlotTake, parseProjectConcurrency, PROJECT_CONCURRENCY_DEFAULT, ProjectPasses, projectConcurrency, saveProjectConcurrency, savedProjectConcurrency } from "./project-concurrency.js";
242
262
  import { activeWebhookTargets, webhookPass, LEGACY_WEBHOOK_WARNING, loadWebhookTargets, effectivePrimary, isMessagingChannel, loadConsoleUrl, phoneOrigin, readUpConsoleUrl, saveConsoleUrl, savePrimary, UP_CONSOLE_FILE } from "./webhooks.js";
@@ -320,10 +340,16 @@ export const OPERATE_HELP = `toolroll — operating the queue
320
340
  the project's own demo or dev server, the one non-public address a
321
341
  report's scout may screenshot
322
342
  toolroll task evidence <id> the task's evidence pack as JSON (--html for a printable page; --out <file>)
343
+ toolroll map [<project>] [--json] a project's code map, made by Toolroll from main: areas and how they use each other
344
+ toolroll map area <id> | file <path> | search <words> [--cursor <c>]
345
+ one area, one file, or files and exported names; --cursor reads the next page of the same version
346
+ toolroll map refresh ask for a check of main now (an approver)
323
347
  toolroll storage where the disk goes: database, build checkouts, releases, evidence, leftover test temp folders
324
348
  toolroll storage clean [--yes] preview removing finished tasks' clean checkouts (their branches stay) and test temp folders older than a day; --yes removes them
325
349
  toolroll storage clean --landed [--yes] preview work that landed in main over a week ago: marks its results complete, removes their clean checkouts and local branches; --yes does it (instance operator)
326
- toolroll storage cleanup <when> remove a finished task's clean checkout: finished|2d|7d|never (instance operator)
350
+ toolroll storage cleanup <when> remove a finished task's clean folder: finished|2d|7d|never (instance operator)
351
+ toolroll storage cleanup idle <days>
352
+ remove an idle task's clean folder: 7|14|30|never (instance operator)
327
353
  toolroll storage discard <path> --yes throw away a checkout kept for its changes; its branch stays
328
354
  toolroll integrations [--json] which integrations work: Connected, Not set up or Broken, and what to do (--saved: no new checks)
329
355
  toolroll monitoring where the audit stream and traces go, and how each destination is doing
@@ -688,10 +714,10 @@ Exit codes
688
714
  * (surface.ts) is tested for exact equality against them.
689
715
  */
690
716
  export const TASK_ACTIONS = [
691
- "add", "list", "show", "wait", "state", "block", "unblock", "next", "steer", "pause", "continue", "assign",
717
+ "add", "new", "keep", "list", "show", "wait", "state", "block", "unblock", "next", "steer", "pause", "continue", "assign",
692
718
  "reopen", "scope", "approve", "hold", "unhold", "require", "requeue", "regate", "plan",
693
719
  "review", "accept", "repair", "route", "stop", "resume", "complete", "revise", "merge", "ask",
694
- "checks", "add-tests", "archive", "restore", "tidy", "ship",
720
+ "checks", "add-tests", "archive", "restore", "tidy", "ship", "ready",
695
721
  ];
696
722
  export const PUBLISH_ACTIONS = ["setup", "grant", "revoke", "status", "unblock", "rearm", "merge", "refire"];
697
723
  export const CONFIG_ACTIONS = ["show", "set", "clear"];
@@ -706,11 +732,11 @@ export const KEYS_ACTIONS = ["status", "set", "clear", "verify", "auth"];
706
732
  /** Flags whose value is a secret someone types or pastes: taken as the next argument even when it starts with "--". */
707
733
  export const SECRET_VALUE_FLAGS = new Set(["token", "password"]);
708
734
  export const OPERATE_VALUE_FLAGS = new Set([
709
- "key", "db", "runner", "ttl", "state", "on", "reason", "until", "id", "backend",
735
+ "key", "db", "runner", "ttl", "state", "on", "reason", "until", "id", "backend", "build", "from",
710
736
  "allow", "selector", "paths", "credentials", "repo", "token", "capacity",
711
737
  "goal", "not", "touches", "acceptance", "candidate", "by", "digest", "as", "branch", "pool", "base", "model", "turns",
712
738
  "max", "cap", "probe", "kind", "expires", "cmd", "since", "repair-model",
713
- "choose", "note", "max-open-decisions", "name", "days", "publication", "auto-approve", "review-auto", "port", "host", "allow-host",
739
+ "choose", "choice", "claim", "revision", "note", "max-open-decisions", "name", "days", "publication", "auto-approve", "review-auto", "port", "host", "allow-host",
714
740
  "for", "tick-every", "bridge-every", "reconcile-every", "incarnation",
715
741
  "say", "ceiling-usd", "cap-usd",
716
742
  // evals: the cases a diagnostic campaign runs, its k, an attempt's world and what its builds may not read, and the
@@ -754,8 +780,14 @@ export const OPERATE_VALUE_FLAGS = new Set([
754
780
  "read-deny", "mach",
755
781
  // task scope --release <version>:<pr>:<branch>: a release scope's terms (release-once.md).
756
782
  "release",
783
+ // task accept --despite "<reason>": acceptance with a reason, never a completion.
784
+ "despite",
757
785
  ]);
758
786
  export const OPERATE_BOOLEAN_FLAGS = new Set([
787
+ // task accept --finish: the person's Accept, exactly task complete's exact-receipt completion.
788
+ "finish",
789
+ // task new --preview: the terminal UI's own, the exact terms with nothing started.
790
+ "preview",
759
791
  // evals run: a report-only campaign; keep each attempt's world for a look afterwards.
760
792
  "diagnostic", "keep",
761
793
  // task scope --release ... --no-carry: every later commit asks again.
@@ -847,6 +879,9 @@ export function parseOperateArgs(argv, ownValues = new Set()) {
847
879
  }
848
880
  /** Route a native command and return its exit code. Its password source cannot be selected by an HTTP request. */
849
881
  export async function runOperate(command, argv, write, options = {}) {
882
+ // People's names for task commands (task-aliases.ts) are the commands they name, before any rule reads the command.
883
+ if (command === "task")
884
+ argv = withTaskAlias(["task", ...argv]).slice(1);
850
885
  // Each command gets its own remembered-login holder (Passwordless: "cli-login" is this command's, never the next one's).
851
886
  return withPasswordSource(CLI_PASSWORD_SOURCE, () => rememberedLogin.run({ login: null }, () => runOperateLocal(command, argv, write, options)));
852
887
  }
@@ -1062,6 +1097,8 @@ export async function runOperateAs(argv, opts) {
1062
1097
  const source = opts.source === "mcp" ? "mcp" : "api";
1063
1098
  const options = opts.options ?? {};
1064
1099
  const now = options.now ?? new Date();
1100
+ // People's names for task commands are the commands they name, before the remote policy reads the command.
1101
+ argv = [...withTaskAlias(argv)];
1065
1102
  const [root = "", ...rest] = argv;
1066
1103
  const json = rest.includes("--json");
1067
1104
  let invocation = root;
@@ -1305,6 +1342,8 @@ async function dispatch(command, positional, flags, context) {
1305
1342
  const verified = acting === null || lead ? null : authenticateApprover(context.store, acting.name, acting.token, repo === undefined ? null : resolve(repo));
1306
1343
  return runKnowledgeApply(flags, { ...context, now: context.clock(), actor: verified !== null && verified.ok ? acting.name : null });
1307
1344
  }
1345
+ case "map":
1346
+ return mapCommand(positional, flags, context);
1308
1347
  case "models": {
1309
1348
  // A local owner's lowering act (models turn-off / dismiss, through the terminal UI) asks for no credential: the
1310
1349
  // rule door re-checks the owner and its exact act itself.
@@ -1325,10 +1364,13 @@ async function dispatch(command, positional, flags, context) {
1325
1364
  const acting = await askCredentials(flags, context);
1326
1365
  if (memoryWriteVerb(positional) && acting !== null && parseLeadToken(acting.token) !== null)
1327
1366
  return fail(context.write, context.json, `memory ${positional[0]}`, "unauthenticated", MEMORY_WRITE_REFUSAL, EXIT.refused);
1328
- const verified = acting === null ? null : verifyApproverByPassword(context.store, acting.name, acting.token, context.store.knownRepos().filter(repo => context.store.accountCanAccess(acting.name, repo)));
1329
- const who = verified !== null && verified.ok ? verified.who : null;
1367
+ // The laptop's local owner: only the rows LOCAL_ALLOW names reach here, and each writer checks the exact act.
1368
+ const verified = acting === null || currentLocalOwner() !== null ? null : verifyApproverByPassword(context.store, acting.name, acting.token, context.store.knownRepos().filter(repo => context.store.accountCanAccess(acting.name, repo)));
1369
+ const who = currentLocalOwner() !== null ? localReader(context) : verified !== null && verified.ok ? verified.who : null;
1330
1370
  return runMemoryCommand(positional, flags, { store: context.store, write: context.write, json: context.json, now: context.clock(), actor: who?.name ?? null, repos: who?.repos ?? [], configDir: dirname(context.databaseFile), ...(context.evidenceRoot === undefined ? {} : { evidenceRoot: context.evidenceRoot }) });
1331
1371
  }
1372
+ case "flaky":
1373
+ return flakyCommand(positional, flags, context);
1332
1374
  case "claim":
1333
1375
  return claimCommand(positional, flags, context);
1334
1376
  case "heartbeat":
@@ -1764,7 +1806,7 @@ async function runnerCommand(positional, flags, context) {
1764
1806
  // at the mint. A caller flag never grants what the mint did not.
1765
1807
  const acting = await askCredentials(flags, context);
1766
1808
  if (acting === null) {
1767
- return fail(write, json, "runner register", "usage", "`runner register <name> --repo <path> --as <you> --token <t>` — minting runner authority is an operator act", EXIT.usage);
1809
+ return fail(write, json, "runner register", "usage", "`runner register <name> --repo <path> --as <you> --token <t>` — adding a builder is an approver's act", EXIT.usage);
1768
1810
  }
1769
1811
  const authenticated = credentialApprover(store, acting.name, acting.token);
1770
1812
  if (!authenticated.ok)
@@ -1864,7 +1906,7 @@ async function runnerCommand(positional, flags, context) {
1864
1906
  const acting = await askCredentials(flags, context);
1865
1907
  const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
1866
1908
  if (acting === null || !verified?.ok)
1867
- return refuseApprover(context, "runner retire", verified, "`runner retire <name> --as <you> --token <t>` — revoking runner authority is an operator act", name);
1909
+ return refuseApprover(context, "runner retire", verified, "`runner retire <name> --as <you> --token <t>` — retiring a builder is an approver's act", name);
1868
1910
  const retired = store.retireRunner(name, now, mutationFrom(flags, now));
1869
1911
  if (!retired)
1870
1912
  return fail(write, json, "runner retire", "unknown", `no runner \`${name}\``, EXIT.refused);
@@ -1880,7 +1922,7 @@ async function runnerCommand(positional, flags, context) {
1880
1922
  // stated authority, never accumulation nobody can read back.
1881
1923
  const acting = await askCredentials(flags, context);
1882
1924
  if (acting === null) {
1883
- return fail(write, json, "runner bind", "usage", "`runner bind <name> --repo <path> --as <you> --token <t>` — binding repositories is an operator act", EXIT.usage);
1925
+ return fail(write, json, "runner bind", "usage", "`runner bind <name> --repo <path> --as <you> --token <t>` — choosing a builder's projects is an approver's act", EXIT.usage);
1884
1926
  }
1885
1927
  const authenticated = credentialApprover(store, acting.name, acting.token);
1886
1928
  if (!authenticated.ok)
@@ -1960,7 +2002,7 @@ async function coordinatorCommand(positional, flags, context) {
1960
2002
  }
1961
2003
  const acting = await askCredentials(flags, context);
1962
2004
  if (acting === null) {
1963
- return fail(write, json, "coordinator mint", "usage", "`coordinator mint <name> --repo <path> --as <you> --token <t>` — minting filing authority is an operator act", EXIT.usage);
2005
+ return fail(write, json, "coordinator mint", "usage", "`coordinator mint <name> --repo <path> --as <you> --token <t>` — minting your lead's credential is an approver's act", EXIT.usage);
1964
2006
  }
1965
2007
  const authenticated = credentialApprover(store, acting.name, acting.token);
1966
2008
  if (!authenticated.ok)
@@ -2007,7 +2049,7 @@ async function coordinatorCommand(positional, flags, context) {
2007
2049
  const acting = await askCredentials(flags, context);
2008
2050
  const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
2009
2051
  if (acting === null || !verified?.ok)
2010
- return refuseApprover(context, "coordinator revoke", verified, "`coordinator revoke <cid> --as <you> --token <t>` — revoking is an operator act", name);
2052
+ return refuseApprover(context, "coordinator revoke", verified, "`coordinator revoke <cid> --as <you> --token <t>` — revoking is an approver's act", name);
2011
2053
  const revoked = revokeCoordinator(store, name, acting.name, now);
2012
2054
  if (!revoked.ok) {
2013
2055
  const said = revoked.reason === "unknown" ? `no coordinator \`${name}\` — \`coordinator list\` shows cids` : "already revoked";
@@ -3586,6 +3628,12 @@ async function reconcileCommand(flags, context) {
3586
3628
  // asked to confirm a build stopped, so a release waiting on its check moves on by itself.
3587
3629
  await proveFinishedRunsStopped(store, clock);
3588
3630
  store.settleQuiescentStops(clock());
3631
+ // A vanished build whose old process is now proven stopped gets its retry decision (vanished-retry.ts).
3632
+ retryVanishedWhenStopped(store, clock());
3633
+ // A fast start that didn't start in time goes back to the ordinary Approve plan (fast-start.ts).
3634
+ expireFastStarts(store, clock());
3635
+ // A plan from before agent routing gets its route and one Approve (route-change.ts).
3636
+ routeLegacyPlans(store, clock());
3589
3637
  // An update whose updater ended while letting work finish must not leave new work paused.
3590
3638
  try {
3591
3639
  releaseStalledUpdate(dirname(context.databaseFile), clock());
@@ -6466,6 +6514,11 @@ async function runWatchLoop(args) {
6466
6514
  const batched = await runBatchChecks(store, context.evidenceRoot, { now: context.clock, shouldStop: stopping });
6467
6515
  if (batched > 0)
6468
6516
  progress(`watch: settled ${batched} batch-checked result(s)`);
6517
+ // A held release a person let go on resumes by itself, in the background of this pass (release-next.ts).
6518
+ if (context.agentRunner === undefined)
6519
+ void resumeHeldReleases(store, dirname(context.databaseFile), context.clock());
6520
+ // A result whose verdict went missing asks for its one automatic recheck (check-only.ts).
6521
+ requestMissingRechecks(store, context.evidenceRoot, context.clock());
6469
6522
  const ran = await runWaitingChecks(store, context.evidenceRoot, { now: context.clock, shouldStop: stopping });
6470
6523
  if (ran > 0)
6471
6524
  progress(`watch: ran ${ran} follow-up check(s)`);
@@ -6478,11 +6531,27 @@ async function runWatchLoop(args) {
6478
6531
  // all. A test's injected agent runner means a test: no real gh.
6479
6532
  if (context.agentRunner === undefined)
6480
6533
  await refreshGithubNightlies(store, context.clock);
6534
+ // The flaky tests list's GitHub rows (flaky-ci.ts): read-only, each project at most every 30 minutes.
6535
+ if (context.agentRunner === undefined)
6536
+ await refreshLearning(store, context.clock);
6481
6537
  // A finished check may send a build's card down its failure path: now, not on the next beat.
6482
6538
  if (ran + batched > 0)
6483
6539
  moveCards(store, repo, context.clock(), context.evidenceRoot);
6484
6540
  },
6485
6541
  });
6542
+ // The code map (docs/plans/code-map.md §2): its own beat, so a slow remote holds nothing else. A test's injected agent
6543
+ // runner means a test: no real fetch.
6544
+ const codeMapChecks = startMaintenance({
6545
+ intervalMs: 60_000,
6546
+ shouldStop: stopping,
6547
+ onError: error => progress(`watch: a code map check failed — ${describe(error)}; it tries again`),
6548
+ run: async () => {
6549
+ if (store.isDemo() || context.agentRunner !== undefined)
6550
+ return;
6551
+ await refreshCodeMaps(store, { scratch: join(context.evidenceRoot, ".code-map"), clock: context.clock });
6552
+ sweepDeliveries(store);
6553
+ },
6554
+ });
6486
6555
  // Flows on their own cadence (flow-cadence.ts), not between builds: while builds hold every slot of this
6487
6556
  // project, a card whose step finished still moves within one beat. Moves run apart from triggers, replies
6488
6557
  // and steps, so a long check script doesn't hold them either. Neither starts a build: a card entering a
@@ -6762,6 +6831,7 @@ async function runWatchLoop(args) {
6762
6831
  await Promise.all(lanes);
6763
6832
  await maintenance.stop();
6764
6833
  await followUpChecks.stop();
6834
+ await codeMapChecks.stop();
6765
6835
  await flowMoves.stop();
6766
6836
  await flowSteps.stop();
6767
6837
  await modelWatch.stop();
@@ -7436,6 +7506,8 @@ async function upCommand(flags, context) {
7436
7506
  retireRunnerIfCurrent(store, runnerName, runnerToken, clock());
7437
7507
  return fail(write, json, "up", "runner-alive", "The worker process record could not be saved; nothing started", EXIT.refused);
7438
7508
  }
7509
+ // What a person asked for, and what ran: an automatic restart acts on exactly this (builder-restart.ts).
7510
+ recordBuilderWish(store, "running", "up", packageVersion(), clock());
7439
7511
  doorAnswer = answer;
7440
7512
  break;
7441
7513
  }
@@ -8608,6 +8680,9 @@ async function statusCommand(positional, flags, context) {
8608
8680
  // This installation's console and background service (easy-start D), beside its updates and integrations: a person
8609
8681
  // limited to some projects returned above without it; an all-project caller reads what a local one does.
8610
8682
  const laptop = { laptop: await laptopStatus(context.store, context.databaseFile, context.laptopSeams) };
8683
+ // Nothing else on this computer is left to notice a builder that died: a look at status brings it back when it may.
8684
+ if (!laptop.laptop.running && laptop.laptop.background === "on" && !machineActing())
8685
+ await restartOwnBuilder(context.store, context.databaseFile, context.laptopSeams, context.clock()).catch(() => null);
8611
8686
  return succeed(context.write, context.json, command, { ...status, projects, ...laptop, ...update, ...integrations, ...updateWaiting, ...unsentReplies, ...(legacyWebhookWarning === null ? {} : { legacyWebhookWarning }) }, () => [...(waiting === null ? [] : [waiting.words]), ...(unsentLine === null ? [] : [unsentLine]), ...renderInstallationStatus(status), ...(projectsLine === null ? [] : [projectsLine]), ...(line === null ? [] : [line]), ...(brokenLine === null ? [] : [brokenLine]), ...(legacyWebhookWarning === null ? [] : [legacyWebhookWarning])]);
8612
8687
  }
8613
8688
  function unsentRepliesLine(unsent) {
@@ -8752,10 +8827,16 @@ function taskAction(action, rest, flags, context) {
8752
8827
  demo: context.store.isDemo(), credentials: () => askCredentials(flags, context), ...(context.publishExec === undefined ? {} : { exec: context.publishExec }) });
8753
8828
  case "add":
8754
8829
  return addTask(rest, flags, context);
8830
+ case "new":
8831
+ return newTaskCommand(rest, flags, context);
8832
+ case "keep":
8833
+ return keepGrownCommand(rest, flags, context);
8755
8834
  case "checks":
8756
8835
  return runTaskChecks(rest, flags, context);
8757
8836
  case "ship":
8758
8837
  return taskShipCommand(rest, context);
8838
+ case "ready":
8839
+ return taskReadyCommand(rest, context);
8759
8840
  case "add-tests":
8760
8841
  return addTestsTask(rest, flags, context);
8761
8842
  case "list":
@@ -9332,8 +9413,8 @@ function showTask(positional, context) {
9332
9413
  ...(task.state !== "done" || detail.proofVerdict === null
9333
9414
  ? []
9334
9415
  : [
9335
- ` proof: ${proofVerdictWords(current?.receipt?.runId === latestFinished?.id ? current?.readiness?.verdict ?? detail.proofVerdict : detail.proofVerdict, detail.proofReasons).word}${detail.proofAccepted ? " (accepted)" : ""}`,
9336
- ...(detail.proofReasons.length > 0 ? [` ${detail.proofReasons.join("; ")}`] : []),
9416
+ ` checked: ${proofVerdictWords(current?.receipt?.runId === latestFinished?.id ? current?.readiness?.verdict ?? detail.proofVerdict : detail.proofVerdict, detail.proofReasons).word}${detail.proofAccepted ? " (accepted)" : ""}`,
9417
+ ...(detail.proofReasons.length > 0 ? [` ${detail.proofReasons.map(plainReasonWords).join(" ")}`] : []),
9337
9418
  ...matrixWords(detail.proofMatrix),
9338
9419
  ...(detail.semanticCoverage === null ? [] : coverageWords(detail.semanticCoverage).map(line => ` ${line}`)),
9339
9420
  ]),
@@ -9341,11 +9422,11 @@ function showTask(positional, context) {
9341
9422
  ? []
9342
9423
  : detail.report.ok
9343
9424
  ? [
9344
- ` report: ${detail.report.report.title} (run ${detail.report.run})`,
9425
+ ` report: ${detail.report.report.title} (build #${detail.report.run})`,
9345
9426
  ` ${detail.report.report.summary}`,
9346
9427
  ...detail.report.report.followUps.map((one, index) => ` follow-up ${index + 1}: ${one.title}`),
9347
9428
  ]
9348
- : [` report: ${detail.report.problem} (run ${detail.report.run})`]),
9429
+ : [` report: ${detail.report.problem} (build #${detail.report.run})`]),
9349
9430
  ...pullRequestLines(detail.pullRequest),
9350
9431
  ...(base === null ? [] : [` base: ${baseStandingWords(base)}`]),
9351
9432
  ...(detail.blockedBy.length > 0 ? [` waits for ${detail.blockedBy.join(", ")}`] : []),
@@ -9378,7 +9459,7 @@ function showTask(positional, context) {
9378
9459
  ...detail.runs
9379
9460
  .filter(one => one.route !== null)
9380
9461
  .slice(0, 8)
9381
- .map(one => ` run #${one.id} ${one.role.padEnd(8)} ${one.route.provider}${one.route.model === null ? "" : ` · ${one.route.model}`} [${one.route.chosen}] route ${one.route.routeDigest}`),
9462
+ .map(one => ` build #${one.id} ${one.role.padEnd(8)} ${one.route.provider}${one.route.model === null ? "" : ` · ${one.route.model}`} [${one.route.chosen}]`),
9382
9463
  ]);
9383
9464
  }
9384
9465
  /** The review status lines `task show` prints (v50): the state word, the
@@ -9418,47 +9499,72 @@ function taskRouteStandingLines(id, routed) {
9418
9499
  if (routed === null)
9419
9500
  return [];
9420
9501
  if (routed.kind === "route") {
9421
- return [` agents ${routed.source === "approved" ? "sealed by the approval — configuration changes cannot reroute it" : routed.source === "proposed" ? "proposed — the next approval seals them" : "recommended live — no scope filed yet"}`];
9502
+ return [` agents ${routed.source === "approved" ? "approved with the plan — a configuration change never moves them" : routed.source === "proposed" ? "proposed — approving the plan approves them" : "recommended now — no plan filed yet"}`];
9422
9503
  }
9423
9504
  if (routed.kind === "legacy") {
9424
9505
  return [` agents approved before agent routing existed — ${routed.profile.provider} · ${routed.profile.model} builds and repairs (repair model ${routed.profile.repairModel === "inherit" ? routed.profile.model : routed.profile.repairModel}); the planner and reviewer resolve from configuration at run time`];
9425
9506
  }
9426
- return [` agents UNREADABLE — ${routed.problem}`, ` nothing runs for ${id} until its scope is re-filed and approved again`];
9507
+ return [` agents UNREADABLE — ${routed.problem}`, ` nothing runs for ${id} until its plan is saved again and approved`];
9427
9508
  }
9428
9509
  /**
9429
- * `task accept <id>` (Priority 2): the operator's explicit acceptance of a
9430
- * short or refuted proof verdict — the one act that lets the task read
9431
- * done despite incomplete evidence. Credentialed like approving a scope:
9432
- * accepting bad news is still authority.
9510
+ * `task accept <id>`: every call names what it means (docs/plans/plain-toolroll.md P1.1), so the same words never
9511
+ * quietly start meaning something else.
9512
+ * - `--finish` or `--digest <receipt>` is the person's Accept: exactly `task complete`'s exact-receipt completion, with
9513
+ * the same rule that `--json` and scoped credentials must pass `--digest`.
9514
+ * - `--despite "<reason>"` (`--note` is its alias) is acceptance only: an explicit acceptance of a short or refuted
9515
+ * result with a reason, credentialed like approving a plan. It never completes, and never combines with the above.
9516
+ * - A bare `task accept <id>` refuses, for good, naming both.
9433
9517
  */
9434
9518
  async function acceptTaskProof(positional, flags, context) {
9435
9519
  const { store, write, json, now } = context;
9520
+ const finishing = flags.has("finish") || flags.has("digest");
9521
+ const despiteFlag = flags.has("despite") ? flags.get("despite") : flags.get("note");
9522
+ const despiting = flags.has("despite") || flags.has("note");
9523
+ if (flags.has("despite") && flags.has("note"))
9524
+ return fail(write, json, "task accept", "usage", "Use --despite \"<reason>\" once (--note is the same flag).", EXIT.usage);
9525
+ if (finishing && despiting) {
9526
+ return fail(write, json, "task accept", "usage", "--despite accepts a result with a reason and never finishes the task; --finish and --digest finish it. Use one: first accept with --despite, then finish with --finish.", EXIT.usage);
9527
+ }
9528
+ if (!finishing && !despiting) {
9529
+ return fail(write, json, "task accept", "usage", "Say what accepting means: `toolroll task accept <id> --finish` accepts the result and finishes the task, or `--despite \"<reason>\"` accepts it with a reason without finishing.", EXIT.usage);
9530
+ }
9531
+ if (finishing) {
9532
+ // The person's Accept: `task complete`, unchanged, under this command's name.
9533
+ if (flags.get("finish") !== undefined && flags.get("finish") !== true)
9534
+ return fail(write, json, "task accept", "usage", "--finish takes no value.", EXIT.usage);
9535
+ const rest = new Map(flags);
9536
+ rest.delete("finish");
9537
+ return runTaskOutcomeCommand("complete", positional, rest, { ...context, command: "task accept", operator: async () => {
9538
+ const acting = await askCredentials(flags, context);
9539
+ if (acting === null)
9540
+ return null;
9541
+ const authenticated = verifyApproverByPassword(context.store, acting.name, acting.token, context.store.knownRepos().filter(repo => context.store.accountCanAccess(acting.name, repo)));
9542
+ return authenticated.ok ? authenticated.who : null;
9543
+ } });
9544
+ }
9436
9545
  const id = positional[0];
9437
9546
  if (id === undefined)
9438
- return fail(write, json, "task accept", "usage", "`toolroll task accept <id> [--note <text>] --as <you> --token <t>`", EXIT.usage);
9547
+ return fail(write, json, "task accept", "usage", "`toolroll task accept <id> --despite \"<reason>\" --as <you> --token <t>`", EXIT.usage);
9548
+ if (typeof despiteFlag !== "string" || despiteFlag.trim() === "")
9549
+ return fail(write, json, "task accept", "usage", "--despite needs the reason you accept it anyway: `--despite \"<reason>\"`.", EXIT.usage);
9550
+ const validated = validateNote(despiteFlag);
9551
+ if (!validated.ok)
9552
+ return fail(write, json, "task accept", "invalid", validated.problem, EXIT.usage);
9439
9553
  const task = store.getTask(id);
9440
9554
  if (task === null)
9441
9555
  return fail(write, json, "task accept", "unknown-task", `no task \`${id}\``, EXIT.refused);
9442
9556
  const acting = await askCredentials(flags, context);
9443
9557
  const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
9444
9558
  if (acting === null || !verified?.ok)
9445
- return refuseApprover(context, "task accept", verified, "accepting incomplete proof is a person's act — it takes `--as <you> --token <t>`", id);
9559
+ return refuseApprover(context, "task accept", verified, "accepting a result with a reason is a person's act — it takes `--as <you> --token <t>`", id);
9446
9560
  const ref = store.refFor(BUILT_IN, id);
9447
9561
  // v40 fix: a reviewer run is never the attempt whose proof is accepted.
9448
9562
  const latest = store.runsFor(ref.id).find(one => one.finishedAt !== null && (one.role === "builder" || one.role === "scout"));
9449
9563
  if (latest === undefined)
9450
9564
  return fail(write, json, "task accept", "no-run", `${id} has no finished attempt to accept`, EXIT.refused);
9451
- const rawNote = text(flags, "note");
9452
- let note = null;
9453
- if (rawNote !== undefined && rawNote.trim() !== "") {
9454
- const validated = validateNote(rawNote);
9455
- if (!validated.ok)
9456
- return fail(write, json, "task accept", "invalid", validated.problem, EXIT.usage);
9457
- note = validated.note;
9458
- }
9459
- store.acceptProof(latest.id, acting.name, note, now);
9565
+ store.acceptProof(latest.id, acting.name, validated.note, now);
9460
9566
  return succeed(write, json, "task accept", { id, run: latest.id, acceptedBy: acting.name }, () => [
9461
- `Accepted: ${id}'s build #${latest.id} reads done despite its proof, on ${acting.name}'s say-so.`,
9567
+ `Accepted ${id}'s build #${latest.id} with your reason, ${acting.name}. It isn't finished: \`toolroll task accept ${id} --finish\` finishes it.`,
9462
9568
  ]);
9463
9569
  }
9464
9570
  /**
@@ -9730,7 +9836,7 @@ async function pauseTask(positional, flags, context) {
9730
9836
  return fail(write, json, "task pause", "usage", "`toolroll task pause <id> [--run <run-id>] --as <you> --token <t>`", EXIT.usage);
9731
9837
  const acting = await askCredentials(flags, context);
9732
9838
  if (acting === null)
9733
- return fail(write, json, "task pause", "usage", "pausing takes `--as <you> --token <t>` — it is an operator's act and is recorded against your name", EXIT.usage);
9839
+ return fail(write, json, "task pause", "usage", "pausing takes `--as <you> --token <t>` — it is an approver's act and is recorded against your name", EXIT.usage);
9734
9840
  const authed = authenticateApprover(store, acting.name, acting.token);
9735
9841
  if (!authed.ok)
9736
9842
  return fail(write, json, "task pause", "not-an-approver", "that is not an approver, or the token does not match", EXIT.refused);
@@ -9748,7 +9854,7 @@ async function continueTask(positional, flags, context) {
9748
9854
  return fail(write, json, "task continue", "usage", "`toolroll task continue <id> [--run <run-id>] --as <you> --token <t>`", EXIT.usage);
9749
9855
  const acting = await askCredentials(flags, context);
9750
9856
  if (acting === null)
9751
- return fail(write, json, "task continue", "usage", "resuming takes `--as <you> --token <t>` — it is an operator's act and is recorded against your name", EXIT.usage);
9857
+ return fail(write, json, "task continue", "usage", "resuming takes `--as <you> --token <t>` — it is an approver's act and is recorded against your name", EXIT.usage);
9752
9858
  const authed = authenticateApprover(store, acting.name, acting.token);
9753
9859
  if (!authed.ok)
9754
9860
  return fail(write, json, "task continue", "not-an-approver", "that is not an approver, or the token does not match", EXIT.refused);
@@ -10283,6 +10389,11 @@ async function approveTask(positional, flags, context) {
10283
10389
  interactive() && !json) {
10284
10390
  write(`Approving lets a builder work on ${id}, within exactly this:`);
10285
10391
  write("");
10392
+ const changed = routeChangeOf(store, id);
10393
+ if (changed !== null) {
10394
+ write(changed);
10395
+ write("");
10396
+ }
10286
10397
  for (const line of describeScope(scope))
10287
10398
  write(line);
10288
10399
  for (const line of planContractLines(store, context.evidenceRoot, id))
@@ -10310,6 +10421,11 @@ async function approveTask(positional, flags, context) {
10310
10421
  }
10311
10422
  write(`Would approve this, and let a builder work on ${id}:`);
10312
10423
  write("");
10424
+ const changed = routeChangeOf(store, id);
10425
+ if (changed !== null) {
10426
+ write(changed);
10427
+ write("");
10428
+ }
10313
10429
  for (const line of describeScope(scope))
10314
10430
  write(line);
10315
10431
  for (const line of planContractLines(store, context.evidenceRoot, id))
@@ -10746,7 +10862,7 @@ async function stopTaskCommand(positional, flags, context) {
10746
10862
  const acting = await askCredentials(flags, context);
10747
10863
  const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
10748
10864
  if (acting === null || !verified?.ok)
10749
- return refuseApprover(context, "task stop", verified, "stopping takes `--as <you> --token <t>` — it is an operator's act and is recorded against your name", id);
10865
+ return refuseApprover(context, "task stop", verified, "stopping takes `--as <you> --token <t>` — it is an approver's act and is recorded against your name", id);
10750
10866
  const asked = requestTaskStop(store, { taskId: id, runId, by: acting.name, via: "cli" }, clock());
10751
10867
  if (!asked.ok)
10752
10868
  return fail(write, json, "task stop", asked.reason, asked.detail, EXIT.refused, { run: runId });
@@ -11435,9 +11551,11 @@ async function storageCommand(positional, flags, context) {
11435
11551
  };
11436
11552
  const refused = (what) => fail(context.write, context.json, command, "refused", `An instance operator ${what}: pass --as and --token (or use the remembered login).`, EXIT.refused);
11437
11553
  const summary = (plan) => [
11438
- `Checkouts: ${plan.count}, ${bytesWords(plan.totalBytes)}. ${plan.waitingReview} waiting for review, ${plan.withChanges} kept because they have changes.`,
11439
- plan.go.length === 0 ? "A clean up now would free nothing." : `A clean up now would free about ${bytesWords(plan.freeBytes)} (${plan.go.length} checkout${plan.go.length === 1 ? "" : "s"}): toolroll storage clean`,
11440
- `A finished task's clean checkout is removed ${cleanupWords(plan.cleanup)}; its branch stays.`,
11554
+ `Task folders: ${plan.count}, ${bytesWords(plan.totalBytes)}. ${plan.waitingReview} waiting for review, ${plan.withChanges} with uncommitted changes.`,
11555
+ plan.go.length === 0 ? "A clean up now would free nothing." : `A clean up now would free about ${bytesWords(plan.freeBytes)} (${plan.go.length} task folder${plan.go.length === 1 ? "" : "s"}): toolroll storage clean`,
11556
+ `A finished task's clean folder is removed ${cleanupWords(plan.cleanup)}; its branch stays.`,
11557
+ idleCleanupWords(plan),
11558
+ ...uncommittedLines(plan),
11441
11559
  ];
11442
11560
  const itemLine = (one) => ` ${bytesWords(one.bytes).padStart(8)} ${one.path}${one.why === null ? "" : ` (${whyWords(one)})`}`;
11443
11561
  // Test temp folders the tests, journeys and scripts left (a killed run, an older version): those nothing touched for a day go.
@@ -11490,16 +11608,28 @@ async function storageCommand(positional, flags, context) {
11490
11608
  if (action === "cleanup") {
11491
11609
  if (rest.length === 0) {
11492
11610
  const cleanup = store.checkoutCleanup();
11493
- return succeed(context.write, context.json, command, { cleanup }, () => [`A finished task's clean checkout is removed ${cleanupWords(cleanup)}; its branch stays.`]);
11611
+ const idle = checkoutIdle(store);
11612
+ return succeed(context.write, context.json, command, { cleanup, idle }, () => [`A finished task's clean folder is removed ${cleanupWords(cleanup)}; its branch stays.`, `A task's clean folder is removed ${idleWords(idle)}; its branch stays.`]);
11613
+ }
11614
+ const usage = `Use storage cleanup <${CLEANUP_CHOICES.map(one => one.value).join("|")}> or storage cleanup idle <7|14|30|never>.`;
11615
+ if (rest[0] === "idle") {
11616
+ const idle = rest.length === 2 ? parseIdle(rest[1]) : undefined;
11617
+ if (idle === undefined)
11618
+ return fail(context.write, context.json, command, "usage", usage, EXIT.usage);
11619
+ const actor = await operator();
11620
+ if (actor === null)
11621
+ return refused("sets idle cleanup");
11622
+ const saved = setCheckoutIdle(store, idle, actor, context.clock());
11623
+ return succeed(context.write, context.json, command, { idle: saved }, () => [`Saved. A task's clean folder is removed ${idleWords(saved)}; its branch stays.`]);
11494
11624
  }
11495
11625
  const cleanup = rest.length === 1 ? parseCleanup(rest[0]) : undefined;
11496
11626
  if (cleanup === undefined)
11497
- return fail(context.write, context.json, command, "usage", `Use storage cleanup <${CLEANUP_CHOICES.map(one => one.value).join("|")}>.`, EXIT.usage);
11627
+ return fail(context.write, context.json, command, "usage", usage, EXIT.usage);
11498
11628
  const actor = await operator();
11499
11629
  if (actor === null)
11500
11630
  return refused("sets checkout cleanup");
11501
11631
  const saved = store.setCheckoutCleanup(cleanup, actor, context.clock());
11502
- return succeed(context.write, context.json, command, { cleanup: saved }, () => [`Saved. A finished task's clean checkout is removed ${cleanupWords(saved)}; its branch stays.`]);
11632
+ return succeed(context.write, context.json, command, { cleanup: saved }, () => [`Saved. A finished task's clean folder is removed ${cleanupWords(saved)}; its branch stays.`]);
11503
11633
  }
11504
11634
  if (action === "clean" && flags.has("landed")) {
11505
11635
  if (rest.length > 0 || flags.get("landed") !== true)
@@ -11519,7 +11649,7 @@ async function storageCommand(positional, flags, context) {
11519
11649
  const sharedBytes = sharedGo.reduce((sum, one) => sum + one.bytes, 0);
11520
11650
  const anything = plan.go.length > 0 || temp.stale > 0 || sharedGo.length > 0;
11521
11651
  return succeed(context.write, context.json, command, { preview: true, go: plan.go, stay: plan.stay, freeBytes: plan.freeBytes, testTemp: temp, sharedGo, sharedFreeBytes: sharedBytes }, () => [
11522
- ...(plan.go.length === 0 ? [anything ? "No checkouts to clean up." : "Nothing to clean up."] : [`Would remove ${plan.go.length} checkout${plan.go.length === 1 ? "" : "s"}, freeing about ${bytesWords(plan.freeBytes)} (their branches stay):`, ...plan.go.map(itemLine)]),
11652
+ ...(plan.go.length === 0 ? [anything ? "No task folders to clean up." : "Nothing to clean up."] : [`Would remove ${plan.go.length} task folder${plan.go.length === 1 ? "" : "s"}, freeing about ${bytesWords(plan.freeBytes)} (their branches stay):`, ...plan.go.map(itemLine)]),
11523
11653
  ...(sharedGo.length === 0 ? [] : [`Would remove ${sharedGo.length} shared dependency install${sharedGo.length === 1 ? "" : "s"} no checkout uses, about ${bytesWords(sharedBytes)}:`, ...sharedGo.map(sharedLine)]),
11524
11654
  ...(plan.stay.length === 0 ? [] : [`Stays (${plan.stay.length}):`, ...plan.stay.map(itemLine)]),
11525
11655
  ...(temp.stale === 0 ? [] : [`Would remove ${temp.stale} test temp folder${temp.stale === 1 ? "" : "s"} older than a day, about ${bytesWords(temp.staleBytes)}.`]),
@@ -11554,7 +11684,7 @@ async function storageCommand(positional, flags, context) {
11554
11684
  ];
11555
11685
  saveSweep(store, { at: context.clock().toISOString(), source: "manual", actor, parts });
11556
11686
  return succeed(context.write, context.json, command, { removed: done.removed, kept: done.kept, freedBytes: done.freed, testTempRemoved: swept.removed.length, testTempFailed: swept.failed, sharedRemoved, sharedFreedBytes: sharedFreed }, () => [
11557
- done.removed.length === 0 ? (swept.removed.length === 0 && sharedRemoved.length === 0 ? "Nothing to clean up." : "No checkouts to clean up.") : `Removed ${done.removed.length} checkout${done.removed.length === 1 ? "" : "s"}, about ${bytesWords(done.freed)}; their branches stay.`,
11687
+ done.removed.length === 0 ? (swept.removed.length === 0 && sharedRemoved.length === 0 ? "Nothing to clean up." : "No task folders to clean up.") : `Removed ${done.removed.length} task folder${done.removed.length === 1 ? "" : "s"}, about ${bytesWords(done.freed)}; their branches stay.`,
11558
11688
  ...done.removed.map(itemLine),
11559
11689
  ...(sharedRemoved.length === 0 ? [] : [`Removed ${sharedRemoved.length} shared dependency install${sharedRemoved.length === 1 ? "" : "s"} no checkout used, about ${bytesWords(sharedFreed)}.`, ...sharedRemoved.map(sharedLine)]),
11560
11690
  ...done.kept.map(one => ` kept ${one.path} (${whyWords({ why: one.why })})`),
@@ -11632,7 +11762,8 @@ async function landedCommandWith(remotes, yes, operator, refused, pool, context)
11632
11762
  ]);
11633
11763
  }
11634
11764
  function checkoutSummary(plan) {
11635
- return { count: plan.count, totalBytes: plan.totalBytes, waitingReview: plan.waitingReview, withChanges: plan.withChanges, cleanBytes: plan.freeBytes, cleanCount: plan.go.length, cleanup: plan.cleanup };
11765
+ return { count: plan.count, totalBytes: plan.totalBytes, waitingReview: plan.waitingReview, withChanges: plan.withChanges, cleanBytes: plan.freeBytes, cleanCount: plan.go.length, cleanup: plan.cleanup,
11766
+ idle: plan.idle, idleCount: plan.idleCount, idleBytes: plan.idleBytes, uncommitted: plan.stay.filter(one => one.why === "has changes").map(one => ({ path: one.path, taskId: one.taskId, bytes: one.bytes })) };
11636
11767
  }
11637
11768
  /** `review [show] | on | off --repo <path>`: the project's one automatic review
11638
11769
  * of finished builds. Changing it takes an approver of that project; the
@@ -13311,6 +13442,8 @@ async function backgroundCommand(positional, flags, context) {
13311
13442
  return succeed(write, json, command, off, () => [off.action === "already-off" ? "Toolroll wasn't set to run in the background." : "Toolroll no longer runs in the background. Start it with `toolroll` when you need it."]);
13312
13443
  }
13313
13444
  const on = await backgroundOn(context.store, context.databaseFile, port, seams);
13445
+ if (on.ok)
13446
+ recordBuilderWish(context.store, "running", "background on", packageVersion(), context.clock());
13314
13447
  if (!on.ok) {
13315
13448
  const code = ["agent", "conflict", "declined", "running-elsewhere", "unsupported", "console-unproven", "invalid-unit", "unreadable"].includes(on.reason) ? EXIT.refused : EXIT.failed;
13316
13449
  if (json) {
@@ -13474,7 +13607,7 @@ export function upLaptopPortCandidates(store, fromSeam) {
13474
13607
  }
13475
13608
  /** The memory verbs that change project memory (memory-cli.ts), and `propose`, which files the proposals a person then
13476
13609
  * applies: a person's act, never the lead's. The reads stay as they are. */
13477
- const MEMORY_WRITE_VERBS = new Set(["decide", "retire", "apply", "propose"]);
13610
+ const MEMORY_WRITE_VERBS = new Set(["decide", "retire", "apply", "propose", "tidy", "restore", "unmerge"]);
13478
13611
  function memoryWriteVerb(positional) {
13479
13612
  return MEMORY_WRITE_VERBS.has(positional[0] ?? "");
13480
13613
  }
@@ -13586,3 +13719,220 @@ async function releaseApproveCommand(positional, flags, context) {
13586
13719
  ...(shown.stopped === null ? [] : [`The release isn't running: run \`toolroll release ${chosen.branch}\` to go on.`]),
13587
13720
  ]);
13588
13721
  }
13722
+ /**
13723
+ * `toolroll task ready <task>` (read-only; docs/plans/plain-toolroll.md P1.6): "Can I merge?" — the task's checklist
13724
+ * from the records the task page reads (merge-checklist.ts), each blocker with the existing act that clears it. A task
13725
+ * outside the caller's projects is no task here.
13726
+ */
13727
+ function taskReadyCommand(positional, context) {
13728
+ const { store, write, json } = context;
13729
+ const command = "task ready";
13730
+ const id = positional[0];
13731
+ if (id === undefined || positional.length > 1)
13732
+ return fail(write, json, command, "usage", "`toolroll task ready <task>`", EXIT.usage);
13733
+ const lens = context.principal?.lens ?? null;
13734
+ const ref = store.lookupRef(id);
13735
+ if (ref === null || store.getTask(id) === null || (lens !== null && (ref.repo === null || !lens.includes(ref.repo))))
13736
+ return fail(write, json, command, "unknown-task", `no task \`${id}\``, EXIT.refused);
13737
+ const now = (context.clock ?? (() => context.now))();
13738
+ const list = mergeChecklistOf(store, id, now, { principal: "operator", repos: lens, includeUnplaced: lens === null, dependencyRepos: lens }, context.evidenceRoot);
13739
+ if (list === null)
13740
+ return fail(write, json, command, "unknown-task", `no task \`${id}\``, EXIT.refused);
13741
+ return succeed(write, json, command, list, () => checklistLines(list));
13742
+ }
13743
+ /** The folders with uncommitted changes: `toolroll storage` lists them so a person can decide. Never removed automatically. */
13744
+ function uncommittedLines(plan) {
13745
+ const changed = plan.stay.filter(one => one.why === "has changes");
13746
+ if (changed.length === 0)
13747
+ return [];
13748
+ return [`${changed.length === 1 ? "This folder has" : "These folders have"} uncommitted changes and stay${changed.length === 1 ? "s" : ""} until you decide (toolroll storage discard <path> --yes throws one away):`,
13749
+ ...changed.map(one => ` ${bytesWords(one.bytes).padStart(8)} ${one.path}${one.taskId === null ? "" : ` (${one.taskId})`}`)];
13750
+ }
13751
+ /** `toolroll map` (code-map-cli.ts): who is asking, and which projects they can open. A read needs no sign-in on this
13752
+ * computer's own database, as `knowledge search`; a sign-in limits it to that person's projects. */
13753
+ async function mapCommand(positional, flags, context) {
13754
+ const { store } = context;
13755
+ const local = currentLocalOwner();
13756
+ const { name, token } = credentialsFrom(flags, context);
13757
+ let actor = null;
13758
+ let repos = null;
13759
+ const all = [...new Set([...store.knownRepos(), ...store.listProjects().map(one => one.path)])];
13760
+ if (local !== null) {
13761
+ actor = local.account;
13762
+ repos = all.filter(repo => store.accountCanAccess(local.account, repo));
13763
+ }
13764
+ else if (name !== undefined && token !== undefined) {
13765
+ if (!authenticateAccount(store, name, token).ok)
13766
+ return fail(context.write, context.json, "map", "unauthenticated", "Sign in again: that login doesn't work here.", EXIT.refused);
13767
+ const remote = activeRemote();
13768
+ actor = name;
13769
+ repos = all.filter(repo => store.accountCanAccess(name, repo) && (remote === null || remote.allows(repo)));
13770
+ }
13771
+ else
13772
+ repos = all;
13773
+ return runMapCommand(positional, flags, { store, write: context.write, json: context.json, now: context.clock(), actor, repos,
13774
+ approver: repo => local === null && name !== undefined && token !== undefined && authenticateApprover(store, name, token, repo).ok,
13775
+ door: repo => {
13776
+ if (local !== null)
13777
+ return { kind: "local-owner", actor: local.account };
13778
+ if (name === undefined || token === undefined)
13779
+ return null;
13780
+ // The credential generation the proof is checked against, read before it, as `models` does.
13781
+ const generation = store.accountOf(name)?.generation ?? -1;
13782
+ const proved = authenticateApprover(store, name, token, repo);
13783
+ return proved.ok ? { kind: "person", person: { actor: name, generation, method: proved.method } } : null;
13784
+ } });
13785
+ }
13786
+ // ---- `toolroll task new` (docs/plans/plain-toolroll.md P2.4) -----------------------------------------------------------
13787
+ /**
13788
+ * Start a task from a person's own terminal: their words become the exact terms, printed; "Build now?" is their yes.
13789
+ * Filing and approving are one step (fast-start.ts), for small work a person approves without typing a password here;
13790
+ * anything else is the ordinary `task add`, then Approve plan. Never for a script or an agent: `--json`, no terminal, a
13791
+ * token or a remote run all refuse. The terminal UI drives it as the local owner, with --preview and --build.
13792
+ */
13793
+ async function newTaskCommand(positional, flags, context) {
13794
+ const { store, write, json } = context;
13795
+ const clock = context.clock ?? (() => new Date());
13796
+ const command = "task new";
13797
+ const local = currentLocalOwner();
13798
+ if (context.principal !== undefined || machineActing())
13799
+ return fail(write, json, command, "person-only", "Only a person starts a build this way. File it with `task add`.", EXIT.refused);
13800
+ if (local === null && (json || !personAtTerminal(context)))
13801
+ return fail(write, json, command, "person-only", "`task new` asks a person at a terminal. Use `task add` from a script or an agent.", EXIT.refused);
13802
+ const { name, token } = credentialsFrom(flags, context);
13803
+ const passwordFree = token !== undefined && (local !== null || passwordlessOn(store));
13804
+ const facts = { passwordless: local === null && passwordFree, localOwner: local !== null, freshAt: null, returnTo: "/" };
13805
+ const buildNow = (preview, digest) => fastStartBuild(store, { preview, digest, actor: name ?? "", via: local === null ? "cli" : "terminal-ui", password: facts,
13806
+ approveNow: (taskId, sawDigest, fast) => approve(store, taskId, name ?? "", clock(), sawDigest, token ?? "", {}, fast) }, clock());
13807
+ const answerBuild = (built) => built.ok
13808
+ ? succeed(write, json, command, { task: built.taskId }, () => [`Building ${built.taskId}. A builder starts it next.`])
13809
+ : fail(write, json, command, built.reason, built.message, EXIT.refused);
13810
+ const pressed = text(flags, "build");
13811
+ if (pressed !== undefined) {
13812
+ if (local === null)
13813
+ return fail(write, json, command, "person-only", "--build is the terminal UI's own.", EXIT.refused);
13814
+ return answerBuild(buildNow(pressed, text(flags, "digest") ?? ""));
13815
+ }
13816
+ const fromLink = text(flags, "from");
13817
+ if (fromLink !== undefined)
13818
+ return importTaskCommand(fromLink, flags, context, name);
13819
+ const words = positional.join(" ").trim();
13820
+ const repo = text(flags, "repo");
13821
+ if (words === "" || repo === undefined)
13822
+ return fail(write, json, command, "usage", "`toolroll task new \"<what should change>\" --repo <project>`", EXIT.usage);
13823
+ const placed = canonicalProject(repo) ?? resolve(repo);
13824
+ const prepared = name === undefined || token === undefined ? { eligible: false, reason: "password" }
13825
+ : await prepareFastStart(store, { actor: name, repo: placed, title: words, via: local === null ? "cli" : "terminal-ui", password: facts }, clock());
13826
+ const terms = prepared.eligible ? fastTermsLines(store, prepared.scope, placed) : [];
13827
+ if (flags.has("preview")) {
13828
+ if (local === null)
13829
+ return fail(write, json, command, "person-only", "--preview is the terminal UI's own.", EXIT.refused);
13830
+ return succeed(write, json, command, prepared.eligible ? { eligible: true, preview: prepared.preview, digest: prepared.digest, terms } : { eligible: false, reason: prepared.reason, message: fastRefusalWords(prepared.reason) }, () => []);
13831
+ }
13832
+ if (!prepared.eligible) {
13833
+ write(`${fastRefusalWords(prepared.reason)} Filing it for a plan.`);
13834
+ return addTask([words], flags, context);
13835
+ }
13836
+ write("Build approves exactly this:");
13837
+ write("");
13838
+ for (const line of terms)
13839
+ write(` ${line.label}: ${line.words}`);
13840
+ write("");
13841
+ if (!(await (context.easySeams?.confirm ?? confirm)("Build now?"))) {
13842
+ write("Nothing started. File it for a plan with `toolroll task add`.");
13843
+ return EXIT.refused;
13844
+ }
13845
+ return answerBuild(buildNow(prepared.preview, prepared.digest));
13846
+ }
13847
+ /** `toolroll task keep <id>`: "Approve it to keep it", a plan for the exact commit a fast start grew into. Approve it with `task approve`. */
13848
+ async function keepGrownCommand(positional, flags, context) {
13849
+ const { store, write, json, clock } = context;
13850
+ const id = positional[0];
13851
+ if (id === undefined)
13852
+ return fail(write, json, "task keep", "usage", "`toolroll task keep <id> --as <you> --token <t>`", EXIT.usage);
13853
+ const acting = await askCredentials(flags, context);
13854
+ const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
13855
+ if (acting === null || !verified?.ok)
13856
+ return refuseApprover(context, "task keep", verified, "keeping a change takes `--as <you> --token <t>`", id);
13857
+ const kept = keepGrownChange(store, id, clock());
13858
+ if (!kept.ok)
13859
+ return fail(write, json, "task keep", "refused", kept.message, EXIT.refused);
13860
+ return succeed(write, json, "task keep", { id, digest: kept.digest }, () => [`Its plan now names the exact commit. Approve it with \`toolroll task approve ${id}\`.`]);
13861
+ }
13862
+ /** One resume at a time per process; a release's own steps can take many minutes. */
13863
+ let resumingReleases = false;
13864
+ async function resumeHeldReleases(store, stateDir, now) {
13865
+ if (resumingReleases)
13866
+ return;
13867
+ resumingReleases = true;
13868
+ try {
13869
+ await resumeAllowedReleases(store, stateDir, journal => runRelease({ branch: journal.branch, checkout: journal.checkout, stateDir, limits: {} }, releaseAdapters({ checkout: journal.checkout, say: () => { } })), now);
13870
+ }
13871
+ catch { /* the release's own journal says where it stopped; the next person act tries again */ }
13872
+ finally {
13873
+ resumingReleases = false;
13874
+ }
13875
+ }
13876
+ /** `task new --from <url>`: read and quote an issue or pull request, then, on a person's yes, file it for the ordinary approval. */
13877
+ async function importTaskCommand(url, flags, context, actor) {
13878
+ const { store, write, json } = context;
13879
+ const clock = context.clock ?? (() => new Date());
13880
+ const command = "task new";
13881
+ const repo = text(flags, "repo");
13882
+ if (repo === undefined)
13883
+ return fail(write, json, command, "usage", "`toolroll task new --from <url> --repo <project>`", EXIT.usage);
13884
+ const placed = canonicalProject(repo) ?? resolve(repo);
13885
+ const read = await readGithubItem(store, { url, repo: placed }, context.easySeams?.github ?? {});
13886
+ if (!read.ok)
13887
+ return fail(write, json, command, read.reason, read.message, EXIT.refused);
13888
+ write(`${read.item.kind === "pull" ? "Pull request" : "Issue"} #${read.item.number}, written outside Toolroll:`);
13889
+ write(` > ${read.item.title}`);
13890
+ for (const line of read.item.body.split(/\r?\n/))
13891
+ write(` > ${line}`);
13892
+ write("");
13893
+ if (!(await (context.easySeams?.confirm ?? confirm)("Add this task? You approve its plan before anything builds."))) {
13894
+ write("Nothing filed.");
13895
+ return EXIT.refused;
13896
+ }
13897
+ const filed = await fileGithubItem(store, { url, repo: placed, actor: actor ?? "cli", shownHead: read.item.head }, clock(), context.easySeams?.github ?? {});
13898
+ if (!filed.ok)
13899
+ return fail(write, json, command, filed.reason, filed.message, EXIT.refused);
13900
+ return succeed(write, json, command, { task: filed.taskId }, () => [`Filed ${filed.taskId}. Its plan is ready for you to approve.`]);
13901
+ }
13902
+ /** `toolroll flaky list|show`: reads only, for the remembered local login (or --as/--token) and its projects. */
13903
+ async function flakyCommand(positional, flags, context) {
13904
+ const acting = await askCredentials(flags, context);
13905
+ const verified = acting === null || currentLocalOwner() !== null ? null : verifyApproverByPassword(context.store, acting.name, acting.token, context.store.knownRepos().filter(repo => context.store.accountCanAccess(acting.name, repo)));
13906
+ const who = currentLocalOwner() !== null ? localReader(context) : verified !== null && verified.ok ? verified.who : null;
13907
+ return runFlakyCommand(positional, flags, { store: context.store, write: context.write, json: context.json, now: context.clock(), actor: who?.name ?? null, repos: who?.repos ?? [] });
13908
+ }
13909
+ /** The watch loop's background learning pass (docs/plans/learning-loops.md): advisory reads, each project at most every
13910
+ * 30 minutes through the plane's own gh. A failure here never reaches the loop: it is tried on the next pass. */
13911
+ export async function refreshLearning(store, now, reads) {
13912
+ try {
13913
+ backfillMemoryUse(store, now());
13914
+ }
13915
+ catch { /* advisory: the next pass tries again */ }
13916
+ // Project memory's clean-up proposals: deterministic, a quiet project at most weekly. Nothing changes until a person accepts.
13917
+ for (const repo of store.knownRepos()) {
13918
+ try {
13919
+ scheduledTidy(store, repo, now());
13920
+ }
13921
+ catch { /* the next pass tries again */ }
13922
+ }
13923
+ for (const repo of store.knownRepos()) {
13924
+ try {
13925
+ const key = ciCursorKey(repo), at = now();
13926
+ if (at.getTime() - store.serviceCursor(key) < CI_READ_EVERY_MS || ciRepositoriesOf(store, repo).length === 0)
13927
+ continue;
13928
+ store.setServiceCursor(key, at.getTime(), at);
13929
+ await observeCiAttempts(store, repo, reads ?? githubReads(releaseExec, releaseTools().gh, { retry: true }), at);
13930
+ }
13931
+ catch { /* advisory: the next pass tries again */ }
13932
+ }
13933
+ }
13934
+ /** The laptop's local owner as a reader: its name and its projects. Never a proof: every write proves its own act. */
13935
+ function localReader(context) {
13936
+ const local = currentLocalOwner();
13937
+ return local === null ? null : { name: local.account, repos: context.store.knownRepos().filter(repo => context.store.accountCanAccess(local.account, repo)) };
13938
+ }