toolroll 0.9.93 → 0.9.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (436) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/dist/activity-line.d.ts +7 -4
  3. package/dist/activity-line.js +9 -7
  4. package/dist/agent-fence.d.ts +10 -0
  5. package/dist/agent-fence.js +6 -3
  6. package/dist/approval-authority.d.ts +7 -1
  7. package/dist/approval-authority.js +11 -2
  8. package/dist/approval-password.d.ts +31 -0
  9. package/dist/approval-password.js +26 -0
  10. package/dist/assignment-status.d.ts +1 -1
  11. package/dist/assignment-ui.js +3 -1
  12. package/dist/assignment.js +5 -0
  13. package/dist/attest.d.ts +3 -0
  14. package/dist/attest.js +10 -0
  15. package/dist/boot-identity.d.ts +2 -1
  16. package/dist/boot-identity.js +6 -4
  17. package/dist/bounded-spawn.d.ts +57 -0
  18. package/dist/bounded-spawn.js +220 -0
  19. package/dist/browser/terminal.js +42 -42
  20. package/dist/browser/workspace.css +1 -1
  21. package/dist/browser/workspace.js +32 -32
  22. package/dist/browser-crew.js +2 -0
  23. package/dist/browser-shell.d.ts +1 -1
  24. package/dist/browser-shell.js +2 -2
  25. package/dist/browser-workspace.d.ts +50 -2
  26. package/dist/browser-workspace.js +3 -3
  27. package/dist/build-review.js +2 -0
  28. package/dist/builder-restart.d.ts +21 -0
  29. package/dist/builder-restart.js +50 -0
  30. package/dist/builder.d.ts +11 -2
  31. package/dist/builder.js +234 -101
  32. package/dist/card-catalog.d.ts +209 -0
  33. package/dist/card-catalog.js +133 -0
  34. package/dist/card-cli.d.ts +43 -0
  35. package/dist/card-cli.js +44 -0
  36. package/dist/card-render.d.ts +26 -0
  37. package/dist/card-render.js +223 -0
  38. package/dist/card-sources.d.ts +68 -0
  39. package/dist/card-sources.js +234 -0
  40. package/dist/chat-acceptance.js +1 -1
  41. package/dist/chat-actions.d.ts +106 -9
  42. package/dist/chat-actions.js +473 -46
  43. package/dist/chat-approval.d.ts +13 -0
  44. package/dist/chat-approval.js +39 -9
  45. package/dist/chat-card-part.d.ts +11 -0
  46. package/dist/chat-card-part.js +18 -0
  47. package/dist/chat-card.d.ts +46 -0
  48. package/dist/chat-card.js +99 -0
  49. package/dist/chat-channel.d.ts +2 -0
  50. package/dist/chat-channel.js +37 -14
  51. package/dist/chat-controls.d.ts +4 -0
  52. package/dist/chat-controls.js +4 -0
  53. package/dist/chat-decide.d.ts +10 -16
  54. package/dist/chat-decide.js +79 -62
  55. package/dist/chat-delivery.js +13 -4
  56. package/dist/chat-flow.d.ts +0 -27
  57. package/dist/chat-flow.js +31 -2
  58. package/dist/chat-quiet.js +4 -4
  59. package/dist/chat-task-actions.d.ts +3 -2
  60. package/dist/chat-task-actions.js +19 -5
  61. package/dist/chat-terms.d.ts +14 -2
  62. package/dist/chat-terms.js +35 -0
  63. package/dist/chat-voice.d.ts +1 -1
  64. package/dist/chat-voice.js +2 -2
  65. package/dist/check-facts.d.ts +3 -1
  66. package/dist/check-facts.js +57 -11
  67. package/dist/check-failure.d.ts +24 -3
  68. package/dist/check-failure.js +89 -13
  69. package/dist/check-levels-ui.d.ts +4 -0
  70. package/dist/check-levels-ui.js +10 -1
  71. package/dist/check-load.d.ts +85 -0
  72. package/dist/check-load.js +147 -0
  73. package/dist/check-only-standing.d.ts +36 -0
  74. package/dist/check-only-standing.js +39 -0
  75. package/dist/check-only.d.ts +15 -0
  76. package/dist/check-only.js +99 -0
  77. package/dist/check-repair.d.ts +47 -0
  78. package/dist/check-repair.js +240 -0
  79. package/dist/check-report.d.ts +2 -0
  80. package/dist/check-report.js +1 -1
  81. package/dist/check-stamp.d.ts +46 -0
  82. package/dist/check-stamp.js +93 -0
  83. package/dist/checkout-cleanup.d.ts +36 -5
  84. package/dist/checkout-cleanup.js +166 -14
  85. package/dist/claim.d.ts +6 -0
  86. package/dist/claim.js +13 -2
  87. package/dist/cli-help.d.ts +4 -1
  88. package/dist/cli-help.js +49 -30
  89. package/dist/cli.js +15 -1
  90. package/dist/code-extract.d.ts +44 -0
  91. package/dist/code-extract.js +88 -0
  92. package/dist/code-map-acts.d.ts +24 -0
  93. package/dist/code-map-acts.js +53 -0
  94. package/dist/code-map-admission.d.ts +52 -0
  95. package/dist/code-map-admission.js +295 -0
  96. package/dist/code-map-build.d.ts +63 -0
  97. package/dist/code-map-build.js +415 -0
  98. package/dist/code-map-cli.d.ts +153 -0
  99. package/dist/code-map-cli.js +103 -0
  100. package/dist/code-map-main.d.ts +1 -0
  101. package/dist/code-map-main.js +48 -0
  102. package/dist/code-map-read.d.ts +106 -0
  103. package/dist/code-map-read.js +171 -0
  104. package/dist/code-map-report.d.ts +37 -0
  105. package/dist/code-map-report.js +95 -0
  106. package/dist/code-map-shape.d.ts +172 -0
  107. package/dist/code-map-shape.js +327 -0
  108. package/dist/code-map-store.d.ts +136 -0
  109. package/dist/code-map-store.js +233 -0
  110. package/dist/coding-provider.d.ts +3 -1
  111. package/dist/coding-provider.js +12 -7
  112. package/dist/coding-update.js +3 -2
  113. package/dist/coding-workspace.d.ts +1 -1
  114. package/dist/coding-workspace.js +8 -8
  115. package/dist/console-chrome.js +2 -2
  116. package/dist/containment.d.ts +5 -0
  117. package/dist/containment.js +14 -1
  118. package/dist/contracts/chat-actions.d.ts +92 -0
  119. package/dist/contracts/chat-actions.js +20 -0
  120. package/dist/contracts/chat-card.d.ts +91 -0
  121. package/dist/contracts/chat-card.js +54 -0
  122. package/dist/contracts/chat-content.d.ts +191 -0
  123. package/dist/contracts/chat-content.js +4 -0
  124. package/dist/contracts/cli.d.ts +52 -0
  125. package/dist/contracts/cli.js +4 -0
  126. package/dist/contracts/console-api.d.ts +12 -3
  127. package/dist/contracts/console-api.js +12 -3
  128. package/dist/contracts/flow-propose.d.ts +8 -1
  129. package/dist/contracts/flow-propose.js +3 -1
  130. package/dist/contracts/flow-send.d.ts +126 -0
  131. package/dist/contracts/flow-send.js +3 -0
  132. package/dist/contracts/gateway-tools.d.ts +17 -0
  133. package/dist/contracts/gateway-tools.js +3 -1
  134. package/dist/contracts/lead-tools.d.ts +234 -0
  135. package/dist/contracts/lead-tools.js +25 -1
  136. package/dist/contracts/project-knowledge.d.ts +38 -5
  137. package/dist/contracts/project-knowledge.js +11 -5
  138. package/dist/contracts/project-memory.d.ts +3 -1
  139. package/dist/contracts/project-memory.js +3 -2
  140. package/dist/contracts/storage.d.ts +14 -0
  141. package/dist/contracts/storage.js +3 -1
  142. package/dist/contracts/store-json.d.ts +18 -0
  143. package/dist/contracts/youiyoux-request.d.ts +1 -1
  144. package/dist/contracts/youiyoux-request.js +1 -1
  145. package/dist/desktop-access.js +6 -2
  146. package/dist/discord-chat.js +14 -0
  147. package/dist/dispatch.d.ts +1 -1
  148. package/dist/dispatch.js +25 -16
  149. package/dist/dispose.d.ts +19 -0
  150. package/dist/dispose.js +55 -9
  151. package/dist/easy-start.js +2 -3
  152. package/dist/eval-oracle.js +4 -1
  153. package/dist/evals.js +2 -1
  154. package/dist/evidence-pair.d.ts +53 -0
  155. package/dist/evidence-pair.js +128 -0
  156. package/dist/exhaustion.d.ts +17 -4
  157. package/dist/exhaustion.js +44 -4
  158. package/dist/export.js +1 -1
  159. package/dist/failing-first.d.ts +31 -0
  160. package/dist/failing-first.js +44 -0
  161. package/dist/fallback-schema.d.ts +8 -0
  162. package/dist/fallback-schema.js +40 -0
  163. package/dist/fallback.d.ts +138 -0
  164. package/dist/fallback.js +605 -0
  165. package/dist/fast-start.d.ts +136 -0
  166. package/dist/fast-start.js +431 -0
  167. package/dist/fast-terms.d.ts +9 -0
  168. package/dist/fast-terms.js +23 -0
  169. package/dist/fenced-check-coverage.d.ts +62 -0
  170. package/dist/fenced-check-coverage.js +54 -0
  171. package/dist/findings.d.ts +224 -0
  172. package/dist/findings.js +449 -0
  173. package/dist/first-run.js +1 -1
  174. package/dist/flaky-ci.d.ts +31 -0
  175. package/dist/flaky-ci.js +149 -0
  176. package/dist/flaky-cli.d.ts +60 -0
  177. package/dist/flaky-cli.js +60 -0
  178. package/dist/flaky.d.ts +75 -9
  179. package/dist/flaky.js +255 -0
  180. package/dist/fleet-schema.d.ts +17 -0
  181. package/dist/fleet-schema.js +135 -0
  182. package/dist/flow-browser.js +2 -2
  183. package/dist/flow-card-task.js +3 -0
  184. package/dist/flow-code.d.ts +2 -2
  185. package/dist/flow-code.js +9 -5
  186. package/dist/flow-result-card.d.ts +22 -0
  187. package/dist/flow-result-card.js +55 -0
  188. package/dist/flow-send.js +12 -9
  189. package/dist/flow-steps.js +1 -1
  190. package/dist/flow-triggers.d.ts +1 -1
  191. package/dist/flow-triggers.js +2 -2
  192. package/dist/flow-unattended.d.ts +26 -0
  193. package/dist/flow-unattended.js +12 -3
  194. package/dist/flows-ui.js +10 -2
  195. package/dist/follow-through-brief.d.ts +12 -0
  196. package/dist/follow-through-brief.js +31 -0
  197. package/dist/follow-through-cli.d.ts +23 -0
  198. package/dist/follow-through-cli.js +153 -0
  199. package/dist/git-incident.d.ts +16 -0
  200. package/dist/git-incident.js +69 -6
  201. package/dist/github-import.d.ts +62 -0
  202. package/dist/github-import.js +146 -0
  203. package/dist/guides.js +15 -10
  204. package/dist/handover-note.d.ts +33 -0
  205. package/dist/handover-note.js +106 -0
  206. package/dist/invoke.d.ts +25 -2
  207. package/dist/invoke.js +157 -58
  208. package/dist/job-object-helper.ps1 +10 -3
  209. package/dist/keymap.d.ts +41 -0
  210. package/dist/keymap.js +51 -0
  211. package/dist/knowledge-cli.js +1 -1
  212. package/dist/knowledge-ui.d.ts +21 -4
  213. package/dist/knowledge-ui.js +75 -7
  214. package/dist/laptop-background.d.ts +16 -0
  215. package/dist/laptop-background.js +108 -2
  216. package/dist/lead-cli.js +26 -24
  217. package/dist/lead-context.js +1 -1
  218. package/dist/lead-contract.d.ts +1 -1
  219. package/dist/lead-contract.js +3 -2
  220. package/dist/lead-doors.d.ts +4 -0
  221. package/dist/lead-doors.js +2 -2
  222. package/dist/lead-progress.js +5 -0
  223. package/dist/lead-status.d.ts +2 -0
  224. package/dist/lead-status.js +6 -2
  225. package/dist/lead-tools.js +162 -34
  226. package/dist/lead.js +2 -2
  227. package/dist/lesson-notes.d.ts +81 -0
  228. package/dist/lesson-notes.js +336 -0
  229. package/dist/limits-ui.js +1 -1
  230. package/dist/local-owner.d.ts +1 -1
  231. package/dist/local-owner.js +16 -0
  232. package/dist/machine-check.d.ts +16 -0
  233. package/dist/machine-check.js +26 -0
  234. package/dist/machine-identity.d.ts +75 -0
  235. package/dist/machine-identity.js +170 -0
  236. package/dist/mcp-core.js +11 -1
  237. package/dist/memory-cli.d.ts +300 -1
  238. package/dist/memory-cli.js +62 -9
  239. package/dist/memory-pass.d.ts +16 -0
  240. package/dist/memory-pass.js +67 -3
  241. package/dist/memory-tidy.d.ts +181 -0
  242. package/dist/memory-tidy.js +523 -0
  243. package/dist/memory-use.d.ts +35 -0
  244. package/dist/memory-use.js +109 -0
  245. package/dist/merge-checklist.d.ts +55 -0
  246. package/dist/merge-checklist.js +182 -0
  247. package/dist/modes.d.ts +6 -6
  248. package/dist/modes.js +3 -1
  249. package/dist/needs-you.d.ts +31 -9
  250. package/dist/needs-you.js +110 -41
  251. package/dist/nightly-github.d.ts +2 -0
  252. package/dist/nightly-github.js +4 -4
  253. package/dist/operate-remote-arguments.js +7 -1
  254. package/dist/operate-remote.js +3 -1
  255. package/dist/operate.d.ts +6 -1
  256. package/dist/operate.js +565 -64
  257. package/dist/phase-routing.js +2 -2
  258. package/dist/plain-schema.d.ts +23 -0
  259. package/dist/plain-schema.js +94 -0
  260. package/dist/plain-words.d.ts +20 -0
  261. package/dist/plain-words.js +66 -0
  262. package/dist/plan-login.d.ts +14 -0
  263. package/dist/plan-login.js +58 -0
  264. package/dist/planner.d.ts +1 -1
  265. package/dist/planner.js +4 -2
  266. package/dist/process-liveness.d.ts +2 -0
  267. package/dist/process-liveness.js +61 -17
  268. package/dist/project-delete-ui.d.ts +19 -2
  269. package/dist/project-delete-ui.js +29 -1
  270. package/dist/project-delete.js +14 -1
  271. package/dist/project-knowledge.d.ts +18 -11
  272. package/dist/project-knowledge.js +39 -9
  273. package/dist/project-learning.d.ts +2 -1
  274. package/dist/project-learning.js +15 -45
  275. package/dist/project-memory.d.ts +5 -0
  276. package/dist/project-memory.js +22 -2
  277. package/dist/project-tools.d.ts +4 -4
  278. package/dist/project-tools.js +23 -7
  279. package/dist/proof.js +30 -5
  280. package/dist/protected-storage-look.d.ts +1 -0
  281. package/dist/protected-storage-look.js +13 -0
  282. package/dist/protected-storage.d.ts +70 -39
  283. package/dist/protected-storage.js +196 -147
  284. package/dist/provider-auth.js +1 -1
  285. package/dist/provider-capture.d.ts +34 -0
  286. package/dist/provider-capture.js +53 -0
  287. package/dist/publish.js +9 -1
  288. package/dist/pull-request-flow.js +5 -0
  289. package/dist/push.d.ts +18 -2
  290. package/dist/push.js +40 -16
  291. package/dist/release-adapters.d.ts +9 -0
  292. package/dist/release-adapters.js +76 -0
  293. package/dist/release-auto.js +13 -13
  294. package/dist/release-card.d.ts +1 -2
  295. package/dist/release-card.js +11 -0
  296. package/dist/release-carry.d.ts +7 -2
  297. package/dist/release-carry.js +49 -10
  298. package/dist/release-cli.d.ts +4 -0
  299. package/dist/release-cli.js +29 -22
  300. package/dist/release-coverage.js +1 -1
  301. package/dist/release-next.d.ts +48 -0
  302. package/dist/release-next.js +161 -0
  303. package/dist/release.d.ts +20 -1
  304. package/dist/release.js +85 -9
  305. package/dist/remote-destination.d.ts +36 -0
  306. package/dist/remote-destination.js +217 -0
  307. package/dist/remote-main.d.ts +48 -0
  308. package/dist/remote-main.js +84 -5
  309. package/dist/repository-context.d.ts +3 -2
  310. package/dist/repository-context.js +112 -70
  311. package/dist/restart-certification.d.ts +1 -0
  312. package/dist/restart-certification.js +8 -6
  313. package/dist/result-acts.d.ts +27 -19
  314. package/dist/result-acts.js +36 -25
  315. package/dist/result-follow-ups.d.ts +2 -2
  316. package/dist/result-follow-ups.js +18 -4
  317. package/dist/result-review.d.ts +1 -1
  318. package/dist/route-change-check.d.ts +4 -0
  319. package/dist/route-change-check.js +13 -0
  320. package/dist/route-change.d.ts +22 -0
  321. package/dist/route-change.js +89 -0
  322. package/dist/route-hooks.js +3 -2
  323. package/dist/route-results.d.ts +6 -1
  324. package/dist/route-results.js +14 -5
  325. package/dist/run-closeout.d.ts +1 -1
  326. package/dist/run-closeout.js +7 -5
  327. package/dist/runner-process.js +3 -2
  328. package/dist/runner.js +2 -1
  329. package/dist/scope.d.ts +28 -4
  330. package/dist/scope.js +92 -28
  331. package/dist/scout.d.ts +1 -1
  332. package/dist/scout.js +2 -1
  333. package/dist/serve.d.ts +1 -1
  334. package/dist/serve.js +26 -18
  335. package/dist/server/chat.js +16 -14
  336. package/dist/server/chrome.d.ts +1 -1
  337. package/dist/server/chrome.js +26 -35
  338. package/dist/server/console.css +73 -0
  339. package/dist/server/guards.js +3 -0
  340. package/dist/server/home.d.ts +6 -0
  341. package/dist/server/home.js +21 -13
  342. package/dist/server/http.js +1 -1
  343. package/dist/server/kit.js +1 -1
  344. package/dist/server/merge-ready-view.d.ts +10 -0
  345. package/dist/server/merge-ready-view.js +30 -0
  346. package/dist/server/pages-assets.d.ts +1 -0
  347. package/dist/server/pages-assets.js +8 -5
  348. package/dist/server/pages.js +103 -82
  349. package/dist/server/render-chat.js +16 -3
  350. package/dist/server/render-code-map.d.ts +30 -0
  351. package/dist/server/render-code-map.js +137 -0
  352. package/dist/server/render-flaky.d.ts +20 -0
  353. package/dist/server/render-flaky.js +59 -0
  354. package/dist/server/render-follow-through.d.ts +11 -0
  355. package/dist/server/render-follow-through.js +68 -0
  356. package/dist/server/render-history.js +1 -1
  357. package/dist/server/render-new-task.d.ts +26 -1
  358. package/dist/server/render-new-task.js +30 -5
  359. package/dist/server/render-pages.d.ts +1 -175
  360. package/dist/server/render-pages.js +38 -313
  361. package/dist/server/render-projects.d.ts +2 -1
  362. package/dist/server/render-projects.js +9 -7
  363. package/dist/server/render-release.d.ts +2 -0
  364. package/dist/server/render-release.js +3 -3
  365. package/dist/server/render-results.d.ts +2 -0
  366. package/dist/server/render-results.js +68 -68
  367. package/dist/server/render-tasks.d.ts +16 -2
  368. package/dist/server/render-tasks.js +177 -141
  369. package/dist/server/retired-routes.d.ts +17 -0
  370. package/dist/server/retired-routes.js +53 -0
  371. package/dist/server/route-table.d.ts +3 -0
  372. package/dist/server/route-table.js +48 -12
  373. package/dist/server/settings.js +166 -34
  374. package/dist/server/task-views.js +66 -26
  375. package/dist/server/tasks.d.ts +2 -6
  376. package/dist/server/tasks.js +250 -321
  377. package/dist/server/welcome.js +1 -1
  378. package/dist/settings-groups-ui.d.ts +1 -0
  379. package/dist/settings-groups-ui.js +4 -2
  380. package/dist/settings-kit.js +3 -2
  381. package/dist/settings-proofs.js +3 -1
  382. package/dist/slack-chat.js +14 -1
  383. package/dist/steer-mailbox.d.ts +8 -1
  384. package/dist/steer-mailbox.js +164 -6
  385. package/dist/stop-proof.js +6 -4
  386. package/dist/storage-sweep-process.js +1 -1
  387. package/dist/storage-sweep.d.ts +5 -3
  388. package/dist/storage-sweep.js +30 -9
  389. package/dist/storage-ui.js +28 -17
  390. package/dist/storage.d.ts +27 -2
  391. package/dist/storage.js +39 -2
  392. package/dist/store.d.ts +89 -7
  393. package/dist/store.js +386 -114
  394. package/dist/surface.js +67 -6
  395. package/dist/sweep-quarantine.d.ts +1 -0
  396. package/dist/sweep-quarantine.js +6 -4
  397. package/dist/tamper-scan.d.ts +4 -0
  398. package/dist/tamper-scan.js +90 -0
  399. package/dist/task-aliases.d.ts +5 -0
  400. package/dist/task-aliases.js +9 -0
  401. package/dist/task-control.d.ts +2 -1
  402. package/dist/task-control.js +4 -1
  403. package/dist/task-outcome-cli.d.ts +2 -0
  404. package/dist/task-outcome-cli.js +17 -17
  405. package/dist/task-sizing.d.ts +5 -0
  406. package/dist/task-sizing.js +2 -0
  407. package/dist/task-source.d.ts +7 -0
  408. package/dist/task-source.js +14 -0
  409. package/dist/task-status.d.ts +9 -0
  410. package/dist/task-status.js +45 -49
  411. package/dist/teardown-git.d.ts +18 -0
  412. package/dist/teardown-git.js +105 -21
  413. package/dist/telegram-lead.js +23 -6
  414. package/dist/telegram-progress.js +5 -3
  415. package/dist/telegram-status.js +6 -4
  416. package/dist/telegram.js +12 -1
  417. package/dist/test-infra.d.ts +8 -0
  418. package/dist/test-infra.js +18 -0
  419. package/dist/tidy.js +1 -1
  420. package/dist/toolroll-update.js +19 -5
  421. package/dist/tui-contract.d.ts +24 -0
  422. package/dist/tui-source.js +67 -8
  423. package/dist/tui.js +51 -51
  424. package/dist/vanished-retry.d.ts +30 -0
  425. package/dist/vanished-retry.js +174 -0
  426. package/dist/work-index.d.ts +2 -0
  427. package/dist/work-index.js +95 -66
  428. package/dist/work-summary.d.ts +1 -1
  429. package/dist/work-summary.js +7 -2
  430. package/dist/workspace-ui.d.ts +7 -3
  431. package/dist/workspace-ui.js +38 -23
  432. package/dist/worktree.d.ts +12 -3
  433. package/dist/worktree.js +91 -14
  434. package/package.json +2 -1
  435. package/dist/server/render-next.d.ts +0 -63
  436. package/dist/server/render-next.js +0 -109
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Lesson notes (docs/plans/learning-loops.md, "Lesson notes"): when a check failure is followed by a revision whose check
3
+ * explicitly passed the same tests, Toolroll proposes a note saying exactly that — "`<test>` failed in build #F; it
4
+ * passed in revision R, which changed `<files>`". Not a claim that R caused it. A person writes or edits the words and
5
+ * accepts it as a project decision through the memory proposal flow (memory-pass.ts decideProposal); nothing changes
6
+ * before that. Then Toolroll reports how often the test fails afterwards, with no causal claim.
7
+ *
8
+ * The rules (all must hold), checked here when R's check is recorded:
9
+ * 1. F is a builder run whose check failed (its own result), not infrastructure, with at least one case that failed
10
+ * with explicit execution evidence. File-level-only failures get no note.
11
+ * 2. R is a builder run of a revision of F's task, or of F's explicit repair draft, or the next Full check of the same
12
+ * release; R's commit descends from F's (a verified commit walk).
13
+ * 3. R's check passed with the same command and access as F's.
14
+ * 4. Every failed case of F has an explicit `passed` line in R. F's cases join R's advisory `learn` request (never the
15
+ * gate's `watch`) so that can be true.
16
+ * 5. No retry-assisted pass: nothing in R passed on a retry.
17
+ * 6. None of those cases has mixed outcomes at F's tree.
18
+ * 7. Otherwise no note, and R's page says exactly what was recorded: an explicit skip, a test file that isn't in R, or
19
+ * no test result at all. Each has its own words; none is read as another.
20
+ *
21
+ * Tallies are written with each check's results and never pruned, so "since" numbers survive retention. The only writer
22
+ * of `lesson_note` and `lesson_note_tally`, and with flaky.ts of `learning_watch`. A policy path (release-auto.ts
23
+ * EVAL_POLICY). Nothing here changes a check's outcome, a verdict, an approval or a rerun.
24
+ */
25
+ import { createHash } from "node:crypto";
26
+ import { isUnitFile, projectGitDir } from "./check-report.js";
27
+ import { readVerifiedCommit, verifiedBlobs, verifiedTreeDiff } from "./commit-extract.js";
28
+ import { isExactCase } from "./contracts/release-check.js";
29
+ import { readVerifiedArtifact, scanForSecrets } from "./evidence.js";
30
+ import { identityFile, identityWords } from "./flaky.js";
31
+ const sha = (text) => createHash("sha256").update(text).digest("hex");
32
+ const EXPLICIT = "('report','learn')";
33
+ const PATHS_MAX = 200;
34
+ const WALK_MAX = 500;
35
+ export const NOTE_SURFACE = "lesson-note";
36
+ const FINGERPRINT = "note:";
37
+ /** How deep a revision's ancestry is followed looking for the failed check. */
38
+ const ANCESTRY_MAX = 8;
39
+ export function lessonNoteOf(store, proposal) {
40
+ const row = store.handle.prepare("SELECT * FROM lesson_note WHERE proposal = ?").get(proposal);
41
+ if (row === undefined)
42
+ return null;
43
+ return { proposal, repo: String(row["repo"]), failedRun: Number(row["failed_run"]), passingRun: Number(row["passing_run"]), repairChain: row["repair_chain"] == null ? null : Number(row["repair_chain"]), revisionOrigin: row["revision_origin"] == null ? null : String(row["revision_origin"]),
44
+ identities: JSON.parse(String(row["identities_json"])), changedPaths: JSON.parse(String(row["changed_paths_json"])), testFilesChanged: JSON.parse(String(row["test_files_changed_json"])), factsDigest: String(row["facts_digest"]), createdAt: String(row["created_at"]) };
45
+ }
46
+ function factOf(store, run) {
47
+ const row = store.handle.prepare("SELECT * FROM check_fact WHERE run = ?").get(run);
48
+ if (row === undefined)
49
+ return null;
50
+ const text = (key) => row[key] == null ? null : String(row[key]);
51
+ return { run, repo: text("repo"), taskRef: Number(row["task_ref"]), head: text("head"), tree: text("tree"), commandDigest: text("command_digest"), access: text("access"), conditionsClass: text("conditions_class"), purpose: String(row["purpose"]) };
52
+ }
53
+ const receiptIs = (store, run, state) => store.handle.prepare("SELECT 1 FROM check_result WHERE run = ? AND identity = 'check' AND source = 'receipt' AND state = ?").get(run, state) !== undefined;
54
+ const roleOf = (store, run) => { const row = store.handle.prepare("SELECT role FROM run WHERE id = ?").get(run); return row === undefined ? null : String(row["role"]); };
55
+ /** The check F a run revises, by the plane's own records: its repair draft's source, the failed check of the nearest
56
+ * ancestor task with one, or the previous check of the same release. Null when it revises none. */
57
+ function revisedRun(store, runId, taskRef, purpose) {
58
+ const ref = store.handle.prepare("SELECT external_id, revision_of, revision_origin FROM task_ref WHERE id = ?").get(taskRef);
59
+ if (ref === undefined)
60
+ return null;
61
+ const origin = ref["revision_origin"] == null ? null : String(ref["revision_origin"]);
62
+ const draft = store.repairChainForDraft(String(ref["external_id"]));
63
+ if (draft !== null)
64
+ return { failed: draft.sourceRun, chain: draft.id, origin };
65
+ const newestCheck = store.handle.prepare("SELECT f.run FROM check_fact f JOIN run r ON r.id = f.run WHERE f.task_ref = ? AND r.role = 'builder' AND f.run < ? ORDER BY f.run DESC LIMIT 1");
66
+ let cursor = ref["revision_of"] == null ? null : String(ref["revision_of"]);
67
+ for (let depth = 0; cursor !== null && depth < ANCESTRY_MAX; depth++) {
68
+ const parent = store.handle.prepare("SELECT id, revision_of FROM task_ref WHERE backend = 'built-in' AND external_id = ?").get(cursor);
69
+ if (parent === undefined)
70
+ return null;
71
+ const newest = newestCheck.get(Number(parent["id"]), runId);
72
+ if (newest !== undefined)
73
+ return receiptIs(store, Number(newest["run"]), "failed") ? { failed: Number(newest["run"]), chain: null, origin } : null;
74
+ cursor = parent["revision_of"] == null ? null : String(parent["revision_of"]);
75
+ }
76
+ if (purpose === "release") {
77
+ const previous = newestCheck.get(taskRef, runId);
78
+ if (previous !== undefined && receiptIs(store, Number(previous["run"]), "failed"))
79
+ return { failed: Number(previous["run"]), chain: null, origin };
80
+ }
81
+ return null;
82
+ }
83
+ /** A run's failed cases with explicit execution evidence (the report's lines; file-level names don't count). */
84
+ function failedCases(store, run) {
85
+ return store.handle.prepare("SELECT DISTINCT identity FROM check_result WHERE run = ? AND state = 'failed' AND source = 'report' ORDER BY identity").all(run).map(row => String(row["identity"])).filter(isExactCase);
86
+ }
87
+ /**
88
+ * What this run's check should also ask about for learning: the failed cases of the check it revises (rule 4). Advisory:
89
+ * a separate handshake field, never the gate's `watch`. Called before the run's check starts, so its own facts don't
90
+ * exist yet.
91
+ */
92
+ export function revisedFailures(store, runId) {
93
+ const run = store.handle.prepare("SELECT task_ref, role FROM run WHERE id = ?").get(runId);
94
+ if (run === undefined || run["role"] !== "builder")
95
+ return [];
96
+ const taskRef = Number(run["task_ref"]);
97
+ const scoped = store.handle.prepare("SELECT purpose FROM check_fact WHERE task_ref = ? ORDER BY run DESC LIMIT 1").get(taskRef);
98
+ const prior = revisedRun(store, runId, taskRef, scoped === undefined ? null : String(scoped["purpose"]));
99
+ return prior === null ? [] : failedCases(store, prior.failed);
100
+ }
101
+ /** Whether `head` descends from `ancestor`, walking verified commits, bounded. Unreadable is false: no note. */
102
+ function descends(gitDir, head, ancestor) {
103
+ if (head === ancestor)
104
+ return false;
105
+ const seen = new Set(), queue = [head];
106
+ while (queue.length > 0 && seen.size < WALK_MAX) {
107
+ const at = queue.shift();
108
+ if (at === ancestor)
109
+ return true;
110
+ if (seen.has(at))
111
+ continue;
112
+ seen.add(at);
113
+ const commit = readVerifiedCommit(gitDir, at);
114
+ if (!commit.ok)
115
+ return false;
116
+ queue.push(...commit.value.parents);
117
+ }
118
+ return false;
119
+ }
120
+ /** Test and harness files, as a reader would call them. Every one R changed is listed, never hidden. */
121
+ export function testOrHarnessPath(path) {
122
+ return isUnitFile(path) || /^(?:tests?|scripts|\.github)\//.test(path) || /(?:^|\/)(?:vitest|jest|playwright)[^/]*\.[a-z]+$/.test(path) || /(?:^|\/)package(?:-lock)?\.json$/.test(path);
123
+ }
124
+ /** Text from a candidate, made safe to show: one line, clipped, control-free; anything secret-shaped is withheld. */
125
+ function safeText(text, max) {
126
+ // eslint-disable-next-line no-control-regex
127
+ const line = text.replace(/[\u0000-\u001f\u007f\u2028\u2029]+/g, " ").trim();
128
+ if (scanForSecrets(line).length > 0)
129
+ return "(text withheld)";
130
+ return line.length > max ? `${line.slice(0, max - 1)}…` : line;
131
+ }
132
+ /** The first failure line of F's sealed log, as output: clipped, secret-scanned, null when there is none to read. */
133
+ function firstFailureLine(store, root, run) {
134
+ const log = store.artifactsFor(run).find(one => one.kind === "check-log");
135
+ if (log === undefined)
136
+ return null;
137
+ const read = readVerifiedArtifact(root, log);
138
+ if (!read.ok)
139
+ return null;
140
+ const line = read.content.toString("utf8").split("\n").find(one => /\b(?:FAIL|AssertionError|Error)\b/.test(one));
141
+ return line === undefined ? null : safeText(line, 160);
142
+ }
143
+ /**
144
+ * Consider a note for run R, once its check is recorded. Returns what happened; a note that can't be proposed for a
145
+ * reason a person should see says so on R (rule 7). Any unreadable fact means no note.
146
+ */
147
+ export function considerLessonNote(store, root, runId, now) {
148
+ const passing = factOf(store, runId);
149
+ if (passing === null || passing.repo === null || passing.head === null || passing.tree === null || roleOf(store, runId) !== "builder")
150
+ return { none: "no comparable check" };
151
+ if (!receiptIs(store, runId, "passed"))
152
+ return { none: "its check didn't pass" };
153
+ const prior = revisedRun(store, runId, passing.taskRef, passing.purpose);
154
+ if (prior === null)
155
+ return { none: "it revises no failed check" };
156
+ const failing = factOf(store, prior.failed);
157
+ if (failing === null || failing.repo !== passing.repo || failing.head === null || failing.tree === null)
158
+ return { none: "the failed check isn't comparable" };
159
+ if (roleOf(store, prior.failed) !== "builder" || !receiptIs(store, prior.failed, "failed"))
160
+ return { none: "the earlier run isn't a failed build" };
161
+ if (store.handle.prepare("SELECT 1 FROM run WHERE id = ? AND exec_class = 'infrastructure'").get(prior.failed) !== undefined)
162
+ return { none: "the failure was the machine's" };
163
+ const identities = failedCases(store, prior.failed);
164
+ if (identities.length === 0)
165
+ return { none: "no failed case has explicit test results" };
166
+ const gitDir = projectGitDir(passing.repo);
167
+ if (gitDir === null || !descends(gitDir, passing.head, failing.head))
168
+ return { none: "the revision doesn't descend from the failed commit" };
169
+ if (passing.commandDigest !== failing.commandDigest || passing.access !== failing.access)
170
+ return { none: "the two checks ran different commands or access" };
171
+ const commit = passing.head.slice(0, 7);
172
+ const said = (text) => { store.addRunNote(runId, "Toolroll", text, now); return { none: text }; };
173
+ const state = store.handle.prepare(`SELECT state FROM check_result WHERE run = ? AND identity = ? AND source IN ${EXPLICIT}`);
174
+ const missing = [];
175
+ for (const identity of identities) {
176
+ const states = state.all(runId, identity).map(row => String(row["state"]));
177
+ const name = safeText(identityWords(identity), 200);
178
+ if (states.includes("passed-on-retry"))
179
+ missing.push(`\`${name}\` passed only on a retry in revision ${commit}.`);
180
+ else if (states.includes("passed") && !states.includes("failed"))
181
+ continue;
182
+ else if (states.includes("skipped"))
183
+ missing.push(`\`${name}\` was skipped in revision ${commit}.`);
184
+ else {
185
+ const file = identityFile(identity);
186
+ const blobs = file === null ? null : verifiedBlobs(gitDir, passing.tree, [file]);
187
+ if (file !== null && blobs !== null && blobs.ok && !blobs.value.has(file))
188
+ missing.push(`The test file \`${safeText(file, 200)}\` is not in revision ${commit}.`);
189
+ else
190
+ missing.push(`No test result recorded for \`${name}\` in revision ${commit}.`);
191
+ }
192
+ }
193
+ if (missing.length > 0)
194
+ return said(`No lesson note: ${missing.slice(0, 10).join(" ")}`);
195
+ if (store.handle.prepare("SELECT 1 FROM check_result WHERE run = ? AND state = 'passed-on-retry' LIMIT 1").get(runId) !== undefined)
196
+ return said(`No lesson note: something in revision ${commit} passed only on a retry.`);
197
+ const mixed = store.handle.prepare("SELECT 1 FROM local_observation WHERE identity IN (SELECT value FROM json_each(?)) AND tree = ? LIMIT 1").get(JSON.stringify(identities), failing.tree);
198
+ if (mixed !== undefined)
199
+ return said(`No lesson note: the test that failed in build #${prior.failed} has mixed outcomes at that commit.`);
200
+ const before = readVerifiedCommit(gitDir, failing.head), after = readVerifiedCommit(gitDir, passing.head);
201
+ if (!before.ok || !after.ok)
202
+ return { none: "a commit couldn't be read" };
203
+ const diff = verifiedTreeDiff(gitDir, before.value.tree, after.value.tree);
204
+ if (!diff.ok)
205
+ return { none: "the change couldn't be read" };
206
+ const changed = diff.value.map(one => one.path).sort();
207
+ const testFiles = changed.filter(testOrHarnessPath);
208
+ const fingerprint = `${FINGERPRINT}${sha(identities.join("\n")).slice(0, 32)}`;
209
+ const quote = firstFailureLine(store, root, prior.failed);
210
+ const names = identities.map(one => safeText(identityWords(one), 200));
211
+ const shown = (paths) => `${paths.slice(0, 20).map(one => safeText(one, 200)).join(", ")}${paths.length > 20 ? ` and ${paths.length - 20} more` : ""}`;
212
+ const facts = `${names.map(one => `\`${one}\``).join(", ")} failed in build #${prior.failed} (${failing.head.slice(0, 7)}, ${failing.conditionsClass ?? "conditions not recorded"}). It passed in revision ${commit} (build #${runId}). That revision changed: ${shown(changed) || "nothing readable"}.${testFiles.length === 0 ? "" : ` Tests and harness changed: ${shown(testFiles)}.`}`;
213
+ const draft = safeText(`${names[0]}${names.length > 1 ? ` and ${names.length - 1} more` : ""} failed in build #${prior.failed} and passed in revision ${commit}.`, 200);
214
+ return store.transact(() => {
215
+ const decided = store.handle.prepare("SELECT 1 FROM memory_rejection WHERE repo = ? AND fingerprint = ?").get(passing.repo, fingerprint) !== undefined
216
+ || store.handle.prepare("SELECT 1 FROM memory_proposal WHERE repo = ? AND fingerprint = ? AND status = 'pending'").get(passing.repo, fingerprint) !== undefined;
217
+ if (decided)
218
+ return { none: "already proposed or rejected" };
219
+ const at = now.toISOString();
220
+ const inserted = store.handle.prepare("INSERT INTO memory_proposal(repo,kind,fingerprint,title,rationale,before_text,after_text,evidence,sessions,status,created_at,surface) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)")
221
+ .run(passing.repo, "decision-add", fingerprint, `Lesson note: ${safeText(names[0], 80)}`, facts, null, draft, JSON.stringify(quote === null ? [] : [{ session: `build:${prior.failed}`, quote }]), 1, "pending", at, NOTE_SURFACE);
222
+ const proposal = Number(inserted.lastInsertRowid);
223
+ store.handle.prepare(`INSERT INTO lesson_note (proposal, repo, failed_run, passing_run, repair_chain, revision_origin, identities_json, changed_paths_json, test_files_changed_json, facts_digest, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
224
+ .run(proposal, passing.repo, prior.failed, runId, prior.chain, prior.origin, JSON.stringify(identities), JSON.stringify(changed.slice(0, PATHS_MAX)), JSON.stringify(testFiles), sha(facts), at);
225
+ return { proposal };
226
+ });
227
+ }
228
+ // ---- accepted notes ------------------------------------------------------------------------------------------------
229
+ const sourceRefOf = (proposal) => `lesson-note:${proposal}`;
230
+ const noteOfDecision = (ref) => { const found = ref === null ? null : /^lesson-note:(\d+)$/.exec(ref); return found === null ? null : Number(found[1]); };
231
+ /** Track an accepted note's cases in every later Full check, whether or not a run received it. */
232
+ export function watchNote(store, input) {
233
+ const sourceRef = input.decisionRevision === undefined ? `note:${input.proposal}` : `note:${input.proposal}:${input.decisionRevision}`;
234
+ for (const identity of input.identities)
235
+ store.handle.prepare(`INSERT INTO learning_watch (repo, identity, reason, source_ref, since, until) VALUES (?, ?, 'note', ?, ?, NULL) ON CONFLICT (repo, identity, reason, source_ref) DO NOTHING`).run(input.repo, identity, sourceRef, input.now.toISOString());
236
+ }
237
+ /** A note's tracking ends when its decision stops being active. */
238
+ export function endNoteWatch(store, decisionSourceRef, now) {
239
+ const proposal = noteOfDecision(decisionSourceRef);
240
+ if (proposal === null)
241
+ return;
242
+ store.handle.prepare("UPDATE learning_watch SET until = ? WHERE reason = 'note' AND (source_ref = ? OR source_ref LIKE ?) AND until IS NULL").run(now.toISOString(), `note:${proposal}`, `note:${proposal}:%`);
243
+ }
244
+ export const lessonNoteSourceRef = sourceRefOf;
245
+ function addTally(store, note, month, add, partial = 0) {
246
+ const was = store.handle.prepare("SELECT sources_json FROM lesson_note_tally WHERE note = ? AND month = ?").get(note, month);
247
+ const sources = JSON.stringify([...new Set([...(was === undefined ? [] : JSON.parse(String(was["sources_json"]))), ...add.sources])].sort());
248
+ store.handle.prepare(`INSERT INTO lesson_note_tally (note, month, executions, failures, mixed_failures, received_executions, received_failures, unreported_checks, sources_json, partial) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
249
+ ON CONFLICT (note, month) DO UPDATE SET executions = executions + excluded.executions, failures = failures + excluded.failures, mixed_failures = mixed_failures + excluded.mixed_failures,
250
+ received_executions = received_executions + excluded.received_executions, received_failures = received_failures + excluded.received_failures, unreported_checks = unreported_checks + excluded.unreported_checks,
251
+ sources_json = excluded.sources_json, partial = MAX(partial, excluded.partial)`).run(note, month, add.executions, add.failures, add.mixed, add.receivedExecutions, add.receivedFailures, add.unreported, sources, partial);
252
+ }
253
+ const activeNotes = (store, repo) => store.handle.prepare(`SELECT n.proposal, n.identities_json, d.id AS decision, d.decided_at FROM lesson_note n JOIN project_decision d ON d.repo = n.repo AND d.source_ref = 'lesson-note:' || n.proposal AND d.status = 'active' WHERE n.repo = ?`).all(repo)
254
+ .map(row => ({ note: Number(row["proposal"]), decision: Number(row["decision"]), identities: JSON.parse(String(row["identities_json"])), since: String(row["decided_at"]) }));
255
+ const monthOf = (now) => now.toISOString().slice(0, 7);
256
+ /**
257
+ * Count one check for every accepted note, in the transaction that records its results: distinct `(run, identity)`
258
+ * pairs with explicit results, split by whether the run received the decision. A check with no line for the note's
259
+ * tests counts as unreported. `observations`: what this check just added, so a failure elsewhere that turned out to
260
+ * have mixed outcomes is counted as such.
261
+ */
262
+ export function tallyCheck(store, runId, repo, observations, now) {
263
+ const notes = activeNotes(store, repo);
264
+ if (notes.length === 0)
265
+ return;
266
+ const month = monthOf(now);
267
+ const results = store.handle.prepare(`SELECT state, source FROM check_result WHERE run = ? AND identity = ? AND source IN ${EXPLICIT}`);
268
+ const mixedHere = store.handle.prepare("SELECT 1 FROM local_observation WHERE identity = ? AND failed_run = ? LIMIT 1");
269
+ for (const one of notes) {
270
+ const received = store.handle.prepare("SELECT 1 FROM memory_use WHERE run = ? AND kind = 'decision' AND item = ?").get(runId, String(one.decision)) !== undefined;
271
+ const tally = { executions: 0, failures: 0, mixed: 0, receivedExecutions: 0, receivedFailures: 0, unreported: 0, sources: new Set() };
272
+ for (const identity of one.identities) {
273
+ const rows = results.all(runId, identity).filter(row => row["state"] !== "skipped");
274
+ if (rows.length === 0)
275
+ continue;
276
+ const failed = rows.some(row => row["state"] === "failed" || row["state"] === "passed-on-retry");
277
+ tally.executions++;
278
+ if (received)
279
+ tally.receivedExecutions++;
280
+ for (const row of rows)
281
+ tally.sources.add(String(row["source"]));
282
+ if (failed) {
283
+ tally.failures++;
284
+ if (received)
285
+ tally.receivedFailures++;
286
+ if (mixedHere.get(identity, runId) !== undefined)
287
+ tally.mixed++;
288
+ }
289
+ }
290
+ if (tally.executions === 0)
291
+ tally.unreported = 1;
292
+ addTally(store, one.note, month, tally);
293
+ }
294
+ // Observations this check added about an earlier failure: that failure is now known to have mixed outcomes.
295
+ for (const id of observations) {
296
+ const row = store.handle.prepare("SELECT identity, failed_run FROM local_observation WHERE id = ?").get(id);
297
+ if (row === undefined || Number(row["failed_run"]) === runId)
298
+ continue;
299
+ const at = store.handle.prepare("SELECT recorded_at FROM check_fact WHERE run = ?").get(Number(row["failed_run"]));
300
+ for (const one of notes)
301
+ if (one.identities.includes(String(row["identity"])) && at !== undefined && String(at["recorded_at"]) >= one.since) {
302
+ addTally(store, one.note, month, { executions: 0, failures: 0, mixed: 1, receivedExecutions: 0, receivedFailures: 0, unreported: 0, sources: new Set() });
303
+ }
304
+ }
305
+ }
306
+ /** A month whose check couldn't be recorded is partial, and says so. */
307
+ export function markTalliesPartial(store, repo, now) {
308
+ for (const one of activeNotes(store, repo))
309
+ addTally(store, one.note, monthOf(now), { executions: 0, failures: 0, mixed: 0, receivedExecutions: 0, receivedFailures: 0, unreported: 0, sources: new Set() }, 1);
310
+ }
311
+ /** What a note's tallies say since it was accepted: sums of durable rows, unchanged by retention. */
312
+ export function noteAfterwards(store, decision) {
313
+ const note = noteOfDecision(decision.sourceRef);
314
+ if (note === null)
315
+ return null;
316
+ const rows = store.handle.prepare("SELECT * FROM lesson_note_tally WHERE note = ?").all(note);
317
+ const sum = (key) => rows.reduce((total, row) => total + Number(row[key]), 0);
318
+ return { since: decision.decidedAt, executions: sum("executions"), failures: sum("failures"), mixed: sum("mixed_failures"), receivedExecutions: sum("received_executions"), receivedFailures: sum("received_failures"), unreported: sum("unreported_checks"),
319
+ partial: rows.some(row => Number(row["partial"]) === 1), sources: [...new Set(rows.flatMap(row => JSON.parse(String(row["sources_json"]))))].sort() };
320
+ }
321
+ const DAY = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", timeZone: "UTC" });
322
+ /** "Since Nov 5: failed 1 of 31 runs of this test (0 of 18 that received this note; 1 of 13 that didn't). …". No "helped". */
323
+ export function afterwardsWords(after) {
324
+ const others = after.executions - after.receivedExecutions, otherFailures = after.failures - after.receivedFailures;
325
+ return [
326
+ `Since ${DAY.format(new Date(after.since))}: failed ${after.failures} of ${after.executions} run${after.executions === 1 ? "" : "s"} of this test (${after.receivedFailures} of ${after.receivedExecutions} that received this note; ${otherFailures} of ${others} that didn't).`,
327
+ after.mixed === 0 ? "" : `${after.mixed} of these had mixed outcomes.`,
328
+ after.unreported === 0 ? "" : `${after.unreported} check${after.unreported === 1 ? "" : "s"} since had no per-test reporting.`,
329
+ after.partial ? "Partial: not every check was counted." : "",
330
+ ].filter(Boolean).join(" ");
331
+ }
332
+ /** What waits for a person, for pages and chat. */
333
+ export function memoryWaiting(store, repo) {
334
+ return { notes: Number(store.handle.prepare("SELECT COUNT(*) AS n FROM memory_proposal WHERE repo = ? AND status = 'pending' AND surface = ?").get(repo, NOTE_SURFACE)?.["n"] ?? 0),
335
+ cleanups: Number(store.handle.prepare("SELECT COUNT(*) AS n FROM memory_tidy_proposal WHERE repo = ? AND status = 'pending'").get(repo)?.["n"] ?? 0) };
336
+ }
package/dist/limits-ui.js CHANGED
@@ -72,7 +72,7 @@ export function usageChipOf(windows, now) {
72
72
  // A window with no known length or reset has no reset to name.
73
73
  const reset = old ? "window reset" : top.resetsAt === null || top.windowMinutes === null ? "resets: unknown" : `resets ${shortReset(top.resetsAt, now)}`;
74
74
  const text = `${PROVIDER_NAMES[top.provider] ?? top.provider} ${windowLabel(top.window, top.windowMinutes)} · ${Math.round(top.usedPercent)}% · ${reset}${stale ? ` · as of ${ago(top.observedAt, now)}` : ""}`;
75
- return { text, tone: old ? "neutral" : toneOf(top.usedPercent, top.reached), stale, href: "/work", title: `What the providers last reported: ${text}` };
75
+ return { text, tone: old ? "neutral" : toneOf(top.usedPercent, top.reached), stale, href: "/tasks", title: `What the providers last reported: ${text}` };
76
76
  }
77
77
  /** The same tiles for the page without scripts. */
78
78
  export function limitsHtml(limits) {
@@ -10,7 +10,7 @@ export declare function authorityOf(value: unknown): Authority | null;
10
10
  * The acts a local owner may do (plan C1). Ids only: each surface (a route row, a chat action, a CLI command) names
11
11
  * the act it is, and an act not in this list is refused to a local owner.
12
12
  */
13
- export declare const LOCAL_OWNER_ACTS: readonly ["file-task", "lead-send", "approve-plan", "answer-question", "stop-run", "pause-run", "resume-run", "complete-task", "request-revision", "archive-task", "add-project", "choose-default-agent", "mark-seen", "watch-agent", "read", "routing-turn-off", "dismiss-suggestion", "trial-stop"];
13
+ export declare const LOCAL_OWNER_ACTS: readonly ["file-task", "lead-send", "approve-plan", "build-now", "answer-question", "stop-run", "pause-run", "resume-run", "complete-task", "request-revision", "archive-task", "add-project", "choose-default-agent", "mark-seen", "watch-agent", "read", "routing-turn-off", "dismiss-suggestion", "trial-stop", "code-map-switch", "memory-review"];
14
14
  export type LocalOwnerAct = (typeof LOCAL_OWNER_ACTS)[number];
15
15
  /** Whether a runtime string names an act a local owner may do. */
16
16
  export declare function localOwnerAct(value: unknown): value is LocalOwnerAct;
@@ -20,6 +20,8 @@ export const LOCAL_OWNER_ACTS = Object.freeze([
20
20
  "file-task",
21
21
  "lead-send",
22
22
  "approve-plan",
23
+ // A fast start: file and approve in one step (fast-start.ts). Everything else this act could reach is refused.
24
+ "build-now",
23
25
  "answer-question",
24
26
  "stop-run",
25
27
  "pause-run",
@@ -39,6 +41,12 @@ export const LOCAL_OWNER_ACTS = Object.freeze([
39
41
  "dismiss-suggestion",
40
42
  // Slice 3: stopping a trial of a cheaper model only lowers (proveRuleDoor checks this exact act).
41
43
  "trial-stop",
44
+ // The code map (docs/plans/code-map.md §5): on or off for one project only, the same kind of setting as the default
45
+ // agent. Never a refresh, and never the end of a holdout (code-map-acts.ts refuses those by name).
46
+ "code-map-switch",
47
+ // Learning loops (D3): deciding a lesson note or a clean-up of project memory. The writers (memory-pass.ts decideProposal,
48
+ // memory-tidy.ts) check this exact act; instruction proposals stay with full authority.
49
+ "memory-review",
42
50
  ]);
43
51
  const ACT_SET = new Set(LOCAL_OWNER_ACTS);
44
52
  /** Whether a runtime string names an act a local owner may do. */
@@ -157,10 +165,18 @@ export const CHAT_ACTION_LOCAL_ACTS = Object.freeze({
157
165
  subagent_create: null, subagent_soul: null, subagent_state: null, subagent_note: null, subagent_ask: null,
158
166
  subagent_answer: null, subagent_tools: null, subagent_memory: null, subagent_routine: null, subagent_undo: null,
159
167
  kit_setup: null, decision_record: null, decision_retire: null, lead_about_you: null,
168
+ // Chat and console parity: decided one by one, and none is a local owner's act yet. A password opens them.
169
+ mode_revoke: null, flow_unattended_off: null, flow_archive: null, provider_resume: null, worker_note: null, lead_identity: null,
170
+ task_archive: null, task_restore: null, incident_resolve: null, memory_decide: null,
171
+ finding_send: null, finding_resend: null,
172
+ task_reopen: null, revision_accept: null, revision_reject: null, flow_unattended: null,
160
173
  // Results routing: Not now only lowers (the rule door checks this exact act).
161
174
  suggestion_not_now: "dismiss-suggestion",
162
175
  // Stopping a trial only lowers (the rule door checks this exact act).
163
176
  trial_stop: "trial-stop",
177
+ // On or off for one project; never the end of a holdout.
178
+ code_map_switch: "code-map-switch",
179
+ code_map_holdout_end: null,
164
180
  scope_approve: "approve-plan",
165
181
  result_accept: "complete-task",
166
182
  task_cancel: "stop-run",
@@ -0,0 +1,16 @@
1
+ /** A check the machine runs, said as what it is (docs/plans/plain-toolroll.md, Phase 1 follow-up from a release's own
2
+ * page): a task with a prepared candidate is checked out by the machine and no agent runs, so its status says the check
3
+ * that is running, its step and its progress, from the check's own records, never "an agent", and it has no notes to
4
+ * read and no word to send. Presentation only: it reads the run, its check progress and the release's steps. */
5
+ import type { Store } from "./store.js";
6
+ /** The words for a machine-run check now running on `runId`, or null when an agent builds this task (or nothing runs). */
7
+ export declare function machineCheckWords(store: Store, taskId: string, runId: number | null, release: {
8
+ at?: {
9
+ step: string;
10
+ name: string;
11
+ index: number;
12
+ of: number;
13
+ } | null;
14
+ } | null, now: Date): string | null;
15
+ /** Whether this task's builds are the machine's own checks (a prepared candidate), where no agent runs. */
16
+ export declare function machineChecked(store: Store, taskId: string): boolean;
@@ -0,0 +1,26 @@
1
+ /** The words for a machine-run check now running on `runId`, or null when an agent builds this task (or nothing runs). */
2
+ export function machineCheckWords(store, taskId, runId, release, now) {
3
+ if (runId === null || store.getScope(taskId)?.candidate == null)
4
+ return null;
5
+ const run = store.getRun(runId);
6
+ if (run === null || run.outcome !== null || run.finishedAt !== null)
7
+ return null;
8
+ let progress = null;
9
+ try {
10
+ progress = store.checkProgress(runId)?.line ?? null;
11
+ }
12
+ catch {
13
+ progress = null;
14
+ }
15
+ const started = Date.parse(run.startedAt);
16
+ const minutes = Number.isFinite(started) ? Math.max(0, Math.floor((now.getTime() - started) / 60_000)) : null;
17
+ const elapsed = minutes === null ? "" : minutes < 1 ? " · started just now" : ` · ${minutes} min`;
18
+ const at = release?.at ?? null;
19
+ const head = at === null ? "Running the project check on the prepared version"
20
+ : at.step === "check" ? `Running the release check · step ${at.index} of ${at.of}` : `${at.name} · step ${at.index} of ${at.of}`;
21
+ return `${head}${progress === null || progress === "" ? "" : ` · ${progress}`}${elapsed}`;
22
+ }
23
+ /** Whether this task's builds are the machine's own checks (a prepared candidate), where no agent runs. */
24
+ export function machineChecked(store, taskId) {
25
+ return store.getScope(taskId)?.candidate != null;
26
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Which computer is "this one" for process custody (process witnesses, workspace notes, quarantine journals, a coding
3
+ * agent's custody, runner records, eval campaigns).
4
+ *
5
+ * The network hostname is not an identity: on a Mac with no HostName set, os.hostname() follows the network
6
+ * ("Studio-A.local" on one network, "studio-a.localdomain" on another). A witness recorded under the
7
+ * old name then read as another computer's forever, and a finished build waited on that computer to confirm it
8
+ * stopped (runs 3312 and 3313).
9
+ *
10
+ * So each witness carries this machine's stable id, read from the OS and hashed with a Toolroll-only prefix (the raw
11
+ * id is never stored):
12
+ *
13
+ * darwin IOPlatformUUID (ioreg -rd1 -c IOPlatformExpertDevice): fixed per machine
14
+ * linux /etc/machine-id (or /var/lib/dbus/machine-id): fixed per installation; a container reads as unknown, since
15
+ * containers made from one image can share it
16
+ * win32 HKLM\SOFTWARE\Microsoft\Cryptography MachineGuid
17
+ *
18
+ * THE RULE (isThisMachine): a witness with an id is this machine's exactly when the id equals ours, and never when ours
19
+ * is unknown. A witness without one (written before v150, or by a process that couldn't read its id) is this
20
+ * machine's when its host string is one of this machine's own names: os.hostname() as it reads now, the names macOS
21
+ * configures for it (scutil HostName, LocalHostName + ".local"), and the hostnames recorded beside this machine's own
22
+ * id (machine_host_alias, written only by a process that read that id here, with the hostname it read here at the
23
+ * same moment). No name ever enters the set from a witness, a note, a runner record or anything another computer
24
+ * wrote, so another computer can only match by holding one of this machine's own names: the same coincidence the
25
+ * plain hostname comparison always accepted, and never for a witness that carries a different id.
26
+ */
27
+ export type MachineIdentity = {
28
+ ok: true;
29
+ id: string;
30
+ source: "darwin-platform-uuid" | "linux-machine-id" | "win32-machine-guid" | "injected";
31
+ } | {
32
+ ok: false;
33
+ reason: "unsupported-platform" | "unreadable" | "malformed" | "container";
34
+ detail: string;
35
+ };
36
+ export type MachineIdentityReaders = {
37
+ platform?: NodeJS.Platform;
38
+ readFile?: (path: string) => string;
39
+ exists?: (path: string) => boolean;
40
+ run?: (file: string, args: string[]) => string;
41
+ };
42
+ /** The stored form of a raw OS id: hashed under a Toolroll-only prefix, so the record never holds the raw id. */
43
+ export declare function hashMachineId(source: string, raw: string): string;
44
+ /** Read this machine's id from the OS, never cached here (machineIdentity() caches it). */
45
+ export declare function readMachineIdentity(readers?: MachineIdentityReaders): MachineIdentity;
46
+ /** The names macOS configures for this machine (not the network's): HostName when set, and LocalHostName.local
47
+ * (Bonjour keeps it unique on its network). Empty elsewhere or when scutil can't answer. */
48
+ export declare function readConfiguredHostNames(readers?: MachineIdentityReaders): string[];
49
+ /** This machine's identity, read once per process. */
50
+ export declare function machineIdentity(): MachineIdentity;
51
+ /** The id to stamp on a new witness, or null when this machine's id is unknown. */
52
+ export declare function currentMachineId(): string | null;
53
+ /** Tests only: pin (or clear, with null) this process's machine id and configured names, and forget remembered names. */
54
+ export declare function injectMachineIdentity(identity: MachineIdentity | null, configured?: string[] | null): void;
55
+ /** Note hostnames recorded beside `id` in this machine's own records. Names for another id are kept apart and never
56
+ * read as this machine's. */
57
+ export declare function rememberHostAliases(id: string, hosts: Iterable<string>): void;
58
+ /** The names this machine is known by right now, from this machine alone: its hostname as it reads now and the
59
+ * names macOS configures for it. What a new witness records beside its id. */
60
+ export declare function liveHostAliases(): string[];
61
+ export type ThisMachine = {
62
+ id: string | null;
63
+ hosts: string[];
64
+ };
65
+ /** This machine: its id (null when unknown) and every name it is known by: its live names, plus the names recorded
66
+ * beside its id when the id is known. */
67
+ export declare function thisMachine(extra?: Iterable<string>): ThisMachine;
68
+ /** THE RULE (module comment): whether a witness names this machine. */
69
+ export declare function isThisMachine(witness: {
70
+ host: string;
71
+ machineId?: string | null;
72
+ }, here?: ThisMachine): boolean;
73
+ /** Whether a recorded host is the machine a caller named by `here`: exact, or, when `here` is this machine's hostname
74
+ * as it reads now, any of this machine's names (a caller that passes another host, as a test does, compares exactly). */
75
+ export declare function sameMachineHost(seen: string, here?: string, machineId?: string | null): boolean;