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,60 @@
1
+ import { type FlakyList, type FlakyRow } from "./flaky.js";
2
+ import type { Store } from "./store.js";
3
+ export declare const FLAKY_DESCRIPTORS: readonly [{
4
+ readonly action: "list";
5
+ readonly synopsis: "tests with mixed outcomes under recorded conditions (read only)";
6
+ readonly mutation: "none";
7
+ readonly takesQuery: false;
8
+ readonly flags: readonly [{
9
+ readonly name: "repo";
10
+ readonly takesValue: true;
11
+ readonly meaning: "admitted project checkout root";
12
+ }, {
13
+ readonly name: "days";
14
+ readonly takesValue: true;
15
+ readonly meaning: "how far back to look (default 90)";
16
+ }, {
17
+ readonly name: "json";
18
+ readonly takesValue: false;
19
+ readonly meaning: "versioned structured response";
20
+ }, {
21
+ readonly name: "db";
22
+ readonly takesValue: true;
23
+ readonly meaning: "local Toolroll database";
24
+ }];
25
+ }, {
26
+ readonly action: "show";
27
+ readonly synopsis: "one row's history, and the task that would fix it: flaky show <identity> (read only)";
28
+ readonly mutation: "none";
29
+ readonly takesQuery: true;
30
+ readonly flags: readonly [{
31
+ readonly name: "repo";
32
+ readonly takesValue: true;
33
+ readonly meaning: "admitted project checkout root";
34
+ }, {
35
+ readonly name: "days";
36
+ readonly takesValue: true;
37
+ readonly meaning: "how far back to look (default 90)";
38
+ }, {
39
+ readonly name: "json";
40
+ readonly takesValue: false;
41
+ readonly meaning: "versioned structured response";
42
+ }, {
43
+ readonly name: "db";
44
+ readonly takesValue: true;
45
+ readonly meaning: "local Toolroll database";
46
+ }];
47
+ }];
48
+ export type FlakyCliContext = {
49
+ store: Store;
50
+ write: (line: string) => void;
51
+ json: boolean;
52
+ now: Date;
53
+ actor: string | null;
54
+ repos: readonly string[];
55
+ };
56
+ /** A row's one line of status, the same words the page says. */
57
+ export declare function flakyStatusWords(row: FlakyRow): string;
58
+ /** How much of the window had per-test reporting, said plainly. */
59
+ export declare function flakyCoverageWords(list: FlakyList): string;
60
+ export declare function runFlakyCommand(positional: readonly string[], options: Map<string, string | true>, context: FlakyCliContext): number;
@@ -0,0 +1,60 @@
1
+ /** `toolroll flaky list|show`: the flaky tests list from the terminal, read only. Rerunning stays `task regate`; fixing
2
+ * one is an ordinary task a person approves (`show` prints it). Nothing here starts a model or changes anything. */
3
+ import { envelopeJson } from "./envelope.js";
4
+ import { FLAKY_WINDOW_DAYS, flakyList, flakyShow } from "./flaky.js";
5
+ const flags = [
6
+ { name: "repo", takesValue: true, meaning: "admitted project checkout root" },
7
+ { name: "days", takesValue: true, meaning: `how far back to look (default ${FLAKY_WINDOW_DAYS})` },
8
+ { name: "json", takesValue: false, meaning: "versioned structured response" },
9
+ { name: "db", takesValue: true, meaning: "local Toolroll database" },
10
+ ];
11
+ export const FLAKY_DESCRIPTORS = [
12
+ { action: "list", synopsis: "tests with mixed outcomes under recorded conditions (read only)", mutation: "none", takesQuery: false, flags },
13
+ { action: "show", synopsis: "one row's history, and the task that would fix it: flaky show <identity> (read only)", mutation: "none", takesQuery: true, flags },
14
+ ];
15
+ const day = (iso) => iso.slice(0, 10);
16
+ /** A row's one line of status, the same words the page says. */
17
+ export function flakyStatusWords(row) {
18
+ return row.status === "mixed outcomes" ? "Mixed outcomes" : row.status === "no failures since its file changed" ? "No failures since its file changed" : "Not enough recorded history";
19
+ }
20
+ /** How much of the window had per-test reporting, said plainly. */
21
+ export function flakyCoverageWords(list) {
22
+ const checks = list.reported + list.unreported;
23
+ return `Reported by ${list.reported} of ${checks} check${checks === 1 ? "" : "s"}${list.unreported === 0 ? "" : `; ${list.unreported} had no per-test reporting`}.`;
24
+ }
25
+ const rowLine = (row) => `${row.name}\n ${flakyStatusWords(row)} · ${row.observations} in window · last seen ${day(row.lastSeen)} · ${row.conditions.join(", ") || "conditions not recorded"} · ${row.hardFailures} hard failure${row.hardFailures === 1 ? "" : "s"} · ${row.sources.join(", ") || "no source recorded"}${row.fileChanged === null ? "" : ` · file changed ${day(row.fileChanged)}`}\n ${row.key}`;
26
+ export function runFlakyCommand(positional, options, context) {
27
+ const action = positional[0];
28
+ const command = action ? `flaky ${action}` : "flaky";
29
+ const fail = (reason, message) => { context.write(context.json ? envelopeJson({ ok: false, command, reason, message }) : message); return reason === "usage" ? 2 : 1; };
30
+ const ok = (result, lines) => { context.write(context.json ? envelopeJson({ ok: true, command, ...result }) : lines.join("\n")); return 0; };
31
+ if (!FLAKY_DESCRIPTORS.some(one => one.action === action))
32
+ return fail("usage", "Use flaky list | show <identity> [--repo PATH] [--days N].");
33
+ if (context.actor === null)
34
+ return fail("unauthenticated", "Sign in first: the remembered local login drives flaky commands.");
35
+ const given = options.get("repo");
36
+ const repo = typeof given === "string" ? given : context.repos.length === 1 ? context.repos[0] : null;
37
+ if (repo === null)
38
+ return fail("usage", "Use --repo with the project checkout root.");
39
+ if (!context.repos.includes(repo))
40
+ return fail("not-found", "That project is outside your access.");
41
+ const daysGiven = options.get("days");
42
+ const days = typeof daysGiven === "string" ? Number(daysGiven) : FLAKY_WINDOW_DAYS;
43
+ if (!Number.isSafeInteger(days) || days < 1 || days > 730)
44
+ return fail("usage", "--days takes a whole number from 1 to 730.");
45
+ if (action === "list") {
46
+ const list = flakyList(context.store, repo, context.now, days);
47
+ const history = list.complete ? [] : [`Not enough recorded history: test results are kept for 180 days, so ${days} days can't be counted in full.`];
48
+ if (list.rows.length === 0)
49
+ return ok({ list }, [...history, ...(list.complete ? ["No mixed outcomes recorded."] : []), flakyCoverageWords(list)]);
50
+ return ok({ list }, ["Mixed outcomes under recorded conditions", ...history, ...list.rows.map(rowLine), flakyCoverageWords(list), ...(list.notPaired === 0 ? [] : [`${list.notPaired} GitHub attempt pairing${list.notPaired === 1 ? "" : "s"} not paired.`]), "Not recorded: network, other processes, time of day."]);
51
+ }
52
+ const key = positional.slice(1).join(" ").trim();
53
+ if (key === "")
54
+ return fail("usage", "flaky show <identity from flaky list>");
55
+ const found = flakyShow(context.store, repo, key, context.now, days);
56
+ if (found === null)
57
+ return fail("not-found", "Nothing is recorded for that test in this project.");
58
+ return ok(found, [rowLine(found.row), ...found.history.map(one => ` ${day(one.at)} ${one.failed} failed, ${one.passed} passed · ${one.commit ?? "commit not recorded"} · ${one.conditions}`),
59
+ `Make a task to fix it: toolroll task add "${found.fix.title.replaceAll('"', "'")}" --goal "${found.fix.goal.replaceAll('"', "'")}" --repo ${repo}`]);
60
+ }
package/dist/flaky.d.ts CHANGED
@@ -1,12 +1,3 @@
1
- /**
2
- * Mixed outcomes under recorded conditions (docs/plans/learning-loops.md, "Flaky tests"): advisory only. Nothing here
3
- * changes a check's outcome, a verdict, an approval or a rerun; rerunning stays a person's `task regate`.
4
- *
5
- * Slice 1 holds the tracked identities an observation adds (`learning_watch`, reason `observation`): every Full check
6
- * with a handshake asks about them for 90 days (check-facts.ts learnRequestFor). Lesson notes (lesson-notes.ts) add
7
- * their own. This module and lesson-notes.ts are the only writers of `learning_watch`; release-auto's learning scan
8
- * (eval-hooks.ts learningWriteProblem) needs a person for any other. A policy path (release-auto.ts EVAL_POLICY).
9
- */
10
1
  import type { Store } from "./store.js";
11
2
  /** Track an identity a local observation named (one row per observation; first write wins, never rewritten). */
12
3
  export declare function watchObservation(store: Store, input: {
@@ -15,3 +6,78 @@ export declare function watchObservation(store: Store, input: {
15
6
  observation: number;
16
7
  now: Date;
17
8
  }): boolean;
9
+ /** How far back the list looks, how long the pruned rows are kept, and how many runs clear a row (the plan's defaults). */
10
+ export declare const FLAKY_WINDOW_DAYS = 90;
11
+ export declare const FLAKY_RETAIN_DAYS = 180;
12
+ export declare const FLAKY_CLEAR_RUNS = 20;
13
+ /** What can be observed at all: a test case, a journey or its group, or a file, never `part:`, `upgrade:`, `check` or
14
+ * `unexplained`, and never a real-model journey (a model's behaviour isn't a test's flakiness). */
15
+ export declare function observableIdentity(identity: string): boolean;
16
+ /**
17
+ * Every observation one settled check adds, in the transaction that records its results (check-facts.ts): an in-check
18
+ * retry, and each identity that failed in one check and explicitly passed in another with equal tree, command, access,
19
+ * harness and conditions. A `file:` identity is observed only as "failed, then the whole check passed", and only when
20
+ * no case in that file names the failure. Idempotent: racing dispositions on one pair leave one row.
21
+ */
22
+ export declare function recordObservations(store: Store, runId: number, now: Date): number[];
23
+ /** The storage sweep's own step: observations older than the retention window go; nothing else deletes them. */
24
+ export declare function pruneObservations(store: Store, now: Date): number;
25
+ export type FlakyStatus = "mixed outcomes" | "no failures since its file changed" | "not enough recorded history";
26
+ export type FlakyRow = {
27
+ /** The stable handle `flaky show` takes: the identity, or `github:<lane>`. */
28
+ key: string;
29
+ kind: "test" | "file" | "lane";
30
+ /** What the row says, as recorded: untrusted test text, escaped wherever it's shown. */
31
+ name: string;
32
+ observations: number;
33
+ lastSeen: string;
34
+ /** `<platform>-<arch>-node<major>`, or GitHub for a lane. */
35
+ conditions: string[];
36
+ /** Failures under recorded conditions that have no recorded pass. */
37
+ hardFailures: number;
38
+ sources: string[];
39
+ /** When the test's file last changed, as far as the recorded checks show; null when they don't. */
40
+ fileChanged: string | null;
41
+ status: FlakyStatus;
42
+ };
43
+ export type FlakyList = {
44
+ repo: string;
45
+ days: number;
46
+ rows: FlakyRow[];
47
+ /** False when the window reaches past what is kept, so a count there could be short. */
48
+ complete: boolean;
49
+ /** CI pairings refused, and how many checks in the window had no per-test reporting. */
50
+ notPaired: number;
51
+ unreported: number;
52
+ reported: number;
53
+ };
54
+ export type FlakyObservation = {
55
+ kind: "regate" | "same-tree" | "in-check-retry" | "github";
56
+ at: string;
57
+ failed: string;
58
+ passed: string;
59
+ commit: string | null;
60
+ conditions: string;
61
+ };
62
+ /** The test file an identity names, when it names one. */
63
+ export declare function identityFile(identity: string): string | null;
64
+ /** A readable name: `<file> › <test>`; a journey's group and name; a file with its limit said. */
65
+ export declare function identityWords(identity: string): string;
66
+ /** Every row for a project: reads only. `days` past the kept window is answered with `complete: false`, never a zero. */
67
+ export declare function flakyList(store: Store, repo: string, now: Date, days?: number): FlakyList;
68
+ /** How many rows the list has now, cheaply: no tree is read. */
69
+ export declare function flakyCount(store: Store, repo: string, now: Date): number;
70
+ /** One row's history, newest first: each observation with both checks (or the CI run and attempts). Null: no such row. */
71
+ export declare function flakyShow(store: Store, repo: string, key: string, now: Date, days?: number): {
72
+ row: FlakyRow;
73
+ history: FlakyObservation[];
74
+ fix: {
75
+ title: string;
76
+ goal: string;
77
+ };
78
+ } | null;
79
+ /** The ordinary task "Make a task to fix it" files; a person approves it as usual. Never to skip or delete the test. */
80
+ export declare function fixTaskOf(row: Pick<FlakyRow, "key" | "name" | "observations" | "conditions" | "kind">): {
81
+ title: string;
82
+ goal: string;
83
+ };
package/dist/flaky.js CHANGED
@@ -1,3 +1,19 @@
1
+ /**
2
+ * Mixed outcomes under recorded conditions (docs/plans/learning-loops.md, "Flaky tests"): advisory only. Nothing here
3
+ * changes a check's outcome, a verdict, an approval or a rerun; rerunning stays a person's `task regate`.
4
+ *
5
+ * Observations are written here, in the transaction that records a check's results (recordObservations). Each adds a
6
+ * tracked identity (`learning_watch`, reason `observation`): every Full check with a handshake asks about it for 90
7
+ * days (check-facts.ts learnRequestFor). Lesson notes (lesson-notes.ts) add their own. This module and lesson-notes.ts
8
+ * are the only writers of `learning_watch`, and this one of `local_observation`; release-auto's learning scan
9
+ * (eval-hooks.ts learningWriteProblem) needs a person for any other. A policy path (release-auto.ts EVAL_POLICY).
10
+ *
11
+ * The list reads only. An infrastructure-class run is never an observation: a run's class is stamped after its check,
12
+ * so recording skips the runs already stamped and every read skips the rest.
13
+ */
14
+ import { projectGitDir } from "./check-report.js";
15
+ import { verifiedBlobs } from "./commit-extract.js";
16
+ import { ciNotPaired, ciRepositoriesOf } from "./flaky-ci.js";
1
17
  /** Track an identity a local observation named (one row per observation; first write wins, never rewritten). */
2
18
  export function watchObservation(store, input) {
3
19
  if (input.repo.trim() === "" || input.identity.length < 1 || input.identity.length > 2600 || !Number.isSafeInteger(input.observation) || input.observation < 1) {
@@ -7,3 +23,242 @@ export function watchObservation(store, input) {
7
23
  ON CONFLICT (repo, identity, reason, source_ref) DO NOTHING`).run(input.repo, input.identity, `observation:${input.observation}`, input.now.toISOString());
8
24
  return Number(added.changes) === 1;
9
25
  }
26
+ // ---- local observations --------------------------------------------------------------------------------------------
27
+ /** How far back the list looks, how long the pruned rows are kept, and how many runs clear a row (the plan's defaults). */
28
+ export const FLAKY_WINDOW_DAYS = 90;
29
+ export const FLAKY_RETAIN_DAYS = 180;
30
+ export const FLAKY_CLEAR_RUNS = 20;
31
+ const DAY_MS = 86_400_000;
32
+ /** Twin checks one settled check is compared with, observations it writes, and rows the list and a history read. */
33
+ const TWINS_MAX = 50;
34
+ const OBSERVED_MAX = 500;
35
+ const ROWS_MAX = 200;
36
+ const HISTORY_MAX = 100;
37
+ /** Distinct trees one read of the list opens: past this, the rest of a row's status stays "mixed outcomes". */
38
+ const TREES_MAX = 40;
39
+ /** What can be observed at all: a test case, a journey or its group, or a file, never `part:`, `upgrade:`, `check` or
40
+ * `unexplained`, and never a real-model journey (a model's behaviour isn't a test's flakiness). */
41
+ export function observableIdentity(identity) {
42
+ if (/^(?:journey|group):[^:]*:real(?::|$)/.test(identity))
43
+ return false;
44
+ return /^(?:unit|journey|group|file):./.test(identity);
45
+ }
46
+ const fileLevel = (identity) => identity.startsWith("file:");
47
+ /** The explicit sources (docs/plans/learning-loops.md, "Explicit execution evidence"). */
48
+ const EXPLICIT = "('report','learn')";
49
+ /** A tree's facts as comparisons need them, or null (nothing comparable was recorded). */
50
+ function comparableFact(store, runId) {
51
+ const row = store.handle.prepare("SELECT repo, task_ref, head, tree, command_digest, access, harness_digest, conditions_digest, purpose FROM check_fact WHERE run = ?").get(runId);
52
+ if (row === undefined || row["repo"] == null || row["tree"] == null || row["conditions_digest"] == null)
53
+ return null;
54
+ const text = (key) => row[key] == null ? null : String(row[key]);
55
+ return { repo: String(row["repo"]), taskRef: Number(row["task_ref"]), head: text("head"), tree: String(row["tree"]), commandDigest: text("command_digest"), access: text("access"), harness: text("harness_digest"), digest: String(row["conditions_digest"]), purpose: String(row["purpose"]) };
56
+ }
57
+ const infrastructure = (store, runId) => store.handle.prepare("SELECT 1 FROM run WHERE id = ? AND exec_class = 'infrastructure'").get(runId) !== undefined;
58
+ /**
59
+ * Every observation one settled check adds, in the transaction that records its results (check-facts.ts): an in-check
60
+ * retry, and each identity that failed in one check and explicitly passed in another with equal tree, command, access,
61
+ * harness and conditions. A `file:` identity is observed only as "failed, then the whole check passed", and only when
62
+ * no case in that file names the failure. Idempotent: racing dispositions on one pair leave one row.
63
+ */
64
+ export function recordObservations(store, runId, now) {
65
+ const fact = comparableFact(store, runId);
66
+ if (fact === null || infrastructure(store, runId))
67
+ return [];
68
+ const at = now.toISOString();
69
+ const added = [];
70
+ const insert = store.handle.prepare(`INSERT INTO local_observation (repo, identity, failed_run, passed_run, tree, conditions_digest, kind, observed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
71
+ ON CONFLICT (identity, failed_run, passed_run) DO NOTHING`);
72
+ const observe = (identity, failed, passed, kind) => {
73
+ if (added.length >= OBSERVED_MAX || !observableIdentity(identity))
74
+ return;
75
+ const done = insert.run(fact.repo, identity, failed, passed, fact.tree, fact.digest, kind, at);
76
+ if (Number(done.changes) !== 1)
77
+ return;
78
+ const id = Number(done.lastInsertRowid);
79
+ added.push(id);
80
+ watchObservation(store, { repo: fact.repo, identity, observation: id, now });
81
+ };
82
+ // A nightly's in-run reruns are never observations; a retry inside any other check is.
83
+ if (fact.purpose !== "nightly") {
84
+ for (const row of store.handle.prepare(`SELECT DISTINCT identity FROM check_result WHERE run = ? AND state = 'passed-on-retry' AND source IN ${EXPLICIT}`).all(runId)) {
85
+ const identity = String(row["identity"]);
86
+ if (!fileLevel(identity))
87
+ observe(identity, runId, runId, "in-check-retry");
88
+ }
89
+ }
90
+ const twins = store.handle.prepare(`SELECT f.run, f.task_ref, f.head FROM check_fact f JOIN run r ON r.id = f.run
91
+ WHERE f.repo = ? AND f.tree = ? AND f.conditions_digest = ? AND f.command_digest IS ? AND f.access IS ? AND f.harness_digest IS ? AND f.run <> ?
92
+ AND (r.exec_class IS NULL OR r.exec_class <> 'infrastructure') ORDER BY f.run DESC LIMIT ?`).all(fact.repo, fact.tree, fact.digest, fact.commandDigest, fact.access, fact.harness, runId, TWINS_MAX);
93
+ const mixed = store.handle.prepare(`SELECT DISTINCT a.identity FROM check_result a JOIN check_result b ON b.identity = a.identity AND b.run = ? AND b.state = 'passed' AND b.source IN ${EXPLICIT}
94
+ WHERE a.run = ? AND a.state = 'failed' AND a.source IN ${EXPLICIT}`);
95
+ const received = (run, state) => store.handle.prepare("SELECT 1 FROM check_result WHERE run = ? AND identity = 'check' AND source = 'receipt' AND state = ?").get(run, state) !== undefined;
96
+ for (const twin of twins) {
97
+ const other = Number(twin["run"]);
98
+ const kind = Number(twin["task_ref"]) === fact.taskRef && (twin["head"] == null ? null : String(twin["head"])) === fact.head ? "regate" : "same-tree";
99
+ for (const row of mixed.all(runId, other))
100
+ observe(String(row["identity"]), other, runId, kind);
101
+ for (const row of mixed.all(other, runId))
102
+ observe(String(row["identity"]), runId, other, kind);
103
+ // File level: an earlier check failed in the file, this later one passed whole.
104
+ if (other < runId && received(runId, "passed") && received(other, "failed")) {
105
+ const failed = store.handle.prepare("SELECT identity, source FROM check_result WHERE run = ? AND state = 'failed'").all(other).map(row => String(row["identity"]));
106
+ for (const identity of new Set(failed.filter(fileLevel))) {
107
+ const prefix = `unit:${identity.slice("file:".length)}::`;
108
+ if (!failed.some(one => one.startsWith(prefix)))
109
+ observe(identity, other, runId, kind);
110
+ }
111
+ }
112
+ }
113
+ return added;
114
+ }
115
+ /** The storage sweep's own step: observations older than the retention window go; nothing else deletes them. */
116
+ export function pruneObservations(store, now) {
117
+ const cut = new Date(now.getTime() - FLAKY_RETAIN_DAYS * DAY_MS).toISOString();
118
+ return Number(store.handle.prepare("DELETE FROM local_observation WHERE observed_at < ?").run(cut).changes);
119
+ }
120
+ /** The test file an identity names, when it names one. */
121
+ export function identityFile(identity) {
122
+ const unit = /^unit:(.+?)::/.exec(identity);
123
+ if (unit !== null)
124
+ return unit[1];
125
+ return identity.startsWith("file:") ? identity.slice("file:".length) : null;
126
+ }
127
+ /** A readable name: `<file> › <test>`; a journey's group and name; a file with its limit said. */
128
+ export function identityWords(identity) {
129
+ const unit = /^unit:(.+?)::(.*)$/s.exec(identity);
130
+ if (unit !== null)
131
+ return `${unit[1]} › ${unit[2]}`;
132
+ const journey = /^journey:([^:]*):([^:]*):(.+?)::(.*)$/s.exec(identity);
133
+ if (journey !== null)
134
+ return `${journey[1]} journey ${journey[3]} › ${journey[4]}`;
135
+ const group = /^group:([^:]*):([^:]*):(.*)$/s.exec(identity);
136
+ if (group !== null)
137
+ return `${group[1]} journeys ${group[3]}`;
138
+ return fileLevel(identity) ? `${identity.slice("file:".length)} (file-level, not confirmed per test)` : identity;
139
+ }
140
+ const SOURCE_WORDS = { report: "Test report", learn: "Test report", log: "Check log", receipt: "Check result" };
141
+ /** Whether results older than the kept window were pruned for this project. */
142
+ function prunedBefore(store, repo, now) {
143
+ const cut = new Date(now.getTime() - FLAKY_RETAIN_DAYS * DAY_MS).toISOString();
144
+ return store.handle.prepare("SELECT 1 FROM check_fact WHERE repo = ? AND recorded_at < ? LIMIT 1").get(repo, cut) !== undefined;
145
+ }
146
+ /** Each tree's blob ids for the given files, read through verified objects, once per tree, within a budget. */
147
+ function blobReader(repo, files) {
148
+ const gitDir = projectGitDir(repo);
149
+ const seen = new Map();
150
+ return (tree, file) => {
151
+ if (gitDir === null)
152
+ return undefined;
153
+ let read = seen.get(tree);
154
+ if (read === undefined) {
155
+ if (seen.size >= TREES_MAX)
156
+ return undefined;
157
+ try {
158
+ const blobs = verifiedBlobs(gitDir, tree, files);
159
+ read = blobs.ok ? new Map([...blobs.value].map(([path, one]) => [path, one.id])) : null;
160
+ }
161
+ catch {
162
+ read = null;
163
+ }
164
+ seen.set(tree, read);
165
+ }
166
+ return read === null ? undefined : read.get(file) ?? null;
167
+ };
168
+ }
169
+ /** What a row's recorded runs say since its file changed. `undefined` from the reader means a tree couldn't be read:
170
+ * the walk stops there and claims nothing. */
171
+ function statusOf(store, repo, identity, blobAt, pruned) {
172
+ const file = identityFile(identity);
173
+ if (file === null || fileLevel(identity))
174
+ return { status: "mixed outcomes", fileChanged: null };
175
+ const rows = store.handle.prepare(`SELECT f.run, f.tree, f.recorded_at, MAX(CASE WHEN r.state IN ('failed','passed-on-retry') THEN 1 ELSE 0 END) AS failed, MAX(CASE WHEN r.state = 'passed' THEN 1 ELSE 0 END) AS passed
176
+ FROM check_result r JOIN check_fact f ON f.run = r.run WHERE f.repo = ? AND r.identity = ? AND r.source IN ${EXPLICIT} AND f.tree IS NOT NULL GROUP BY f.run ORDER BY f.run DESC LIMIT 200`).all(repo, identity);
177
+ if (rows.length === 0)
178
+ return { status: "mixed outcomes", fileChanged: null };
179
+ const newest = blobAt(String(rows[0]["tree"]), file);
180
+ if (newest === undefined)
181
+ return { status: "mixed outcomes", fileChanged: null };
182
+ let clean = 0, boundary = false, failedSince = false, earliest = null;
183
+ for (const row of rows) {
184
+ const here = blobAt(String(row["tree"]), file);
185
+ if (here === undefined)
186
+ return { status: "mixed outcomes", fileChanged: null };
187
+ if (here !== newest) {
188
+ boundary = true;
189
+ break;
190
+ }
191
+ earliest = String(row["recorded_at"]);
192
+ if (Number(row["failed"]) === 1) {
193
+ failedSince = true;
194
+ continue;
195
+ }
196
+ if (Number(row["passed"]) === 1)
197
+ clean++;
198
+ }
199
+ const fileChanged = boundary ? earliest : null;
200
+ if (failedSince)
201
+ return { status: "mixed outcomes", fileChanged };
202
+ if (clean >= FLAKY_CLEAR_RUNS)
203
+ return { status: "no failures since its file changed", fileChanged };
204
+ // Too few passes, and the file's change is older than the results kept: there's no telling, and no zero is shown.
205
+ return { status: !boundary && pruned ? "not enough recorded history" : "mixed outcomes", fileChanged };
206
+ }
207
+ /** Every row for a project: reads only. `days` past the kept window is answered with `complete: false`, never a zero. */
208
+ export function flakyList(store, repo, now, days = FLAKY_WINDOW_DAYS) {
209
+ const span = Number.isSafeInteger(days) && days >= 1 && days <= 730 ? days : FLAKY_WINDOW_DAYS;
210
+ const since = new Date(now.getTime() - span * DAY_MS).toISOString();
211
+ const pruned = prunedBefore(store, repo, now);
212
+ const rows = store.handle.prepare(`SELECT o.identity, COUNT(DISTINCT o.tree || ':' || o.conditions_digest) AS observations, MAX(o.observed_at) AS last_seen
213
+ FROM local_observation o JOIN run a ON a.id = o.failed_run JOIN run b ON b.id = o.passed_run
214
+ WHERE o.repo = ? AND o.observed_at >= ? AND (a.exec_class IS NULL OR a.exec_class <> 'infrastructure') AND (b.exec_class IS NULL OR b.exec_class <> 'infrastructure')
215
+ GROUP BY o.identity ORDER BY last_seen DESC, o.identity LIMIT ?`).all(repo, since, ROWS_MAX);
216
+ const identities = rows.map(row => String(row["identity"]));
217
+ const blobAt = blobReader(repo, [...new Set(identities.flatMap(one => identityFile(one) ?? []))]);
218
+ const local = rows.map(row => {
219
+ const identity = String(row["identity"]);
220
+ const conditions = store.handle.prepare(`SELECT DISTINCT f.conditions_class FROM local_observation o JOIN check_fact f ON f.run = o.failed_run WHERE o.repo = ? AND o.identity = ? AND o.observed_at >= ? AND f.conditions_class IS NOT NULL ORDER BY 1`).all(repo, identity, since).map(one => String(one["conditions_class"]));
221
+ const sources = store.handle.prepare(`SELECT DISTINCT r.source FROM local_observation o JOIN check_result r ON r.identity = o.identity AND r.run IN (o.failed_run, o.passed_run) WHERE o.repo = ? AND o.identity = ? AND o.observed_at >= ? ORDER BY 1`).all(repo, identity, since).map(one => SOURCE_WORDS[String(one["source"])] ?? String(one["source"]));
222
+ const hard = store.handle.prepare(`SELECT COUNT(DISTINCT r.run) AS n FROM check_result r JOIN check_fact f ON f.run = r.run WHERE f.repo = ? AND r.identity = ? AND r.state = 'failed' AND f.recorded_at >= ? AND f.tree IS NOT NULL
223
+ AND NOT EXISTS (SELECT 1 FROM check_result p JOIN check_fact g ON g.run = p.run WHERE p.identity = r.identity AND p.state IN ('passed','passed-on-retry') AND g.repo = f.repo AND g.tree = f.tree AND g.conditions_digest = f.conditions_digest)`).get(repo, identity, since);
224
+ const read = statusOf(store, repo, identity, blobAt, pruned);
225
+ return { key: identity, kind: fileLevel(identity) ? "file" : "test", name: identityWords(identity), observations: Number(row["observations"]), lastSeen: String(row["last_seen"]),
226
+ conditions, hardFailures: Number(hard?.["n"] ?? 0), sources: [...new Set(sources)], fileChanged: read.fileChanged, status: read.status };
227
+ });
228
+ const lanes = store.handle.prepare(`SELECT lane_id, MAX(lane_name) AS lane_name, COUNT(*) AS observations, MAX(observed_at) AS last_seen FROM ci_flaky_observation WHERE repo = ? AND observed_at >= ? GROUP BY lane_id ORDER BY last_seen DESC, lane_id LIMIT ?`).all(repo, since, ROWS_MAX);
229
+ const ci = lanes.map(row => ({ key: `github:${String(row["lane_id"])}`, kind: "lane", name: `${String(row["lane_name"])} (GitHub CI)`, observations: Number(row["observations"]), lastSeen: String(row["last_seen"]),
230
+ conditions: ["GitHub CI"], hardFailures: 0, sources: ["GitHub"], fileChanged: null, status: "mixed outcomes" }));
231
+ const checks = store.handle.prepare("SELECT COUNT(*) AS n, SUM(CASE WHEN EXISTS (SELECT 1 FROM check_result r WHERE r.run = f.run AND r.source IN ('report','learn')) THEN 1 ELSE 0 END) AS reported FROM check_fact f WHERE f.repo = ? AND f.recorded_at >= ?").get(repo, since);
232
+ return { repo, days: span, rows: [...local, ...ci].sort((a, b) => b.lastSeen.localeCompare(a.lastSeen) || a.key.localeCompare(b.key)),
233
+ complete: !(span > FLAKY_RETAIN_DAYS && pruned), notPaired: ciNotPaired(store, ciRepositoriesOf(store, repo), since), reported: Number(checks?.["reported"] ?? 0), unreported: Number(checks?.["n"] ?? 0) - Number(checks?.["reported"] ?? 0) };
234
+ }
235
+ /** How many rows the list has now, cheaply: no tree is read. */
236
+ export function flakyCount(store, repo, now) {
237
+ const since = new Date(now.getTime() - FLAKY_WINDOW_DAYS * DAY_MS).toISOString();
238
+ return Number(store.handle.prepare("SELECT (SELECT COUNT(DISTINCT identity) FROM local_observation WHERE repo = ? AND observed_at >= ?) + (SELECT COUNT(DISTINCT lane_id) FROM ci_flaky_observation WHERE repo = ? AND observed_at >= ?) AS n").get(repo, since, repo, since)?.["n"] ?? 0);
239
+ }
240
+ /** One row's history, newest first: each observation with both checks (or the CI run and attempts). Null: no such row. */
241
+ export function flakyShow(store, repo, key, now, days = FLAKY_RETAIN_DAYS) {
242
+ const row = flakyList(store, repo, now, Math.max(days, FLAKY_WINDOW_DAYS)).rows.find(one => one.key === key);
243
+ if (row === undefined)
244
+ return null;
245
+ const history = row.kind === "lane"
246
+ ? store.handle.prepare(`SELECT workflow_run_id, head_sha, failed_attempt, failed_conclusion, passed_attempt, passed_conclusion, observed_at FROM ci_flaky_observation WHERE repo = ? AND lane_id = ? ORDER BY observed_at DESC, id DESC LIMIT ?`)
247
+ .all(repo, key.slice("github:".length), HISTORY_MAX).map(one => ({ kind: "github", at: String(one["observed_at"]), failed: `GitHub #${String(one["workflow_run_id"])} try ${String(one["failed_attempt"])}`,
248
+ passed: `try ${String(one["passed_attempt"])}`, commit: String(one["head_sha"]).slice(0, 7), conditions: "GitHub CI" }))
249
+ : store.handle.prepare(`SELECT o.kind, o.failed_run, o.passed_run, o.observed_at, f.head, f.conditions_class FROM local_observation o LEFT JOIN check_fact f ON f.run = o.failed_run WHERE o.repo = ? AND o.identity = ? ORDER BY o.observed_at DESC, o.id DESC LIMIT ?`)
250
+ .all(repo, key, HISTORY_MAX).map(one => ({ kind: String(one["kind"]), at: String(one["observed_at"]), failed: `build #${String(one["failed_run"])}`, passed: `build #${String(one["passed_run"])}`,
251
+ commit: one["head"] == null ? null : String(one["head"]).slice(0, 7), conditions: one["conditions_class"] == null ? "not recorded" : String(one["conditions_class"]) }));
252
+ return { row, history, fix: fixTaskOf(row) };
253
+ }
254
+ /** The ordinary task "Make a task to fix it" files; a person approves it as usual. Never to skip or delete the test. */
255
+ export function fixTaskOf(row) {
256
+ // eslint-disable-next-line no-control-regex
257
+ const name = row.name.replace(/[\u0000-\u001f\u007f]+/g, " ").slice(0, 300);
258
+ const short = name.length > 72 ? `${name.slice(0, 71)}…` : name;
259
+ return { title: `Make this test reliable: ${short}`, goal: [
260
+ `Toolroll recorded mixed outcomes under recorded conditions for ${name}: ${row.observations} time${row.observations === 1 ? "" : "s"} it both failed and passed with the same code, command and recorded environment (${row.conditions.join(", ") || "not recorded"}).`,
261
+ row.kind === "lane" ? "It is a GitHub CI job, so the failing test isn't named: read that job's log to find it." : "",
262
+ "Find out why and make the test reliable. Never skip, delete or loosen the test, and don't add retries to hide it.",
263
+ ].filter(Boolean).join(" ") };
264
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The tables behind what happens to a result after its agent finishes (docs/plans/fleet-follow-through.md). v155 adds
3
+ * them and changes no saved row, so the updater's history check needs no rule for it.
4
+ *
5
+ * Every reader of these tables goes through `readingFleet`: a deploy reads with the candidate's code over the installed
6
+ * runtime's file before this version's migration has run (as buildReviewOf does), and a missing table is simply nothing
7
+ * to show. Writers run only in a process whose Store opened, and so migrated, the file.
8
+ */
9
+ /** Who a finding's words are from. 'lead' (the lead drafted, a person confirmed) came after the first v155 builds, which
10
+ * this build's v155 step widens in place (store.ts widenFindingSources). */
11
+ export declare const FINDING_SOURCES = "CHECK (source IN ('build-review', 'person', 'lead', 'check-failure'))";
12
+ export declare const FINDING_SOURCES_BEFORE_LEAD = "CHECK (source IN ('build-review', 'person', 'check-failure'))";
13
+ export declare const V155_SCHEMA = "\n-- A finding sent to the task that owns the code. It belongs to the task family, so a revision or repair inherits it.\n-- summary, action and excerpt can hold model output or repository text: they are data everywhere they are read.\nCREATE TABLE IF NOT EXISTS finding (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n family_root TEXT NOT NULL,\n task_id TEXT NOT NULL,\n repo TEXT,\n source TEXT NOT NULL CHECK (source IN ('build-review', 'person', 'lead', 'check-failure')),\n source_key TEXT UNIQUE,\n source_run INTEGER,\n severity TEXT CHECK (severity IS NULL OR severity IN ('HIGH', 'MEDIUM', 'LOW')),\n file TEXT,\n line INTEGER,\n summary TEXT NOT NULL,\n action TEXT NOT NULL,\n excerpt TEXT,\n revision TEXT,\n state TEXT NOT NULL DEFAULT 'open' CHECK (state IN ('open', 'accepted', 'rejected', 'with-person', 'closed')),\n rounds INTEGER NOT NULL DEFAULT 1,\n sent_by TEXT NOT NULL,\n sent_at TEXT NOT NULL,\n delivered_run INTEGER,\n delivered_how TEXT CHECK (delivered_how IS NULL OR delivered_how IN ('mailbox', 'brief')),\n delivered_at TEXT,\n read_at TEXT,\n closed_by TEXT,\n closed_note TEXT,\n closed_at TEXT\n);\nCREATE INDEX IF NOT EXISTS finding_family ON finding (family_root, state);\n-- What the owner answered. 'saved' until the attempt that wrote it settles: then 'counted', or 'refused' with the reason\n-- (an accepted answer on a run that changed nothing; a rejection that names no file in the code).\nCREATE TABLE IF NOT EXISTS finding_answer (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n finding INTEGER NOT NULL REFERENCES finding(id) ON DELETE CASCADE,\n round INTEGER NOT NULL,\n run INTEGER NOT NULL,\n answer TEXT NOT NULL CHECK (answer IN ('accepted', 'rejected')),\n detail TEXT NOT NULL,\n file TEXT,\n line INTEGER,\n revision TEXT,\n state TEXT NOT NULL DEFAULT 'saved' CHECK (state IN ('saved', 'counted', 'refused')),\n problem TEXT,\n at TEXT NOT NULL,\n UNIQUE (finding, round, run)\n);\n-- One failed check on one revision of one task family: the UNIQUE key is what stops a second repair of the same failure.\n-- attempt is the repair ledger's (repair_chain) attempt this row drafted, when it drafted one.\nCREATE TABLE IF NOT EXISTS check_repair (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n family_root TEXT NOT NULL,\n owner_task TEXT NOT NULL,\n repo TEXT NOT NULL,\n revision TEXT NOT NULL,\n failure_key TEXT NOT NULL,\n source_run INTEGER NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('waiting', 'repairing', 'rerunning', 'with-person', 'parked', 'fixed', 'closed')),\n why TEXT NOT NULL,\n repair_task TEXT,\n attempt INTEGER,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n UNIQUE (family_root, revision, failure_key)\n);\nCREATE INDEX IF NOT EXISTS check_repair_state ON check_repair (repo, state);\n-- A failing result saved before a change. The after side is never stored: it is read from the task's latest results,\n-- so a newer commit reads as out of date on its own.\nCREATE TABLE IF NOT EXISTS evidence_pair (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n family_root TEXT NOT NULL,\n repo TEXT,\n before_run INTEGER NOT NULL,\n before_head TEXT NOT NULL,\n receipt_digest TEXT NOT NULL,\n command_digest TEXT,\n saved_by TEXT NOT NULL,\n saved_at TEXT NOT NULL,\n UNIQUE (family_root, before_run)\n);\n-- A bug fix's optional failing test first: asked for on the family; the agent's report beside it, shown as its own report.\nCREATE TABLE IF NOT EXISTS failing_first (\n family_root TEXT PRIMARY KEY,\n repo TEXT,\n asked_by TEXT NOT NULL,\n asked_at TEXT NOT NULL,\n report_run INTEGER,\n test TEXT,\n output TEXT,\n reported_at TEXT\n);\n-- What a provider said when an attempt failed, kept as data for a person. Nothing reads it to route work.\nCREATE TABLE IF NOT EXISTS provider_capture (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run INTEGER NOT NULL UNIQUE,\n repo TEXT,\n provider TEXT NOT NULL,\n model TEXT,\n auth_mode TEXT,\n exit_code INTEGER,\n code TEXT,\n text TEXT NOT NULL,\n hint INTEGER NOT NULL DEFAULT 0 CHECK (hint IN (0, 1)),\n label TEXT CHECK (label IS NULL OR label IN ('exhausted', 'not-exhausted')),\n labelled_by TEXT,\n at TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS provider_capture_provider ON provider_capture (provider, id);\n";
14
+ export declare const V155_TABLES: readonly ["finding", "finding_answer", "check_repair", "evidence_pair", "failing_first", "provider_capture"];
15
+ export declare const V155_STEPS: readonly ["follow-through-tables", "finding-sources"];
16
+ /** One read, or `fallback` when the file predates v155. Any other error is real and goes on. */
17
+ export declare function readingFleet<T>(read: () => T, fallback: T): T;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * The tables behind what happens to a result after its agent finishes (docs/plans/fleet-follow-through.md). v155 adds
3
+ * them and changes no saved row, so the updater's history check needs no rule for it.
4
+ *
5
+ * Every reader of these tables goes through `readingFleet`: a deploy reads with the candidate's code over the installed
6
+ * runtime's file before this version's migration has run (as buildReviewOf does), and a missing table is simply nothing
7
+ * to show. Writers run only in a process whose Store opened, and so migrated, the file.
8
+ */
9
+ /** Who a finding's words are from. 'lead' (the lead drafted, a person confirmed) came after the first v155 builds, which
10
+ * this build's v155 step widens in place (store.ts widenFindingSources). */
11
+ export const FINDING_SOURCES = "CHECK (source IN ('build-review', 'person', 'lead', 'check-failure'))";
12
+ export const FINDING_SOURCES_BEFORE_LEAD = "CHECK (source IN ('build-review', 'person', 'check-failure'))";
13
+ export const V155_SCHEMA = `
14
+ -- A finding sent to the task that owns the code. It belongs to the task family, so a revision or repair inherits it.
15
+ -- summary, action and excerpt can hold model output or repository text: they are data everywhere they are read.
16
+ CREATE TABLE IF NOT EXISTS finding (
17
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
18
+ family_root TEXT NOT NULL,
19
+ task_id TEXT NOT NULL,
20
+ repo TEXT,
21
+ source TEXT NOT NULL ${FINDING_SOURCES},
22
+ source_key TEXT UNIQUE,
23
+ source_run INTEGER,
24
+ severity TEXT CHECK (severity IS NULL OR severity IN ('HIGH', 'MEDIUM', 'LOW')),
25
+ file TEXT,
26
+ line INTEGER,
27
+ summary TEXT NOT NULL,
28
+ action TEXT NOT NULL,
29
+ excerpt TEXT,
30
+ revision TEXT,
31
+ state TEXT NOT NULL DEFAULT 'open' CHECK (state IN ('open', 'accepted', 'rejected', 'with-person', 'closed')),
32
+ rounds INTEGER NOT NULL DEFAULT 1,
33
+ sent_by TEXT NOT NULL,
34
+ sent_at TEXT NOT NULL,
35
+ delivered_run INTEGER,
36
+ delivered_how TEXT CHECK (delivered_how IS NULL OR delivered_how IN ('mailbox', 'brief')),
37
+ delivered_at TEXT,
38
+ read_at TEXT,
39
+ closed_by TEXT,
40
+ closed_note TEXT,
41
+ closed_at TEXT
42
+ );
43
+ CREATE INDEX IF NOT EXISTS finding_family ON finding (family_root, state);
44
+ -- What the owner answered. 'saved' until the attempt that wrote it settles: then 'counted', or 'refused' with the reason
45
+ -- (an accepted answer on a run that changed nothing; a rejection that names no file in the code).
46
+ CREATE TABLE IF NOT EXISTS finding_answer (
47
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
48
+ finding INTEGER NOT NULL REFERENCES finding(id) ON DELETE CASCADE,
49
+ round INTEGER NOT NULL,
50
+ run INTEGER NOT NULL,
51
+ answer TEXT NOT NULL CHECK (answer IN ('accepted', 'rejected')),
52
+ detail TEXT NOT NULL,
53
+ file TEXT,
54
+ line INTEGER,
55
+ revision TEXT,
56
+ state TEXT NOT NULL DEFAULT 'saved' CHECK (state IN ('saved', 'counted', 'refused')),
57
+ problem TEXT,
58
+ at TEXT NOT NULL,
59
+ UNIQUE (finding, round, run)
60
+ );
61
+ -- One failed check on one revision of one task family: the UNIQUE key is what stops a second repair of the same failure.
62
+ -- attempt is the repair ledger's (repair_chain) attempt this row drafted, when it drafted one.
63
+ CREATE TABLE IF NOT EXISTS check_repair (
64
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
65
+ family_root TEXT NOT NULL,
66
+ owner_task TEXT NOT NULL,
67
+ repo TEXT NOT NULL,
68
+ revision TEXT NOT NULL,
69
+ failure_key TEXT NOT NULL,
70
+ source_run INTEGER NOT NULL,
71
+ state TEXT NOT NULL CHECK (state IN ('waiting', 'repairing', 'rerunning', 'with-person', 'parked', 'fixed', 'closed')),
72
+ why TEXT NOT NULL,
73
+ repair_task TEXT,
74
+ attempt INTEGER,
75
+ created_at TEXT NOT NULL,
76
+ updated_at TEXT NOT NULL,
77
+ UNIQUE (family_root, revision, failure_key)
78
+ );
79
+ CREATE INDEX IF NOT EXISTS check_repair_state ON check_repair (repo, state);
80
+ -- A failing result saved before a change. The after side is never stored: it is read from the task's latest results,
81
+ -- so a newer commit reads as out of date on its own.
82
+ CREATE TABLE IF NOT EXISTS evidence_pair (
83
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
84
+ family_root TEXT NOT NULL,
85
+ repo TEXT,
86
+ before_run INTEGER NOT NULL,
87
+ before_head TEXT NOT NULL,
88
+ receipt_digest TEXT NOT NULL,
89
+ command_digest TEXT,
90
+ saved_by TEXT NOT NULL,
91
+ saved_at TEXT NOT NULL,
92
+ UNIQUE (family_root, before_run)
93
+ );
94
+ -- A bug fix's optional failing test first: asked for on the family; the agent's report beside it, shown as its own report.
95
+ CREATE TABLE IF NOT EXISTS failing_first (
96
+ family_root TEXT PRIMARY KEY,
97
+ repo TEXT,
98
+ asked_by TEXT NOT NULL,
99
+ asked_at TEXT NOT NULL,
100
+ report_run INTEGER,
101
+ test TEXT,
102
+ output TEXT,
103
+ reported_at TEXT
104
+ );
105
+ -- What a provider said when an attempt failed, kept as data for a person. Nothing reads it to route work.
106
+ CREATE TABLE IF NOT EXISTS provider_capture (
107
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
108
+ run INTEGER NOT NULL UNIQUE,
109
+ repo TEXT,
110
+ provider TEXT NOT NULL,
111
+ model TEXT,
112
+ auth_mode TEXT,
113
+ exit_code INTEGER,
114
+ code TEXT,
115
+ text TEXT NOT NULL,
116
+ hint INTEGER NOT NULL DEFAULT 0 CHECK (hint IN (0, 1)),
117
+ label TEXT CHECK (label IS NULL OR label IN ('exhausted', 'not-exhausted')),
118
+ labelled_by TEXT,
119
+ at TEXT NOT NULL
120
+ );
121
+ CREATE INDEX IF NOT EXISTS provider_capture_provider ON provider_capture (provider, id);
122
+ `;
123
+ export const V155_TABLES = Object.freeze(["finding", "finding_answer", "check_repair", "evidence_pair", "failing_first", "provider_capture"]);
124
+ export const V155_STEPS = Object.freeze(["follow-through-tables", "finding-sources"]);
125
+ /** One read, or `fallback` when the file predates v155. Any other error is real and goes on. */
126
+ export function readingFleet(read, fallback) {
127
+ try {
128
+ return read();
129
+ }
130
+ catch (error) {
131
+ if (/no such table: (finding|finding_answer|check_repair|evidence_pair|failing_first|provider_capture)\b/.test(String(error)))
132
+ return fallback;
133
+ throw error;
134
+ }
135
+ }