dsh-pstack 0.1.0 → 0.2.0-beta.1

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 (312) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +92 -117
  3. package/assets/agents/comment-sicko.md +1 -1
  4. package/assets/agents/poteto-agent.md +2 -2
  5. package/assets/docs/guide/01-setup.md +9 -9
  6. package/assets/docs/guide/02-poteto-mode.md +4 -4
  7. package/assets/docs/guide/05-build-and-clean.md +3 -3
  8. package/assets/docs/guide/06-verify-and-ship.md +2 -2
  9. package/assets/docs/guide/07-overnight.md +10 -12
  10. package/assets/docs/guide/09-make-it-yours.md +2 -6
  11. package/assets/docs/guide/10-recipes-and-pitfalls.md +3 -3
  12. package/assets/docs/guide/README.md +1 -1
  13. package/assets/docs/guide/images/design.jpg +0 -0
  14. package/assets/docs/guide/images/overnight.jpg +0 -0
  15. package/assets/docs/guide/images/recipes.jpg +0 -0
  16. package/assets/docs/guide/images/router.jpg +0 -0
  17. package/assets/docs/guide/images/understanding.jpg +0 -0
  18. package/assets/docs/guide/images/verification.jpg +0 -0
  19. package/assets/docs/harness/dsh.md +60 -65
  20. package/assets/docs/pstack-models.example.md +40 -9
  21. package/assets/skills/architect/SKILL.md +12 -12
  22. package/assets/skills/arena/SKILL.md +11 -11
  23. package/assets/skills/automate-me/SKILL.md +15 -14
  24. package/assets/skills/benchmark-checklist/SKILL.md +0 -2
  25. package/assets/skills/blast-radius/SKILL.md +5 -5
  26. package/assets/skills/bro/SKILL.md +0 -2
  27. package/assets/skills/correct/SKILL.md +0 -2
  28. package/assets/skills/create-formal-verification-skill/SKILL.md +2 -2
  29. package/assets/skills/create-verification-skill/SKILL.md +2 -2
  30. package/assets/skills/figure-it-out/SKILL.md +13 -13
  31. package/assets/skills/how/SKILL.md +6 -6
  32. package/assets/skills/how/references/explainer-prompt.md +1 -1
  33. package/assets/skills/in-your-own-words/SKILL.md +0 -2
  34. package/assets/skills/interrogate/SKILL.md +9 -7
  35. package/assets/skills/interrogate/references/rubric.md +1 -1
  36. package/assets/skills/ketchup/SKILL.md +4 -4
  37. package/assets/skills/maintain-formal-verification-skill/SKILL.md +2 -2
  38. package/assets/skills/maintain-verification-skill/SKILL.md +3 -3
  39. package/assets/skills/no-comments/SKILL.md +7 -7
  40. package/assets/skills/poteto-help/SKILL.md +32 -31
  41. package/assets/skills/poteto-help/references/prompting.md +1 -1
  42. package/assets/skills/poteto-help/references/recipes.md +1 -1
  43. package/assets/skills/poteto-mode/SKILL.md +17 -19
  44. package/assets/skills/poteto-mode/playbooks/authoring-a-skill.md +3 -3
  45. package/assets/skills/poteto-mode/playbooks/autonomous-run.md +6 -4
  46. package/assets/skills/poteto-mode/playbooks/autopilot-full.md +6 -6
  47. package/assets/skills/poteto-mode/playbooks/autopilot-stack.md +2 -2
  48. package/assets/skills/poteto-mode/playbooks/babysit.md +3 -3
  49. package/assets/skills/poteto-mode/playbooks/bug-fix.md +7 -7
  50. package/assets/skills/poteto-mode/playbooks/eval.md +3 -3
  51. package/assets/skills/poteto-mode/playbooks/feature.md +8 -8
  52. package/assets/skills/poteto-mode/playbooks/hillclimb.md +3 -3
  53. package/assets/skills/poteto-mode/playbooks/investigation.md +2 -2
  54. package/assets/skills/poteto-mode/playbooks/multi-phase-plan.md +21 -21
  55. package/assets/skills/poteto-mode/playbooks/opening-a-pr.md +6 -6
  56. package/assets/skills/poteto-mode/playbooks/orchestrate.md +13 -13
  57. package/assets/skills/poteto-mode/playbooks/pause-safely.md +2 -2
  58. package/assets/skills/poteto-mode/playbooks/perf-issue.md +6 -6
  59. package/assets/skills/poteto-mode/playbooks/prototype.md +4 -4
  60. package/assets/skills/poteto-mode/playbooks/refactoring.md +9 -9
  61. package/assets/skills/poteto-mode/playbooks/runtime-forensics.md +3 -3
  62. package/assets/skills/poteto-mode/playbooks/session-pickup.md +2 -2
  63. package/assets/skills/poteto-mode/playbooks/shipping.md +2 -2
  64. package/assets/skills/poteto-mode/playbooks/trace-forensics.md +2 -2
  65. package/assets/skills/poteto-mode/playbooks/visual-parity.md +5 -5
  66. package/assets/skills/poteto-mode/playbooks/worktree-cleanup.md +14 -6
  67. package/assets/skills/poteto-mode/scripts/check-plan.mjs +85 -70
  68. package/assets/skills/poteto-mode/scripts/check-plan.test.mjs +343 -0
  69. package/assets/skills/poteto-mode/scripts/orch/orch.mjs +4282 -0
  70. package/assets/skills/poteto-mode/scripts/orch/orch.test.mjs +2042 -0
  71. package/assets/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +5101 -0
  72. package/assets/skills/poteto-mode/scripts/watch-pr/github.test.mjs +1177 -0
  73. package/assets/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +1441 -0
  74. package/assets/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +4601 -0
  75. package/assets/skills/poteto-mode/scripts/worktree-audit.mjs +675 -0
  76. package/assets/skills/principle-attack-the-premise/SKILL.md +0 -2
  77. package/assets/skills/principle-boundary-discipline/SKILL.md +0 -2
  78. package/assets/skills/principle-build-the-lever/SKILL.md +0 -3
  79. package/assets/skills/principle-encode-lessons-in-structure/SKILL.md +0 -2
  80. package/assets/skills/principle-exhaust-the-design-space/SKILL.md +0 -2
  81. package/assets/skills/principle-experience-first/SKILL.md +0 -2
  82. package/assets/skills/principle-explain-the-number/SKILL.md +0 -2
  83. package/assets/skills/principle-fix-root-causes/SKILL.md +0 -2
  84. package/assets/skills/principle-foundational-thinking/SKILL.md +0 -2
  85. package/assets/skills/principle-guard-the-context-window/SKILL.md +0 -2
  86. package/assets/skills/principle-laziness-protocol/SKILL.md +0 -2
  87. package/assets/skills/principle-make-operations-idempotent/SKILL.md +0 -2
  88. package/assets/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +0 -2
  89. package/assets/skills/principle-minimize-reader-load/SKILL.md +0 -2
  90. package/assets/skills/principle-model-the-domain/SKILL.md +0 -2
  91. package/assets/skills/principle-never-block-on-the-human/SKILL.md +0 -2
  92. package/assets/skills/principle-outcome-oriented-execution/SKILL.md +0 -2
  93. package/assets/skills/principle-prove-it-works/SKILL.md +2 -2
  94. package/assets/skills/principle-redesign-from-first-principles/SKILL.md +0 -2
  95. package/assets/skills/principle-separate-before-serializing-shared-state/SKILL.md +0 -2
  96. package/assets/skills/principle-sequence-verifiable-units/SKILL.md +0 -2
  97. package/assets/skills/principle-subtract-before-you-add/SKILL.md +0 -2
  98. package/assets/skills/principle-test-behavior-not-implementation/SKILL.md +0 -2
  99. package/assets/skills/principle-type-system-discipline/SKILL.md +0 -2
  100. package/assets/skills/recall/SKILL.md +9 -9
  101. package/assets/skills/reflect/SKILL.md +20 -21
  102. package/assets/skills/reflect/references/divergent-reviewer.md +4 -4
  103. package/assets/skills/reflect/references/judgment-reviewer.md +2 -2
  104. package/assets/skills/reflect/references/synthesizer.md +6 -6
  105. package/assets/skills/reflect/references/tooling-reviewer.md +2 -2
  106. package/assets/skills/setup-pstack/SKILL.md +28 -37
  107. package/assets/skills/sharpen/SKILL.md +0 -2
  108. package/assets/skills/show-me-your-work/SKILL.md +9 -9
  109. package/assets/skills/show-me-your-work/scripts/log.mjs +108 -0
  110. package/assets/skills/swarm/SKILL.md +8 -6
  111. package/assets/skills/tdd/SKILL.md +0 -2
  112. package/assets/skills/teach/SKILL.md +2 -2
  113. package/assets/skills/technical-writing/SKILL.md +2 -2
  114. package/assets/skills/typescript-best-practices/SKILL.md +6 -4
  115. package/assets/skills/unslop/SKILL.md +0 -2
  116. package/assets/skills/why/SKILL.md +9 -9
  117. package/cordis.patch.yml +33 -14
  118. package/dist/frontmatter-BQX3ekwP.mjs +229 -0
  119. package/dist/frontmatter-BQX3ekwP.mjs.map +1 -0
  120. package/dist/history.d.mts +429 -0
  121. package/dist/history.mjs +1129 -0
  122. package/dist/history.mjs.map +1 -0
  123. package/dist/host-contract-o47JH_r8.d.mts +527 -0
  124. package/dist/index.d.mts +273 -11
  125. package/dist/index.mjs +248 -148
  126. package/dist/index.mjs.map +1 -1
  127. package/dist/paths-D_JlVmBk.mjs +42 -0
  128. package/dist/paths-D_JlVmBk.mjs.map +1 -0
  129. package/dist/script.d.mts +193 -0
  130. package/dist/script.mjs +782 -0
  131. package/dist/script.mjs.map +1 -0
  132. package/dist/subagent.d.mts +306 -100
  133. package/dist/subagent.mjs +1309 -343
  134. package/dist/subagent.mjs.map +1 -1
  135. package/locale/en.json +6 -0
  136. package/locale/zh.json +6 -0
  137. package/package.json +55 -41
  138. package/static/icon.png +0 -0
  139. package/assets/skills/poteto-mode/scripts/bootstrap.ts +0 -62
  140. package/assets/skills/poteto-mode/scripts/bun.lock +0 -67
  141. package/assets/skills/poteto-mode/scripts/check-plan.test.ts +0 -96
  142. package/assets/skills/poteto-mode/scripts/orch/orch.test.ts +0 -761
  143. package/assets/skills/poteto-mode/scripts/orch/orch.ts +0 -590
  144. package/assets/skills/poteto-mode/scripts/orch/store.ts +0 -1677
  145. package/assets/skills/poteto-mode/scripts/package.json +0 -16
  146. package/assets/skills/poteto-mode/scripts/watch-pr/cli.test.ts +0 -224
  147. package/assets/skills/poteto-mode/scripts/watch-pr/cli.ts +0 -223
  148. package/assets/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts +0 -129
  149. package/assets/skills/poteto-mode/scripts/watch-pr/github.test.ts +0 -436
  150. package/assets/skills/poteto-mode/scripts/watch-pr/github.ts +0 -718
  151. package/assets/skills/poteto-mode/scripts/watch-pr/policy.test.ts +0 -420
  152. package/assets/skills/poteto-mode/scripts/watch-pr/policy.ts +0 -836
  153. package/assets/skills/poteto-mode/scripts/watch-pr/render.ts +0 -169
  154. package/assets/skills/poteto-mode/scripts/watch-pr/tsconfig.json +0 -13
  155. package/assets/skills/poteto-mode/scripts/watch-pr/types.compile.ts +0 -93
  156. package/assets/skills/poteto-mode/scripts/watch-pr/types.ts +0 -413
  157. package/assets/skills/poteto-mode/scripts/watch-pr/watch-pr +0 -6
  158. package/assets/skills/poteto-mode/scripts/worktree-audit.sh +0 -127
  159. package/assets/skills/show-me-your-work/scripts/log.sh +0 -42
  160. package/dist/LICENSE +0 -22
  161. package/dist/README.md +0 -159
  162. package/dist/assets/agents/comment-sicko.md +0 -32
  163. package/dist/assets/agents/poteto-agent.md +0 -8
  164. package/dist/assets/docs/guide/01-setup.md +0 -64
  165. package/dist/assets/docs/guide/02-poteto-mode.md +0 -139
  166. package/dist/assets/docs/guide/03-understand.md +0 -79
  167. package/dist/assets/docs/guide/04-design.md +0 -133
  168. package/dist/assets/docs/guide/05-build-and-clean.md +0 -83
  169. package/dist/assets/docs/guide/06-verify-and-ship.md +0 -152
  170. package/dist/assets/docs/guide/07-overnight.md +0 -122
  171. package/dist/assets/docs/guide/08-principles.md +0 -72
  172. package/dist/assets/docs/guide/09-make-it-yours.md +0 -100
  173. package/dist/assets/docs/guide/10-recipes-and-pitfalls.md +0 -180
  174. package/dist/assets/docs/guide/README.md +0 -38
  175. package/dist/assets/docs/harness/dsh.md +0 -65
  176. package/dist/assets/docs/pstack-models.example.md +0 -23
  177. package/dist/assets/skills/architect/SKILL.md +0 -81
  178. package/dist/assets/skills/architect/references/design-red-flags.md +0 -57
  179. package/dist/assets/skills/architect/references/rationale-template.md +0 -35
  180. package/dist/assets/skills/architect/references/runner-prompt.md +0 -20
  181. package/dist/assets/skills/arena/SKILL.md +0 -50
  182. package/dist/assets/skills/automate-me/SKILL.md +0 -105
  183. package/dist/assets/skills/benchmark-checklist/SKILL.md +0 -41
  184. package/dist/assets/skills/blast-radius/SKILL.md +0 -52
  185. package/dist/assets/skills/bro/SKILL.md +0 -9
  186. package/dist/assets/skills/correct/SKILL.md +0 -34
  187. package/dist/assets/skills/create-formal-verification-skill/SKILL.md +0 -43
  188. package/dist/assets/skills/create-verification-skill/SKILL.md +0 -46
  189. package/dist/assets/skills/create-verification-skill/references/feature-map-example/README.md +0 -47
  190. package/dist/assets/skills/create-verification-skill/references/feature-map-example/create-note.md +0 -39
  191. package/dist/assets/skills/create-verification-skill/references/feature-map-example/search.md +0 -45
  192. package/dist/assets/skills/figure-it-out/SKILL.md +0 -54
  193. package/dist/assets/skills/how/SKILL.md +0 -38
  194. package/dist/assets/skills/how/references/explainer-prompt.md +0 -55
  195. package/dist/assets/skills/how/references/explorer-prompt.md +0 -35
  196. package/dist/assets/skills/in-your-own-words/SKILL.md +0 -9
  197. package/dist/assets/skills/interrogate/SKILL.md +0 -69
  198. package/dist/assets/skills/interrogate/references/code-quality-review.md +0 -47
  199. package/dist/assets/skills/interrogate/references/lead-judgment.md +0 -39
  200. package/dist/assets/skills/interrogate/references/reviewer-prompt.md +0 -62
  201. package/dist/assets/skills/interrogate/references/rubric.md +0 -77
  202. package/dist/assets/skills/ketchup/SKILL.md +0 -45
  203. package/dist/assets/skills/maintain-formal-verification-skill/SKILL.md +0 -60
  204. package/dist/assets/skills/maintain-verification-skill/SKILL.md +0 -41
  205. package/dist/assets/skills/no-comments/SKILL.md +0 -26
  206. package/dist/assets/skills/poteto-help/SKILL.md +0 -161
  207. package/dist/assets/skills/poteto-help/references/prompting.md +0 -51
  208. package/dist/assets/skills/poteto-help/references/recipes.md +0 -51
  209. package/dist/assets/skills/poteto-mode/SKILL.md +0 -153
  210. package/dist/assets/skills/poteto-mode/playbooks/authoring-a-skill.md +0 -12
  211. package/dist/assets/skills/poteto-mode/playbooks/autonomous-run.md +0 -13
  212. package/dist/assets/skills/poteto-mode/playbooks/autopilot-full.md +0 -25
  213. package/dist/assets/skills/poteto-mode/playbooks/autopilot-stack.md +0 -12
  214. package/dist/assets/skills/poteto-mode/playbooks/babysit.md +0 -23
  215. package/dist/assets/skills/poteto-mode/playbooks/bug-fix.md +0 -15
  216. package/dist/assets/skills/poteto-mode/playbooks/eval.md +0 -25
  217. package/dist/assets/skills/poteto-mode/playbooks/feature.md +0 -21
  218. package/dist/assets/skills/poteto-mode/playbooks/hillclimb.md +0 -21
  219. package/dist/assets/skills/poteto-mode/playbooks/investigation.md +0 -14
  220. package/dist/assets/skills/poteto-mode/playbooks/multi-phase-plan.md +0 -155
  221. package/dist/assets/skills/poteto-mode/playbooks/opening-a-pr.md +0 -34
  222. package/dist/assets/skills/poteto-mode/playbooks/orchestrate.md +0 -112
  223. package/dist/assets/skills/poteto-mode/playbooks/pause-safely.md +0 -10
  224. package/dist/assets/skills/poteto-mode/playbooks/perf-issue.md +0 -25
  225. package/dist/assets/skills/poteto-mode/playbooks/prototype.md +0 -14
  226. package/dist/assets/skills/poteto-mode/playbooks/refactoring.md +0 -16
  227. package/dist/assets/skills/poteto-mode/playbooks/runtime-forensics.md +0 -11
  228. package/dist/assets/skills/poteto-mode/playbooks/session-pickup.md +0 -11
  229. package/dist/assets/skills/poteto-mode/playbooks/shipping.md +0 -12
  230. package/dist/assets/skills/poteto-mode/playbooks/trace-forensics.md +0 -14
  231. package/dist/assets/skills/poteto-mode/playbooks/visual-parity.md +0 -11
  232. package/dist/assets/skills/poteto-mode/playbooks/worktree-cleanup.md +0 -12
  233. package/dist/assets/skills/poteto-mode/references/bugbot-triage.md +0 -142
  234. package/dist/assets/skills/poteto-mode/scripts/bootstrap.ts +0 -62
  235. package/dist/assets/skills/poteto-mode/scripts/bun.lock +0 -67
  236. package/dist/assets/skills/poteto-mode/scripts/check-plan.mjs +0 -205
  237. package/dist/assets/skills/poteto-mode/scripts/check-plan.test.ts +0 -96
  238. package/dist/assets/skills/poteto-mode/scripts/orch/orch.test.ts +0 -761
  239. package/dist/assets/skills/poteto-mode/scripts/orch/orch.ts +0 -590
  240. package/dist/assets/skills/poteto-mode/scripts/orch/store.ts +0 -1677
  241. package/dist/assets/skills/poteto-mode/scripts/package.json +0 -16
  242. package/dist/assets/skills/poteto-mode/scripts/watch-pr/cli.test.ts +0 -224
  243. package/dist/assets/skills/poteto-mode/scripts/watch-pr/cli.ts +0 -223
  244. package/dist/assets/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts +0 -129
  245. package/dist/assets/skills/poteto-mode/scripts/watch-pr/github.test.ts +0 -436
  246. package/dist/assets/skills/poteto-mode/scripts/watch-pr/github.ts +0 -718
  247. package/dist/assets/skills/poteto-mode/scripts/watch-pr/policy.test.ts +0 -420
  248. package/dist/assets/skills/poteto-mode/scripts/watch-pr/policy.ts +0 -836
  249. package/dist/assets/skills/poteto-mode/scripts/watch-pr/render.ts +0 -169
  250. package/dist/assets/skills/poteto-mode/scripts/watch-pr/tsconfig.json +0 -13
  251. package/dist/assets/skills/poteto-mode/scripts/watch-pr/types.compile.ts +0 -93
  252. package/dist/assets/skills/poteto-mode/scripts/watch-pr/types.ts +0 -413
  253. package/dist/assets/skills/poteto-mode/scripts/watch-pr/watch-pr +0 -6
  254. package/dist/assets/skills/poteto-mode/scripts/worktree-audit.sh +0 -127
  255. package/dist/assets/skills/principle-attack-the-premise/SKILL.md +0 -23
  256. package/dist/assets/skills/principle-boundary-discipline/SKILL.md +0 -36
  257. package/dist/assets/skills/principle-build-the-lever/SKILL.md +0 -26
  258. package/dist/assets/skills/principle-encode-lessons-in-structure/SKILL.md +0 -13
  259. package/dist/assets/skills/principle-exhaust-the-design-space/SKILL.md +0 -11
  260. package/dist/assets/skills/principle-experience-first/SKILL.md +0 -21
  261. package/dist/assets/skills/principle-explain-the-number/SKILL.md +0 -19
  262. package/dist/assets/skills/principle-fix-root-causes/SKILL.md +0 -23
  263. package/dist/assets/skills/principle-foundational-thinking/SKILL.md +0 -23
  264. package/dist/assets/skills/principle-guard-the-context-window/SKILL.md +0 -14
  265. package/dist/assets/skills/principle-laziness-protocol/SKILL.md +0 -12
  266. package/dist/assets/skills/principle-make-operations-idempotent/SKILL.md +0 -24
  267. package/dist/assets/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +0 -16
  268. package/dist/assets/skills/principle-minimize-reader-load/SKILL.md +0 -20
  269. package/dist/assets/skills/principle-model-the-domain/SKILL.md +0 -26
  270. package/dist/assets/skills/principle-never-block-on-the-human/SKILL.md +0 -17
  271. package/dist/assets/skills/principle-outcome-oriented-execution/SKILL.md +0 -23
  272. package/dist/assets/skills/principle-prove-it-works/SKILL.md +0 -24
  273. package/dist/assets/skills/principle-redesign-from-first-principles/SKILL.md +0 -18
  274. package/dist/assets/skills/principle-separate-before-serializing-shared-state/SKILL.md +0 -18
  275. package/dist/assets/skills/principle-sequence-verifiable-units/SKILL.md +0 -19
  276. package/dist/assets/skills/principle-subtract-before-you-add/SKILL.md +0 -20
  277. package/dist/assets/skills/principle-test-behavior-not-implementation/SKILL.md +0 -22
  278. package/dist/assets/skills/principle-type-system-discipline/SKILL.md +0 -15
  279. package/dist/assets/skills/recall/SKILL.md +0 -37
  280. package/dist/assets/skills/reflect/SKILL.md +0 -70
  281. package/dist/assets/skills/reflect/references/divergent-reviewer.md +0 -38
  282. package/dist/assets/skills/reflect/references/judgment-reviewer.md +0 -37
  283. package/dist/assets/skills/reflect/references/synthesizer.md +0 -56
  284. package/dist/assets/skills/reflect/references/tooling-reviewer.md +0 -48
  285. package/dist/assets/skills/setup-pstack/SKILL.md +0 -76
  286. package/dist/assets/skills/sharpen/SKILL.md +0 -18
  287. package/dist/assets/skills/show-me-your-work/SKILL.md +0 -84
  288. package/dist/assets/skills/show-me-your-work/references/decision-log-template.tsv +0 -1
  289. package/dist/assets/skills/show-me-your-work/scripts/log.sh +0 -42
  290. package/dist/assets/skills/swarm/SKILL.md +0 -41
  291. package/dist/assets/skills/tdd/SKILL.md +0 -44
  292. package/dist/assets/skills/teach/SKILL.md +0 -23
  293. package/dist/assets/skills/technical-writing/SKILL.md +0 -108
  294. package/dist/assets/skills/typescript-best-practices/SKILL.md +0 -32
  295. package/dist/assets/skills/typescript-best-practices/references/patterns.md +0 -328
  296. package/dist/assets/skills/unslop/SKILL.md +0 -64
  297. package/dist/assets/skills/why/SKILL.md +0 -100
  298. package/dist/assets/skills/why/references/epistemics.md +0 -71
  299. package/dist/assets/skills/why/references/investigator-prompt.md +0 -84
  300. package/dist/assets/skills/why/references/source-playbook.md +0 -17
  301. package/dist/assets/skills/why/references/sources/code-archaeology.md +0 -51
  302. package/dist/assets/skills/why/references/sources/databricks.md +0 -70
  303. package/dist/assets/skills/why/references/sources/datadog.md +0 -97
  304. package/dist/assets/skills/why/references/sources/incident-postmortem.md +0 -5
  305. package/dist/assets/skills/why/references/sources/linear.md +0 -48
  306. package/dist/assets/skills/why/references/sources/notion.md +0 -55
  307. package/dist/assets/skills/why/references/sources/sentry.md +0 -66
  308. package/dist/assets/skills/why/references/sources/slack.md +0 -17
  309. package/dist/assets/skills/why/references/synthesizer-prompt.md +0 -129
  310. package/dist/cordis.patch.yml +0 -20
  311. package/dist/paths-Bbai4CaT.mjs +0 -16
  312. package/dist/paths-Bbai4CaT.mjs.map +0 -1
@@ -0,0 +1,1441 @@
1
+ #!/usr/bin/env node
2
+ import { describe, it } from "node:test";
3
+ import { inspect } from "node:util";
4
+ //#region .runtime-sources/skills/poteto-mode/scripts/expect.ts
5
+ /**
6
+ * A small `expect` for `node:test`.
7
+ *
8
+ * The five upstream test files were written against `bun:test`. Their assertions
9
+ * are ordinary value comparisons, so this module supplies exactly the matchers
10
+ * they use rather than a general test framework. It is deliberately not a drop-in
11
+ * replacement: a matcher that is not listed here fails to compile, which keeps the
12
+ * port auditable against the calls that actually exist.
13
+ *
14
+ * Covered: `toBe`, `toEqual`, `toContain`, `toMatch`, `toThrow`, `toBeNull`,
15
+ * `toBeInstanceOf`, `toHaveLength`, `toEndWith`, `toMatchObject`, `.not` on each,
16
+ * and `rejects` / `resolves` for promises.
17
+ *
18
+ * Two differences from `bun:test`, stated so a failure is not misread:
19
+ *
20
+ * - `toEqual` is structural for arrays and plain objects, `Object.is` otherwise.
21
+ * A class instance only equals itself; `toBeInstanceOf` is the matcher for type
22
+ * identity. The ported tests compare plain data, which is why this is enough.
23
+ * - A failure throws `AssertionError` with the two values rendered, not Bun's
24
+ * coloured diff.
25
+ */
26
+ /** Raised by every failing assertion. */
27
+ var AssertionError = class extends Error {
28
+ constructor(message) {
29
+ super(message);
30
+ this.name = "AssertionError";
31
+ }
32
+ };
33
+ function display(value) {
34
+ if (typeof value === "function") return `[Function ${value.name || "anonymous"}]`;
35
+ return inspect(value, {
36
+ depth: 3,
37
+ breakLength: 120,
38
+ maxArrayLength: 20
39
+ });
40
+ }
41
+ function messageOf(error) {
42
+ if (error instanceof Error) return `${error.name}: ${error.message}`;
43
+ return String(error);
44
+ }
45
+ /** Structural equality for the shapes the ported tests compare. */
46
+ function deepEqual(left, right) {
47
+ if (Object.is(left, right)) return true;
48
+ if (left instanceof Date || right instanceof Date) return left instanceof Date && right instanceof Date && left.getTime() === right.getTime();
49
+ if (Array.isArray(left) || Array.isArray(right)) {
50
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
51
+ return left.every((item, index) => deepEqual(item, right[index]));
52
+ }
53
+ if (left === null || right === null || typeof left !== "object" || typeof right !== "object") return false;
54
+ const leftProto = Object.getPrototypeOf(left);
55
+ if (leftProto !== Object.getPrototypeOf(right)) return false;
56
+ if (leftProto !== Object.prototype && leftProto !== null) return false;
57
+ const leftKeys = Object.keys(left);
58
+ const rightKeys = Object.keys(right);
59
+ if (leftKeys.length !== rightKeys.length) return false;
60
+ return leftKeys.every((key) => deepEqual(left[key], right[key]));
61
+ }
62
+ function contains(haystack, needle) {
63
+ if (typeof haystack === "string") return haystack.includes(String(needle));
64
+ if (Array.isArray(haystack)) return haystack.some((item) => deepEqual(item, needle));
65
+ if (haystack instanceof Set) return [...haystack].some((item) => deepEqual(item, needle));
66
+ return false;
67
+ }
68
+ /**
69
+ * Whether `expected` is a partial description of `actual`.
70
+ *
71
+ * `bun:test`'s `toMatchObject` is recursive: every key in `expected` must exist and
72
+ * match, arrays match element by element with the same partial rule, and extra keys
73
+ * in `actual` are allowed. That is what lets an assertion name only the fields it
74
+ * cares about, which several ported cases rely on.
75
+ */
76
+ function matchesObject(actual, expected) {
77
+ if (Array.isArray(expected)) {
78
+ if (!Array.isArray(actual) || actual.length !== expected.length) return false;
79
+ return expected.every((item, index) => matchesObject(actual[index], item));
80
+ }
81
+ if (expected instanceof Date) return deepEqual(actual, expected);
82
+ if (expected === null || typeof expected !== "object") return deepEqual(actual, expected);
83
+ if (actual === null || typeof actual !== "object") return false;
84
+ return Object.entries(expected).every(([key, value]) => matchesObject(actual[key], value));
85
+ }
86
+ function thrownBy(call) {
87
+ try {
88
+ call();
89
+ return { thrown: false };
90
+ } catch (error) {
91
+ return {
92
+ thrown: true,
93
+ error
94
+ };
95
+ }
96
+ }
97
+ function buildMatchers(actual, negate) {
98
+ /**
99
+ * Fail when `ok` is not what the matcher asked for.
100
+ *
101
+ * `negate` inverts the requirement, so `.not.toContain(x)` passes on a haystack
102
+ * that does not contain `x` and fails when it does.
103
+ */
104
+ const check = (ok, matcher, expected) => {
105
+ if (ok === !negate) return;
106
+ const relation = negate ? `not to ${matcher}` : matcher;
107
+ throw new AssertionError(`expected ${display(actual)} ${relation} ${display(expected)}`);
108
+ };
109
+ const checkPlain = (ok, matcher) => {
110
+ if (ok === !negate) return;
111
+ throw new AssertionError(`expected ${display(actual)} ${negate ? `not to ${matcher}` : matcher}`);
112
+ };
113
+ const matchers = {
114
+ toBe(expected) {
115
+ check(Object.is(actual, expected), "toBe", expected);
116
+ },
117
+ toEqual(expected) {
118
+ check(deepEqual(actual, expected), "toEqual", expected);
119
+ },
120
+ toContain(expected) {
121
+ check(contains(actual, expected), "toContain", expected);
122
+ },
123
+ toMatch(expected) {
124
+ const text = String(actual);
125
+ check(typeof expected === "string" ? text.includes(expected) : expected.test(text), "toMatch", expected);
126
+ },
127
+ toThrow(expected) {
128
+ if (typeof actual !== "function") throw new AssertionError(`toThrow needs a function, received ${display(actual)}`);
129
+ const result = thrownBy(actual);
130
+ if (!result.thrown) {
131
+ checkPlain(false, "toThrow");
132
+ return;
133
+ }
134
+ if (expected === void 0) {
135
+ checkPlain(true, "toThrow");
136
+ return;
137
+ }
138
+ const message = messageOf(result.error);
139
+ const ok = typeof expected === "string" ? message.includes(expected) : expected instanceof RegExp ? expected.test(message) : result.error instanceof expected;
140
+ check(ok, "toThrow", expected);
141
+ },
142
+ toBeNull() {
143
+ checkPlain(actual === null, "toBeNull");
144
+ },
145
+ toBeInstanceOf(expected) {
146
+ check(actual instanceof expected, "toBeInstanceOf", expected);
147
+ },
148
+ toHaveLength(expected) {
149
+ const length = actual?.length;
150
+ check(length === expected, "toHaveLength", expected);
151
+ },
152
+ toEndWith(expected) {
153
+ check(String(actual).endsWith(expected), "toEndWith", expected);
154
+ },
155
+ toMatchObject(expected) {
156
+ check(matchesObject(actual, expected), "toMatchObject", expected);
157
+ }
158
+ };
159
+ Object.defineProperties(matchers, {
160
+ not: {
161
+ get: () => buildMatchers(actual, !negate),
162
+ enumerable: true
163
+ },
164
+ rejects: {
165
+ get: () => buildAsyncMatchers(actual, "rejects", negate),
166
+ enumerable: true
167
+ },
168
+ resolves: {
169
+ get: () => buildAsyncMatchers(actual, "resolves", negate),
170
+ enumerable: true
171
+ }
172
+ });
173
+ return matchers;
174
+ }
175
+ /**
176
+ * Compare against a settled promise.
177
+ *
178
+ * `rejects` reads the rejection reason and fails when the promise resolved;
179
+ * `resolves` is the mirror. Each matcher awaits the settlement, which is what
180
+ * makes `await expect(promise).rejects.toThrow(...)` wait before it compares.
181
+ */
182
+ function buildAsyncMatchers(actual, mode, negate) {
183
+ if (typeof actual?.then !== "function") throw new AssertionError(`expected a promise, received ${display(actual)}`);
184
+ const wantRejection = mode === "rejects" !== negate;
185
+ const settle = async () => {
186
+ let resolved;
187
+ let rejected;
188
+ await Promise.resolve(actual).then((value) => {
189
+ resolved = { value };
190
+ }, (error) => {
191
+ rejected = { error };
192
+ });
193
+ if (wantRejection) {
194
+ if (resolved !== void 0) throw new AssertionError(`expected the promise to reject, it resolved to ${display(resolved.value)}`);
195
+ return rejected?.error;
196
+ }
197
+ if (rejected !== void 0) throw new AssertionError(`expected the promise to resolve, it rejected with ${messageOf(rejected.error)}`);
198
+ return resolved?.value;
199
+ };
200
+ /**
201
+ * Run one matcher against the settled value.
202
+ *
203
+ * `toThrow` and `toBeInstanceOf` both read the rejection reason, so a thrown
204
+ * reason is turned back into a thunk for `toThrow` rather than special-cased
205
+ * twice. `negate` is already folded into the settlement mode, so the inner
206
+ * matchers run un-negated.
207
+ */
208
+ const wrap = async (name, expected) => {
209
+ const subject = await settle();
210
+ if (name === "toThrow") {
211
+ buildMatchers(() => {
212
+ throw subject;
213
+ }, false).toThrow(expected);
214
+ return;
215
+ }
216
+ if (name === "toBeNull") {
217
+ buildMatchers(subject, false).toBeNull();
218
+ return;
219
+ }
220
+ const matchers = buildMatchers(subject, false);
221
+ const matcher = matchers[name];
222
+ if (typeof matcher !== "function") throw new AssertionError(`unknown matcher ${name}`);
223
+ matcher.call(matchers, expected);
224
+ };
225
+ const matchers = {
226
+ toBe: (expected) => wrap("toBe", expected),
227
+ toEqual: (expected) => wrap("toEqual", expected),
228
+ toContain: (expected) => wrap("toContain", expected),
229
+ toMatch: (expected) => wrap("toMatch", expected),
230
+ toThrow: (expected) => wrap("toThrow", expected),
231
+ toBeNull: () => wrap("toBeNull"),
232
+ toBeInstanceOf: (expected) => wrap("toBeInstanceOf", expected),
233
+ toHaveLength: (expected) => wrap("toHaveLength", expected),
234
+ toEndWith: (expected) => wrap("toEndWith", expected),
235
+ toMatchObject: (expected) => wrap("toMatchObject", expected)
236
+ };
237
+ Object.defineProperty(matchers, "not", {
238
+ get: () => buildAsyncMatchers(actual, mode, !negate),
239
+ enumerable: true
240
+ });
241
+ return matchers;
242
+ }
243
+ const expect = Object.assign((actual) => buildMatchers(actual, false), {
244
+ not: (actual) => buildMatchers(actual, true),
245
+ rejects: (actual) => buildAsyncMatchers(actual, "rejects", false),
246
+ resolves: (actual) => buildAsyncMatchers(actual, "resolves", false)
247
+ });
248
+ //#endregion
249
+ //#region .runtime-sources/skills/poteto-mode/scripts/watch-pr/types.ts
250
+ function nonEmpty(items) {
251
+ return items.length === 0 ? null : [items[0], ...items.slice(1)];
252
+ }
253
+ function parsePrNumber(value, label = "PR number") {
254
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) throw new Error(`${label} must be a positive integer`);
255
+ return value;
256
+ }
257
+ //#endregion
258
+ //#region .runtime-sources/skills/poteto-mode/scripts/watch-pr/github.ts
259
+ var WatcherQueryError = class extends Error {
260
+ failure;
261
+ constructor(failure) {
262
+ super(failure.detail);
263
+ this.name = "WatcherQueryError";
264
+ this.failure = failure;
265
+ }
266
+ };
267
+ var ChecksUnavailable = class extends WatcherQueryError {
268
+ constructor(detail) {
269
+ super({
270
+ kind: "checks-unavailable",
271
+ retryable: true,
272
+ detail
273
+ });
274
+ this.name = "ChecksUnavailable";
275
+ }
276
+ };
277
+ const firstLine = (value) => value.trim().split(/\r?\n/, 1)[0]?.slice(0, 240) ?? "";
278
+ function isBugbot(comment) {
279
+ if (comment === null) return false;
280
+ const author = (comment.authorLogin ?? "").toLowerCase();
281
+ const body = comment.body.toLowerCase();
282
+ return author.includes("bugbot") || author === "cursor" && [
283
+ "bugbot",
284
+ "cursor_automation_id",
285
+ "agentic security review",
286
+ "description start",
287
+ "severity"
288
+ ].some((token) => body.includes(token));
289
+ }
290
+ function passKey(comment) {
291
+ if (comment === null) return null;
292
+ for (const pattern of [/RUN_ID:\s*([a-zA-Z0-9_.:-]+)/, /CURSOR_AUTOMATION_ID:\s*([a-zA-Z0-9_.:-]+)/]) {
293
+ const match = pattern.exec(comment.body);
294
+ if (match?.[1]) return match[1];
295
+ }
296
+ return null;
297
+ }
298
+ function buildReviewThreads(threads) {
299
+ const keys = /* @__PURE__ */ new Set();
300
+ let keyless = false;
301
+ for (const thread of threads) {
302
+ if (!isBugbot(thread.firstComment)) continue;
303
+ const key = passKey(thread.firstComment);
304
+ if (key === null) keyless = true;
305
+ else keys.add(key);
306
+ }
307
+ const passes = keys.size > 0 ? keys.size : keyless ? 1 : 0;
308
+ return threads.filter((thread) => !thread.resolved).map(({ id, firstComment }) => ({
309
+ id,
310
+ firstComment,
311
+ isBugbot: isBugbot(firstComment),
312
+ bugbotReviewPasses: passes
313
+ }));
314
+ }
315
+ async function resolveChecks(reader, context) {
316
+ const fast = await reader.checksFastPath(context);
317
+ const direct = fast.kind === "checks" ? nonEmpty(fast.checks) : null;
318
+ if (direct !== null) return {
319
+ source: "gh-pr-checks",
320
+ checks: direct
321
+ };
322
+ const checks = [];
323
+ let after = null;
324
+ do {
325
+ const page = await reader.checkRollupPage(context, after);
326
+ checks.push(...page.checks);
327
+ after = page.endCursor;
328
+ } while (after !== null);
329
+ const fallback = nonEmpty(checks);
330
+ if (fallback !== null) return {
331
+ source: "graphql-rollup",
332
+ checks: fallback
333
+ };
334
+ throw new ChecksUnavailable(`could not read PR checks: ${fast.kind === "unusable" ? `fast path exit=${fast.exitCode}; GraphQL rollup was empty${firstLine(fast.stderr) ? `; ${firstLine(fast.stderr)}` : ""}` : "fast path and GraphQL rollup were empty"}`);
335
+ }
336
+ async function resolveReviewThreads(reader, context) {
337
+ const threads = [];
338
+ let after = null;
339
+ do {
340
+ const page = await reader.reviewThreadPage(context, after);
341
+ threads.push(...page.threads);
342
+ after = page.endCursor;
343
+ } while (after !== null);
344
+ return buildReviewThreads(threads);
345
+ }
346
+ //#endregion
347
+ //#region .runtime-sources/skills/poteto-mode/scripts/watch-pr/policy.ts
348
+ function assessGitHubMerge(args) {
349
+ if (args.mergeStateStatus === "BLOCKED") {
350
+ if (args.headRollupState === "ERROR" || args.headRollupState === "FAILURE") return {
351
+ kind: "refused",
352
+ mergeStateStatus: args.mergeStateStatus,
353
+ headRollupState: args.headRollupState
354
+ };
355
+ return {
356
+ kind: "allowed",
357
+ basis: "rollup",
358
+ mergeStateStatus: args.mergeStateStatus,
359
+ headRollupState: args.headRollupState
360
+ };
361
+ }
362
+ return {
363
+ kind: "allowed",
364
+ basis: "merge-state",
365
+ mergeStateStatus: args.mergeStateStatus,
366
+ headRollupState: args.headRollupState
367
+ };
368
+ }
369
+ async function mergeAssessment(reader, facts) {
370
+ const commits = await reader.commitRollups(facts.context);
371
+ const headRollupState = facts.headRefOid === null ? null : commits.find((commit) => commit.oid === facts.headRefOid)?.state ?? null;
372
+ return {
373
+ hadPreviousPassingCi: commits.some((commit) => commit.oid !== facts.headRefOid && commit.state === "SUCCESS"),
374
+ github: assessGitHubMerge({
375
+ mergeStateStatus: facts.mergeStateStatus,
376
+ headRollupState
377
+ })
378
+ };
379
+ }
380
+ const AUTOMATION_TOKENS = [
381
+ "bugbot",
382
+ "security review",
383
+ "pr review automation",
384
+ "review automation"
385
+ ];
386
+ async function readSnapshot(args) {
387
+ const facts = await args.reader.pullRequest(args.context);
388
+ if (facts.state === "MERGED" || facts.mergedAt !== null) return {
389
+ kind: "merged",
390
+ context: args.context,
391
+ facts
392
+ };
393
+ if (facts.state === "CLOSED") return {
394
+ kind: "closed",
395
+ context: args.context,
396
+ facts
397
+ };
398
+ const threads = await resolveReviewThreads(args.reader, args.context);
399
+ const checks = await resolveChecks(args.reader, args.context);
400
+ const failed = nonEmpty(checks.checks.filter((check) => check.kind === "failed"));
401
+ const pending = nonEmpty(checks.checks.filter((check) => check.kind === "pending"));
402
+ let ci;
403
+ if (failed === null && pending !== null && args.pendingHistory === "omit") ci = {
404
+ kind: "ci-pending",
405
+ source: checks.source,
406
+ all: checks.checks,
407
+ failed: [],
408
+ pending,
409
+ hadPreviousPassingCi: false
410
+ };
411
+ else {
412
+ const merge = await mergeAssessment(args.reader, facts);
413
+ const base = {
414
+ source: checks.source,
415
+ all: checks.checks,
416
+ hadPreviousPassingCi: merge.hadPreviousPassingCi
417
+ };
418
+ if (failed !== null) ci = {
419
+ ...base,
420
+ kind: "ci-failing",
421
+ failed,
422
+ pending: pending ?? [],
423
+ github: merge.github
424
+ };
425
+ else if (merge.github.kind === "refused") ci = {
426
+ ...base,
427
+ kind: "ci-github-rejected",
428
+ failed: [],
429
+ pending: pending ?? [],
430
+ github: merge.github
431
+ };
432
+ else if (pending !== null) ci = {
433
+ ...base,
434
+ kind: "ci-pending",
435
+ failed: [],
436
+ pending
437
+ };
438
+ else ci = {
439
+ ...base,
440
+ kind: "ci-clean",
441
+ failed: [],
442
+ pending: [],
443
+ github: merge.github
444
+ };
445
+ }
446
+ return {
447
+ kind: "open",
448
+ context: args.context,
449
+ facts,
450
+ threads,
451
+ ci,
452
+ reviewAutomationRunning: checks.checks.some((check) => check.kind === "pending" && AUTOMATION_TOKENS.some((token) => check.name.toLowerCase().includes(token)))
453
+ };
454
+ }
455
+ const conflictBlocker = (row) => row.kind === "open" && (row.facts.mergeable === "CONFLICTING" || row.facts.mergeStateStatus === "DIRTY" || row.facts.mergeStateStatus === "CONFLICTING") ? {
456
+ kind: "merge-conflicts",
457
+ pr: row.context,
458
+ facts: row.facts
459
+ } : null;
460
+ function threadBlocker(row) {
461
+ if (row.kind !== "open") return null;
462
+ const threads = nonEmpty(row.threads);
463
+ return threads === null ? null : {
464
+ kind: "review-threads",
465
+ pr: row.context,
466
+ threads
467
+ };
468
+ }
469
+ const ciBlocker = (row) => row.kind === "open" && (row.ci.kind === "ci-failing" || row.ci.kind === "ci-github-rejected") ? {
470
+ kind: "failing-checks",
471
+ pr: row.context,
472
+ ci: row.ci
473
+ } : null;
474
+ function gateReason(row, allowDraft) {
475
+ if (row.kind === "merged") return null;
476
+ if (row.kind === "closed") return "closed-without-merge";
477
+ if (row.facts.isDraft && !allowDraft) return "draft-pr";
478
+ return row.facts.reviewDecision === "CHANGES_REQUESTED" ? "changes-requested" : null;
479
+ }
480
+ function gateBlocker(row, allowDraft) {
481
+ const reason = gateReason(row, allowDraft);
482
+ return reason === null || reason === "draft-pr" && row.kind === "open" && row.ci.kind === "ci-pending" ? null : {
483
+ kind: "merge-gate",
484
+ pr: row.context,
485
+ reason
486
+ };
487
+ }
488
+ function readyContribution(row, allowDraft) {
489
+ if (row.kind === "merged") return {
490
+ kind: "merged-pr",
491
+ context: row.context,
492
+ mergedAt: row.facts.mergedAt
493
+ };
494
+ if (row.kind !== "open" || row.ci.kind !== "ci-clean" || row.threads.length !== 0 || conflictBlocker(row) !== null || gateReason(row, allowDraft) !== null) return null;
495
+ const reviewDecision = row.facts.reviewDecision;
496
+ if (reviewDecision === "CHANGES_REQUESTED") return null;
497
+ return {
498
+ kind: "ready-pr",
499
+ context: row.context,
500
+ proof: {
501
+ mergeability: "clear",
502
+ threads: [],
503
+ ci: row.ci,
504
+ gate: {
505
+ state: "OPEN",
506
+ reviewDecision,
507
+ draft: row.facts.isDraft ? "draft-allowed" : "not-draft"
508
+ }
509
+ }
510
+ };
511
+ }
512
+ function classifyPr(row, allowDraft = false) {
513
+ for (const blocker of [
514
+ conflictBlocker(row),
515
+ threadBlocker(row),
516
+ ciBlocker(row),
517
+ gateBlocker(row, allowDraft)
518
+ ]) if (blocker !== null) return {
519
+ kind: "blocker",
520
+ blocker
521
+ };
522
+ if (row.kind === "open" && row.ci.kind === "ci-pending") return {
523
+ kind: "waiting",
524
+ frontier: row.context,
525
+ pending: row.ci.pending
526
+ };
527
+ const ready = readyContribution(row, allowDraft);
528
+ if (ready === null) throw new Error("snapshot has no classified decision");
529
+ return ready.kind === "merged-pr" ? {
530
+ kind: "merged",
531
+ pr: ready
532
+ } : {
533
+ kind: "ready",
534
+ pr: ready
535
+ };
536
+ }
537
+ function selectTierMajorStackDecision(rows, allowDraft = false) {
538
+ for (const tier of [
539
+ conflictBlocker,
540
+ threadBlocker,
541
+ ciBlocker
542
+ ]) for (const row of rows) {
543
+ const blocker = tier(row);
544
+ if (blocker !== null) return {
545
+ kind: "blocker",
546
+ blocker
547
+ };
548
+ }
549
+ for (const row of rows) {
550
+ const blocker = gateBlocker(row, allowDraft);
551
+ if (blocker !== null) return {
552
+ kind: "blocker",
553
+ blocker
554
+ };
555
+ }
556
+ for (const row of rows) if (row.kind === "open" && row.ci.kind === "ci-pending") return {
557
+ kind: "waiting",
558
+ frontier: row.context,
559
+ pending: row.ci.pending
560
+ };
561
+ const prs = nonEmpty(rows.map((row) => readyContribution(row, allowDraft)).filter((row) => row !== null));
562
+ if (prs === null || prs.length !== rows.length) throw new Error("stack has no classified decision");
563
+ return {
564
+ kind: "clear",
565
+ prs
566
+ };
567
+ }
568
+ const queryBackoffSeconds = (interval, failures) => Math.min(Math.max(interval, 60) * 2 ** (failures - 1), 300);
569
+ function verdictFactory(clock, mode) {
570
+ let sequence = 0;
571
+ function stamp(payload, override) {
572
+ return {
573
+ schemaVersion: 1,
574
+ sequence: sequence += 1,
575
+ observedAt: clock.observedAt(),
576
+ mode: override ?? mode,
577
+ ...payload
578
+ };
579
+ }
580
+ return stamp;
581
+ }
582
+ function blockerVerdict(stamp, blocker) {
583
+ switch (blocker.kind) {
584
+ case "merge-conflicts": return stamp({
585
+ kind: "BLOCKER",
586
+ terminal: true,
587
+ exitCode: 2,
588
+ blocker
589
+ });
590
+ case "review-threads": return stamp({
591
+ kind: "BLOCKER",
592
+ terminal: true,
593
+ exitCode: 3,
594
+ blocker
595
+ });
596
+ case "failing-checks": return stamp({
597
+ kind: "BLOCKER",
598
+ terminal: true,
599
+ exitCode: 4,
600
+ blocker
601
+ });
602
+ case "merge-gate": return stamp({
603
+ kind: "BLOCKER",
604
+ terminal: true,
605
+ exitCode: 6,
606
+ blocker
607
+ });
608
+ default: return blocker;
609
+ }
610
+ }
611
+ function statusQueryVerdict(stamp, failures, failure) {
612
+ return stamp({
613
+ kind: "BLOCKER",
614
+ terminal: true,
615
+ exitCode: 7,
616
+ blocker: {
617
+ kind: "status-query",
618
+ failures,
619
+ failure
620
+ }
621
+ });
622
+ }
623
+ const deadlinePassed = (started, options, now) => options.timeout > 0 && now - started >= options.timeout;
624
+ async function pollUntilTerminal(args) {
625
+ let failures = 0;
626
+ const started = args.dependencies.clock.now();
627
+ while (true) {
628
+ let result;
629
+ try {
630
+ result = await args.step();
631
+ failures = 0;
632
+ } catch (error) {
633
+ if (!(error instanceof WatcherQueryError)) throw error;
634
+ failures += 1;
635
+ if (!error.failure.retryable || failures >= args.options.maxQueryErrors) return statusQueryVerdict(args.stamp, failures, error.failure);
636
+ const retryInSeconds = queryBackoffSeconds(args.options.interval, failures);
637
+ args.dependencies.emit(args.stamp({
638
+ kind: "RETRY",
639
+ terminal: false,
640
+ failure: error.failure,
641
+ consecutiveFailures: failures,
642
+ retryInSeconds
643
+ }));
644
+ if (deadlinePassed(started, args.options, args.dependencies.clock.now())) return args.stamp({
645
+ kind: "TIMEOUT",
646
+ terminal: true,
647
+ exitCode: 5,
648
+ reason: {
649
+ kind: "status-unavailable",
650
+ failure: error.failure
651
+ }
652
+ });
653
+ await args.dependencies.clock.sleep(retryInSeconds);
654
+ continue;
655
+ }
656
+ if (result.kind === "terminal") return result.verdict;
657
+ if (result.kind === "sleep") {
658
+ if (result.onDeadline !== void 0 && deadlinePassed(started, args.options, args.dependencies.clock.now())) return result.onDeadline();
659
+ await args.dependencies.clock.sleep(result.seconds);
660
+ }
661
+ }
662
+ }
663
+ const createQueueState = (queue, now) => ({
664
+ queue,
665
+ snapshots: /* @__PURE__ */ new Map(),
666
+ work: {
667
+ kind: "whole-stack-sweep",
668
+ remaining: queue
669
+ },
670
+ nextSweepAt: now,
671
+ frontier: null,
672
+ lastWaitKey: null,
673
+ startedAt: now
674
+ });
675
+ const orderedRows = (state) => state.queue.flatMap((context) => {
676
+ const row = state.snapshots.get(context.number);
677
+ return row === void 0 ? [] : [row];
678
+ });
679
+ const activeRows = (state) => orderedRows(state).filter((row) => row.kind !== "merged");
680
+ function planQueue(state, now) {
681
+ if (state.work !== null) return state;
682
+ if (state.snapshots.size === 0 || now >= state.nextSweepAt) {
683
+ const remaining = nonEmpty(state.queue.filter((context) => state.snapshots.get(context.number)?.kind !== "merged"));
684
+ if (remaining !== null) return {
685
+ ...state,
686
+ work: {
687
+ kind: "whole-stack-sweep",
688
+ remaining
689
+ }
690
+ };
691
+ }
692
+ const frontier = activeRows(state)[0]?.context;
693
+ return frontier === void 0 ? state : {
694
+ ...state,
695
+ work: {
696
+ kind: "frontier-poll",
697
+ frontier
698
+ }
699
+ };
700
+ }
701
+ function applyQueueSnapshot(state, snapshot, now, options) {
702
+ if (state.work === null) throw new Error("queue has no read in flight");
703
+ const snapshots = new Map(state.snapshots);
704
+ snapshots.set(snapshot.context.number, snapshot);
705
+ const base = {
706
+ ...state,
707
+ snapshots
708
+ };
709
+ if (state.work.kind === "frontier-poll") return {
710
+ state: {
711
+ ...base,
712
+ work: null
713
+ },
714
+ completedSweepRows: null
715
+ };
716
+ const [head, ...tail] = state.work.remaining;
717
+ if (head.number !== snapshot.context.number) throw new Error("snapshot does not match sweep head");
718
+ const remaining = nonEmpty(tail);
719
+ if (remaining !== null) return {
720
+ state: {
721
+ ...base,
722
+ work: {
723
+ kind: "whole-stack-sweep",
724
+ remaining
725
+ }
726
+ },
727
+ completedSweepRows: null
728
+ };
729
+ const rows = nonEmpty(state.queue.flatMap((context) => {
730
+ const row = snapshots.get(context.number);
731
+ return row === void 0 ? [] : [row];
732
+ }));
733
+ if (rows === null || rows.length !== state.queue.length) throw new Error("sweep completed without every snapshot");
734
+ return {
735
+ state: {
736
+ ...base,
737
+ work: null,
738
+ nextSweepAt: now + options.sweepInterval
739
+ },
740
+ completedSweepRows: rows
741
+ };
742
+ }
743
+ function evaluateQueue(state, now, options) {
744
+ const active = activeRows(state);
745
+ if (active.length === 0) {
746
+ const merged = nonEmpty(orderedRows(state).flatMap((row) => row.kind === "merged" ? [{
747
+ kind: "merged-pr",
748
+ context: row.context,
749
+ mergedAt: row.facts.mergedAt
750
+ }] : []));
751
+ if (merged === null) throw new Error("empty queue cannot complete");
752
+ return {
753
+ kind: "complete",
754
+ state,
755
+ merged
756
+ };
757
+ }
758
+ const rows = nonEmpty(active);
759
+ if (rows === null) throw new Error("active queue cannot be empty");
760
+ const decision = selectTierMajorStackDecision(rows, options.allowDraft);
761
+ if (decision.kind === "blocker") return {
762
+ kind: "blocker",
763
+ state,
764
+ blocker: decision.blocker
765
+ };
766
+ const frontier = rows[0].context;
767
+ if (state.frontier !== null && state.frontier.number !== frontier.number) return {
768
+ kind: "advance",
769
+ state: {
770
+ ...state,
771
+ frontier,
772
+ lastWaitKey: null
773
+ },
774
+ merged: state.frontier,
775
+ frontier,
776
+ remaining: active.length
777
+ };
778
+ if (deadlinePassed(state.startedAt, options, now)) return {
779
+ kind: "timeout",
780
+ state: {
781
+ ...state,
782
+ frontier
783
+ },
784
+ frontier,
785
+ unmergedCount: active.length
786
+ };
787
+ const row = rows[0];
788
+ const pending = row.kind === "open" && row.ci.kind === "ci-pending" ? row.ci.pending : null;
789
+ const reason = pending === null ? {
790
+ kind: "merge-queue",
791
+ unmergedCount: active.length
792
+ } : {
793
+ kind: "pending-checks",
794
+ pending
795
+ };
796
+ const key = reason.kind === "pending-checks" ? `pending:${frontier.number}:${reason.pending.length}` : `queue:${frontier.number}:${reason.unmergedCount}`;
797
+ return {
798
+ kind: "waiting",
799
+ state: {
800
+ ...state,
801
+ frontier,
802
+ lastWaitKey: key
803
+ },
804
+ frontier,
805
+ reason,
806
+ emit: state.lastWaitKey !== key
807
+ };
808
+ }
809
+ async function runQueued(args) {
810
+ let state = createQueueState(args.contexts, args.dependencies.clock.now());
811
+ const stamp = verdictFactory(args.dependencies.clock, "queued-stack");
812
+ args.dependencies.emit(stamp({
813
+ kind: "QUEUE",
814
+ terminal: false,
815
+ queue: args.contexts
816
+ }));
817
+ const step = async () => {
818
+ state = planQueue(state, args.dependencies.clock.now());
819
+ if (state.work === null) {
820
+ const complete = evaluateQueue(state, args.dependencies.clock.now(), args.options);
821
+ if (complete.kind !== "complete") throw new Error("queue has no work while active");
822
+ return {
823
+ kind: "terminal",
824
+ verdict: stamp({
825
+ kind: "COMPLETE",
826
+ terminal: true,
827
+ exitCode: 0,
828
+ queue: state.queue,
829
+ merged: complete.merged
830
+ })
831
+ };
832
+ }
833
+ const context = state.work.kind === "whole-stack-sweep" ? state.work.remaining[0] : state.work.frontier;
834
+ const snapshot = await readSnapshot({
835
+ reader: args.dependencies.reader,
836
+ context,
837
+ pendingHistory: "omit",
838
+ allowDraft: args.options.allowDraft
839
+ });
840
+ const applied = applyQueueSnapshot(state, snapshot, args.dependencies.clock.now(), args.options);
841
+ state = applied.state;
842
+ if (applied.completedSweepRows !== null) args.dependencies.emit(stamp({
843
+ kind: "STATUS",
844
+ terminal: false,
845
+ reason: "whole-stack-sweep",
846
+ rows: applied.completedSweepRows
847
+ }));
848
+ if (state.work !== null) return { kind: "continue" };
849
+ const evaluation = evaluateQueue(state, args.dependencies.clock.now(), args.options);
850
+ state = evaluation.state;
851
+ switch (evaluation.kind) {
852
+ case "complete": return {
853
+ kind: "terminal",
854
+ verdict: stamp({
855
+ kind: "COMPLETE",
856
+ terminal: true,
857
+ exitCode: 0,
858
+ queue: state.queue,
859
+ merged: evaluation.merged
860
+ })
861
+ };
862
+ case "blocker": return {
863
+ kind: "terminal",
864
+ verdict: blockerVerdict(stamp, evaluation.blocker)
865
+ };
866
+ case "advance":
867
+ args.dependencies.emit(stamp({
868
+ kind: "ADVANCE",
869
+ terminal: false,
870
+ merged: evaluation.merged,
871
+ frontier: evaluation.frontier,
872
+ remaining: evaluation.remaining
873
+ }));
874
+ return { kind: "continue" };
875
+ case "timeout": return {
876
+ kind: "terminal",
877
+ verdict: stamp({
878
+ kind: "TIMEOUT",
879
+ terminal: true,
880
+ exitCode: 5,
881
+ reason: {
882
+ kind: "queued-stack",
883
+ frontier: evaluation.frontier,
884
+ unmergedCount: evaluation.unmergedCount
885
+ }
886
+ })
887
+ };
888
+ case "waiting":
889
+ if (evaluation.emit) args.dependencies.emit(stamp({
890
+ kind: "WAITING",
891
+ terminal: false,
892
+ frontier: evaluation.frontier,
893
+ reason: evaluation.reason
894
+ }));
895
+ return {
896
+ kind: "sleep",
897
+ seconds: args.options.interval
898
+ };
899
+ default: return evaluation;
900
+ }
901
+ };
902
+ return pollUntilTerminal({
903
+ dependencies: args.dependencies,
904
+ options: args.options,
905
+ stamp,
906
+ step
907
+ });
908
+ }
909
+ //#endregion
910
+ //#region .runtime-sources/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts
911
+ function toRawThread(thread) {
912
+ return {
913
+ id: thread.id,
914
+ firstComment: thread.firstComment,
915
+ resolved: false
916
+ };
917
+ }
918
+ function passingCheck(name = "ci") {
919
+ return {
920
+ kind: "passed",
921
+ name,
922
+ reportedState: "SUCCESS",
923
+ description: "",
924
+ link: "",
925
+ workflow: ""
926
+ };
927
+ }
928
+ function pendingCheck(name = "ci") {
929
+ return {
930
+ kind: "pending",
931
+ name,
932
+ reportedState: "PENDING",
933
+ description: "",
934
+ link: "",
935
+ workflow: ""
936
+ };
937
+ }
938
+ function failedCheck(name = "ci") {
939
+ return {
940
+ kind: "failed",
941
+ name,
942
+ reportedState: "FAILURE",
943
+ description: "",
944
+ link: "",
945
+ workflow: ""
946
+ };
947
+ }
948
+ function fakeReader(options = {}) {
949
+ const calls = [];
950
+ const context = options.current ?? {
951
+ owner: "owner",
952
+ repo: "repo",
953
+ number: parsePrNumber(1)
954
+ };
955
+ const defaults = {
956
+ context,
957
+ mergeable: "MERGEABLE",
958
+ mergeStateStatus: "CLEAN",
959
+ reviewDecision: "APPROVED",
960
+ headRefOid: "head",
961
+ headRefName: "feature",
962
+ baseRefName: "main",
963
+ state: "OPEN",
964
+ mergedAt: null,
965
+ isDraft: false
966
+ };
967
+ let page = 0;
968
+ let threadPage = 0;
969
+ return {
970
+ calls,
971
+ async originRepo() {
972
+ calls.push("originRepo");
973
+ return options.origin === void 0 ? {
974
+ owner: "owner",
975
+ repo: "repo"
976
+ } : options.origin;
977
+ },
978
+ async currentPr(pr) {
979
+ calls.push("currentPr");
980
+ return {
981
+ ...context,
982
+ number: pr ?? context.number
983
+ };
984
+ },
985
+ async pullRequest(requested) {
986
+ calls.push("pullRequest");
987
+ return {
988
+ ...defaults,
989
+ ...options.facts,
990
+ context: requested
991
+ };
992
+ },
993
+ async openPullRequests() {
994
+ calls.push("openPullRequests");
995
+ return options.openPullRequests ?? [];
996
+ },
997
+ async checksFastPath() {
998
+ calls.push("checksFastPath");
999
+ return options.fastPath ?? {
1000
+ kind: "checks",
1001
+ checks: [passingCheck()]
1002
+ };
1003
+ },
1004
+ async checkRollupPage(_requested, after) {
1005
+ calls.push(`checkRollupPage:${after ?? "null"}`);
1006
+ return options.rollupPages?.[page++] ?? {
1007
+ checks: [],
1008
+ endCursor: null
1009
+ };
1010
+ },
1011
+ async reviewThreadPage(_requested, after) {
1012
+ calls.push(`reviewThreadPage:${after ?? "null"}`);
1013
+ return options.threadPages?.[threadPage++] ?? {
1014
+ threads: (options.threads ?? []).map(toRawThread),
1015
+ endCursor: null
1016
+ };
1017
+ },
1018
+ async commitRollups() {
1019
+ calls.push("commitRollups");
1020
+ return options.commitRollups ?? [{
1021
+ oid: "head",
1022
+ state: "SUCCESS"
1023
+ }];
1024
+ }
1025
+ };
1026
+ }
1027
+ //#endregion
1028
+ //#region .runtime-sources/skills/poteto-mode/scripts/watch-pr/policy.test.ts
1029
+ const context = (number) => ({
1030
+ owner: "owner",
1031
+ repo: "repo",
1032
+ number: parsePrNumber(number)
1033
+ });
1034
+ const options = {
1035
+ interval: 10,
1036
+ sweepInterval: 300,
1037
+ timeout: 0,
1038
+ maxQueryErrors: 5,
1039
+ allowDraft: false
1040
+ };
1041
+ describe("readiness truth table", () => {
1042
+ it("covers every specified row and every UNKNOWN rollup value", () => {
1043
+ for (const [mergeStateStatus, headRollupState, expected] of [
1044
+ [
1045
+ "BLOCKED",
1046
+ "FAILURE",
1047
+ "refused"
1048
+ ],
1049
+ [
1050
+ "BLOCKED",
1051
+ "ERROR",
1052
+ "refused"
1053
+ ],
1054
+ [
1055
+ "BLOCKED",
1056
+ "PENDING",
1057
+ "allowed"
1058
+ ],
1059
+ [
1060
+ "UNSTABLE",
1061
+ "FAILURE",
1062
+ "allowed"
1063
+ ],
1064
+ [
1065
+ "UNKNOWN",
1066
+ "ERROR",
1067
+ "allowed"
1068
+ ],
1069
+ [
1070
+ "UNKNOWN",
1071
+ "EXPECTED",
1072
+ "allowed"
1073
+ ],
1074
+ [
1075
+ "UNKNOWN",
1076
+ "FAILURE",
1077
+ "allowed"
1078
+ ],
1079
+ [
1080
+ "UNKNOWN",
1081
+ "PENDING",
1082
+ "allowed"
1083
+ ],
1084
+ [
1085
+ "UNKNOWN",
1086
+ "SUCCESS",
1087
+ "allowed"
1088
+ ],
1089
+ [
1090
+ "UNKNOWN",
1091
+ null,
1092
+ "allowed"
1093
+ ],
1094
+ [
1095
+ "CLEAN",
1096
+ "SUCCESS",
1097
+ "allowed"
1098
+ ]
1099
+ ]) expect(assessGitHubMerge({
1100
+ mergeStateStatus,
1101
+ headRollupState
1102
+ }).kind).toBe(expected);
1103
+ });
1104
+ it("turns a clean visible list plus GitHub refusal into an explicit CI blocker", async () => {
1105
+ const snapshot = await readSnapshot({
1106
+ reader: fakeReader({
1107
+ facts: { mergeStateStatus: "BLOCKED" },
1108
+ fastPath: {
1109
+ kind: "checks",
1110
+ checks: [passingCheck()]
1111
+ },
1112
+ commitRollups: [{
1113
+ oid: "head",
1114
+ state: "FAILURE"
1115
+ }]
1116
+ }),
1117
+ context: context(1),
1118
+ pendingHistory: "include",
1119
+ allowDraft: false
1120
+ });
1121
+ expect(snapshot.kind).toBe("open");
1122
+ if (snapshot.kind !== "open") throw new Error("expected open snapshot");
1123
+ expect(snapshot.ci.kind).toBe("ci-github-rejected");
1124
+ expect(classifyPr(snapshot)).toMatchObject({
1125
+ kind: "blocker",
1126
+ blocker: { kind: "failing-checks" }
1127
+ });
1128
+ });
1129
+ });
1130
+ describe("snapshot query planning", () => {
1131
+ it("does not query commit rollups while queued checks are pending", async () => {
1132
+ const reader = fakeReader({ fastPath: {
1133
+ kind: "checks",
1134
+ checks: [pendingCheck()]
1135
+ } });
1136
+ const snapshot = await readSnapshot({
1137
+ reader,
1138
+ context: context(2),
1139
+ pendingHistory: "omit",
1140
+ allowDraft: false
1141
+ });
1142
+ expect(snapshot.kind).toBe("open");
1143
+ if (snapshot.kind !== "open") throw new Error("expected open snapshot");
1144
+ expect(snapshot.ci.kind).toBe("ci-pending");
1145
+ expect(reader.calls).toEqual([
1146
+ "pullRequest",
1147
+ "reviewThreadPage:null",
1148
+ "checksFastPath"
1149
+ ]);
1150
+ });
1151
+ it("queries rollups for settled and failed lists", async () => {
1152
+ const settled = fakeReader();
1153
+ await readSnapshot({
1154
+ reader: settled,
1155
+ context: context(3),
1156
+ pendingHistory: "omit",
1157
+ allowDraft: false
1158
+ });
1159
+ expect(settled.calls).toContain("commitRollups");
1160
+ const failed = fakeReader({ fastPath: {
1161
+ kind: "checks",
1162
+ checks: [failedCheck()]
1163
+ } });
1164
+ await readSnapshot({
1165
+ reader: failed,
1166
+ context: context(4),
1167
+ pendingHistory: "omit",
1168
+ allowDraft: false
1169
+ });
1170
+ expect(failed.calls).toContain("commitRollups");
1171
+ });
1172
+ it("short-circuits merged rows before threads and checks", async () => {
1173
+ const reader = fakeReader({ facts: {
1174
+ state: "MERGED",
1175
+ mergedAt: "2026-07-26T00:00:00Z"
1176
+ } });
1177
+ expect((await readSnapshot({
1178
+ reader,
1179
+ context: context(5),
1180
+ pendingHistory: "include",
1181
+ allowDraft: false
1182
+ })).kind).toBe("merged");
1183
+ expect(reader.calls).toEqual(["pullRequest"]);
1184
+ });
1185
+ });
1186
+ it("scans stacks tier-major so an upstack conflict outranks frontier CI", async () => {
1187
+ const decision = selectTierMajorStackDecision([await readSnapshot({
1188
+ reader: fakeReader({
1189
+ fastPath: {
1190
+ kind: "checks",
1191
+ checks: [failedCheck()]
1192
+ },
1193
+ commitRollups: [{
1194
+ oid: "head",
1195
+ state: "FAILURE"
1196
+ }]
1197
+ }),
1198
+ context: context(10),
1199
+ pendingHistory: "omit",
1200
+ allowDraft: false
1201
+ }), await readSnapshot({
1202
+ reader: fakeReader({ facts: { mergeable: "CONFLICTING" } }),
1203
+ context: context(11),
1204
+ pendingHistory: "omit",
1205
+ allowDraft: false
1206
+ })]);
1207
+ expect(decision).toMatchObject({
1208
+ kind: "blocker",
1209
+ blocker: {
1210
+ kind: "merge-conflicts",
1211
+ pr: { number: 11 }
1212
+ }
1213
+ });
1214
+ });
1215
+ it("attributes a stack wait to the PR whose checks are pending, not the bottom", async () => {
1216
+ const decision = selectTierMajorStackDecision([await readSnapshot({
1217
+ reader: fakeReader(),
1218
+ context: context(20),
1219
+ pendingHistory: "omit",
1220
+ allowDraft: false
1221
+ }), await readSnapshot({
1222
+ reader: fakeReader({ fastPath: {
1223
+ kind: "checks",
1224
+ checks: [pendingCheck("upstack-build")]
1225
+ } }),
1226
+ context: context(21),
1227
+ pendingHistory: "omit",
1228
+ allowDraft: false
1229
+ })]);
1230
+ expect(decision).toMatchObject({
1231
+ kind: "waiting",
1232
+ frontier: { number: 21 },
1233
+ pending: [{ name: "upstack-build" }]
1234
+ });
1235
+ });
1236
+ it("waits on a draft while checks are pending, then reports the draft gate", async () => {
1237
+ const pending = await readSnapshot({
1238
+ reader: fakeReader({
1239
+ facts: { isDraft: true },
1240
+ fastPath: {
1241
+ kind: "checks",
1242
+ checks: [pendingCheck()]
1243
+ }
1244
+ }),
1245
+ context: context(12),
1246
+ pendingHistory: "omit",
1247
+ allowDraft: false
1248
+ });
1249
+ expect(classifyPr(pending).kind).toBe("waiting");
1250
+ const settled = await readSnapshot({
1251
+ reader: fakeReader({ facts: { isDraft: true } }),
1252
+ context: context(12),
1253
+ pendingHistory: "omit",
1254
+ allowDraft: false
1255
+ });
1256
+ expect(classifyPr(settled)).toMatchObject({
1257
+ kind: "blocker",
1258
+ blocker: {
1259
+ kind: "merge-gate",
1260
+ reason: "draft-pr"
1261
+ }
1262
+ });
1263
+ });
1264
+ describe("queued-stack cadence", () => {
1265
+ async function openSnapshot(pr) {
1266
+ return readSnapshot({
1267
+ reader: fakeReader(),
1268
+ context: pr,
1269
+ pendingHistory: "omit",
1270
+ allowDraft: false
1271
+ });
1272
+ }
1273
+ it("drops a sweep head only after its snapshot succeeds", async () => {
1274
+ const queue = [
1275
+ context(20),
1276
+ context(21),
1277
+ context(22)
1278
+ ];
1279
+ let state = createQueueState(queue, 0);
1280
+ const first = await openSnapshot(queue[0]);
1281
+ state = applyQueueSnapshot(state, first, 0, options).state;
1282
+ expect(state.work).toMatchObject({
1283
+ kind: "whole-stack-sweep",
1284
+ remaining: [{ number: 21 }, { number: 22 }]
1285
+ });
1286
+ const second = await openSnapshot(queue[1]);
1287
+ state = applyQueueSnapshot(state, second, 60, options).state;
1288
+ expect(state.work).toMatchObject({
1289
+ kind: "whole-stack-sweep",
1290
+ remaining: [{ number: 22 }]
1291
+ });
1292
+ });
1293
+ it("resumes the sweep at the PR whose read failed", async () => {
1294
+ const middle = context(21);
1295
+ const base = fakeReader();
1296
+ let failNext = true;
1297
+ const timeline = [];
1298
+ const reader = {
1299
+ ...base,
1300
+ async pullRequest(pr) {
1301
+ if (pr.number === middle.number && failNext) {
1302
+ failNext = false;
1303
+ timeline.push(`fail:${pr.number}`);
1304
+ throw new WatcherQueryError({
1305
+ kind: "command-exit",
1306
+ retryable: true,
1307
+ detail: "rate limited",
1308
+ code: 1
1309
+ });
1310
+ }
1311
+ timeline.push(`read:${pr.number}`);
1312
+ return base.pullRequest(pr);
1313
+ }
1314
+ };
1315
+ let now = 0;
1316
+ let sleeps = 0;
1317
+ const running = runQueued({
1318
+ dependencies: {
1319
+ reader,
1320
+ clock: {
1321
+ now: () => now,
1322
+ observedAt: () => "2026-07-26T00:00:00.000Z",
1323
+ async sleep(seconds) {
1324
+ timeline.push("sleep");
1325
+ now += seconds;
1326
+ sleeps += 1;
1327
+ if (sleeps === 2) throw new Error("stop after resume proof");
1328
+ }
1329
+ },
1330
+ emit(verdict) {
1331
+ timeline.push(`emit:${verdict.kind}`);
1332
+ }
1333
+ },
1334
+ contexts: [
1335
+ context(20),
1336
+ middle,
1337
+ context(22)
1338
+ ],
1339
+ options
1340
+ });
1341
+ await expect(running).rejects.toThrow("stop after resume proof");
1342
+ expect(timeline).toEqual([
1343
+ "emit:QUEUE",
1344
+ "read:20",
1345
+ "fail:21",
1346
+ "emit:RETRY",
1347
+ "sleep",
1348
+ "read:21",
1349
+ "read:22",
1350
+ "emit:STATUS",
1351
+ "emit:WAITING",
1352
+ "sleep"
1353
+ ]);
1354
+ });
1355
+ it("emits a completed sweep only after its final successful snapshot", async () => {
1356
+ const queue = [context(30), context(31)];
1357
+ let state = createQueueState(queue, 0);
1358
+ const first = applyQueueSnapshot(state, await openSnapshot(queue[0]), 0, options);
1359
+ expect(first.completedSweepRows).toBeNull();
1360
+ state = first.state;
1361
+ const second = applyQueueSnapshot(state, await openSnapshot(queue[1]), 5, options);
1362
+ expect(second.completedSweepRows?.map((row) => Number(row.context.number))).toEqual([30, 31]);
1363
+ expect(second.state.nextSweepAt).toBe(305);
1364
+ });
1365
+ it("ADVANCE continues directly to the new frontier without sleeping", async () => {
1366
+ const one = context(40);
1367
+ const two = context(41);
1368
+ const base = fakeReader();
1369
+ const reads = /* @__PURE__ */ new Map();
1370
+ const timeline = [];
1371
+ const reader = {
1372
+ ...base,
1373
+ async pullRequest(pr) {
1374
+ timeline.push(`read:${pr.number}`);
1375
+ const facts = await base.pullRequest(pr);
1376
+ const count = (reads.get(pr.number) ?? 0) + 1;
1377
+ reads.set(pr.number, count);
1378
+ return pr.number === one.number && count > 1 ? {
1379
+ ...facts,
1380
+ state: "MERGED",
1381
+ mergedAt: "2026-07-26T00:00:00Z"
1382
+ } : facts;
1383
+ }
1384
+ };
1385
+ let now = 0;
1386
+ let sleeps = 0;
1387
+ const emitted = [];
1388
+ const running = runQueued({
1389
+ dependencies: {
1390
+ reader,
1391
+ clock: {
1392
+ now: () => now,
1393
+ observedAt: () => "2026-07-26T00:00:00.000Z",
1394
+ async sleep(seconds) {
1395
+ timeline.push("sleep");
1396
+ now += seconds;
1397
+ sleeps += 1;
1398
+ if (sleeps === 2) throw new Error("stop after advance proof");
1399
+ }
1400
+ },
1401
+ emit(verdict) {
1402
+ emitted.push(verdict);
1403
+ timeline.push(`emit:${verdict.kind}`);
1404
+ }
1405
+ },
1406
+ contexts: [one, two],
1407
+ options
1408
+ });
1409
+ await expect(running).rejects.toThrow("stop after advance proof");
1410
+ expect(emitted.some((event) => event.kind === "ADVANCE")).toBe(true);
1411
+ const firstSleep = timeline.indexOf("sleep");
1412
+ expect(timeline.slice(firstSleep, firstSleep + 5)).toEqual([
1413
+ "sleep",
1414
+ "read:40",
1415
+ "emit:ADVANCE",
1416
+ "read:41",
1417
+ "emit:WAITING"
1418
+ ]);
1419
+ });
1420
+ it("deduplicates identical waits and schedules the next due sweep", async () => {
1421
+ const queue = [context(50)];
1422
+ let state = createQueueState(queue, 0);
1423
+ state = applyQueueSnapshot(state, await openSnapshot(queue[0]), 0, options).state;
1424
+ const first = evaluateQueue(state, 0, options);
1425
+ expect(first.kind).toBe("waiting");
1426
+ if (first.kind !== "waiting") throw new Error("expected waiting");
1427
+ expect(first.emit).toBe(true);
1428
+ const second = evaluateQueue(first.state, 10, options);
1429
+ expect(second.kind).toBe("waiting");
1430
+ if (second.kind !== "waiting") throw new Error("expected waiting");
1431
+ expect(second.emit).toBe(false);
1432
+ expect(planQueue(second.state, 300).work?.kind).toBe("whole-stack-sweep");
1433
+ });
1434
+ });
1435
+ it("uses the specified retry floor and cap", () => {
1436
+ expect(queryBackoffSeconds(1, 1)).toBe(60);
1437
+ expect(queryBackoffSeconds(1, 2)).toBe(120);
1438
+ expect(queryBackoffSeconds(60, 4)).toBe(300);
1439
+ });
1440
+ //#endregion
1441
+ export {};