dsh-pstack 0.2.0-beta.1 → 0.2.0

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 (275) hide show
  1. package/LICENSE +2 -3
  2. package/README.md +148 -84
  3. package/README.zh.md +198 -0
  4. package/assets/pstack-config-entry.png +0 -0
  5. package/cordis.patch.yml +105 -27
  6. package/dist/client.mjs +2341 -0
  7. package/dist/client.mjs.map +1 -0
  8. package/dist/host-contract-CUPSpeNk.d.mts +22 -0
  9. package/dist/index.d.mts +49 -238
  10. package/dist/index.mjs +250 -123
  11. package/dist/models.d.mts +44 -0
  12. package/dist/models.mjs +201 -0
  13. package/dist/paths-BT77khwm.mjs +48 -0
  14. package/dist/pstack/LICENSE +21 -0
  15. package/dist/pstack/README.md +291 -0
  16. package/dist/pstack/agents/poteto-agent.md +9 -0
  17. package/{assets → dist/pstack}/docs/guide/01-setup.md +13 -11
  18. package/{assets → dist/pstack}/docs/guide/02-poteto-mode.md +4 -4
  19. package/{assets → dist/pstack}/docs/guide/04-design.md +4 -4
  20. package/{assets → dist/pstack}/docs/guide/05-build-and-clean.md +2 -2
  21. package/{assets → dist/pstack}/docs/guide/06-verify-and-ship.md +13 -3
  22. package/{assets → dist/pstack}/docs/guide/07-overnight.md +15 -13
  23. package/{assets → dist/pstack}/docs/guide/09-make-it-yours.md +6 -2
  24. package/{assets → dist/pstack}/docs/guide/10-recipes-and-pitfalls.md +7 -5
  25. package/{assets → dist/pstack}/docs/guide/README.md +1 -1
  26. package/dist/pstack/docs/harness/dsh.md +98 -0
  27. package/{assets → dist/pstack}/skills/architect/SKILL.md +12 -12
  28. package/dist/pstack/skills/arena/SKILL.md +50 -0
  29. package/{assets → dist/pstack}/skills/automate-me/SKILL.md +14 -14
  30. package/{assets → dist/pstack}/skills/benchmark-checklist/SKILL.md +1 -1
  31. package/{assets → dist/pstack}/skills/blast-radius/SKILL.md +4 -6
  32. package/{assets → dist/pstack}/skills/correct/SKILL.md +1 -1
  33. package/{assets → dist/pstack}/skills/create-formal-verification-skill/SKILL.md +4 -2
  34. package/{assets → dist/pstack}/skills/create-verification-skill/SKILL.md +5 -5
  35. package/dist/pstack/skills/figure-it-out/SKILL.md +54 -0
  36. package/dist/pstack/skills/how/SKILL.md +38 -0
  37. package/{assets → dist/pstack}/skills/in-your-own-words/SKILL.md +1 -1
  38. package/dist/pstack/skills/interrogate/SKILL.md +33 -0
  39. package/{assets → dist/pstack}/skills/ketchup/SKILL.md +3 -5
  40. package/dist/pstack/skills/ketchup-video/SKILL.md +29 -0
  41. package/{assets → dist/pstack}/skills/maintain-formal-verification-skill/SKILL.md +1 -1
  42. package/{assets → dist/pstack}/skills/maintain-verification-skill/SKILL.md +3 -3
  43. package/dist/pstack/skills/make-bot-ui/SKILL.md +117 -0
  44. package/dist/pstack/skills/no-comments/SKILL.md +26 -0
  45. package/{assets → dist/pstack}/skills/poteto-help/SKILL.md +34 -31
  46. package/{assets → dist/pstack}/skills/poteto-help/references/prompting.md +1 -1
  47. package/{assets → dist/pstack}/skills/poteto-help/references/recipes.md +3 -3
  48. package/{assets → dist/pstack}/skills/poteto-mode/SKILL.md +20 -19
  49. package/dist/pstack/skills/poteto-mode/playbooks/authoring-a-skill.md +12 -0
  50. package/dist/pstack/skills/poteto-mode/playbooks/autonomous-run.md +13 -0
  51. package/dist/pstack/skills/poteto-mode/playbooks/autopilot-full.md +25 -0
  52. package/dist/pstack/skills/poteto-mode/playbooks/autopilot-stack.md +8 -0
  53. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/babysit.md +4 -4
  54. package/dist/pstack/skills/poteto-mode/playbooks/bug-fix.md +15 -0
  55. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/eval.md +3 -3
  56. package/dist/pstack/skills/poteto-mode/playbooks/feature.md +21 -0
  57. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/hillclimb.md +3 -3
  58. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/investigation.md +2 -2
  59. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/multi-phase-plan.md +23 -23
  60. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/opening-a-pr.md +6 -6
  61. package/dist/pstack/skills/poteto-mode/playbooks/orchestrate.md +25 -0
  62. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/pause-safely.md +2 -2
  63. package/dist/pstack/skills/poteto-mode/playbooks/perf-issue.md +25 -0
  64. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/prototype.md +4 -4
  65. package/dist/pstack/skills/poteto-mode/playbooks/refactoring.md +16 -0
  66. package/dist/pstack/skills/poteto-mode/playbooks/runtime-forensics.md +11 -0
  67. package/dist/pstack/skills/poteto-mode/playbooks/session-pickup.md +11 -0
  68. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/shipping.md +2 -2
  69. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/trace-forensics.md +2 -2
  70. package/dist/pstack/skills/poteto-mode/playbooks/visual-parity.md +11 -0
  71. package/dist/pstack/skills/poteto-mode/playbooks/worktree-cleanup.md +12 -0
  72. package/{assets → dist/pstack}/skills/poteto-mode/scripts/check-plan.mjs +6 -8
  73. package/dist/pstack/skills/poteto-mode/scripts/check-plan.test.mjs +128 -0
  74. package/{assets/skills/poteto-mode/scripts/check-plan.test.mjs → dist/pstack/skills/poteto-mode/scripts/expect.mjs} +12 -101
  75. package/dist/pstack/skills/poteto-mode/scripts/support.mjs +93 -0
  76. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.mjs +123 -0
  77. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +217 -0
  78. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.mjs +119 -0
  79. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.mjs +626 -0
  80. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.test.mjs +521 -0
  81. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.mjs +657 -0
  82. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +523 -0
  83. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/render.mjs +106 -0
  84. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/types.mjs +9 -0
  85. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +5 -0
  86. package/{assets → dist/pstack}/skills/poteto-mode/scripts/worktree-audit.mjs +15 -22
  87. package/dist/pstack/skills/poteto-mode/scripts/worktree-audit.test.mjs +134 -0
  88. package/{assets → dist/pstack}/skills/principle-attack-the-premise/SKILL.md +1 -1
  89. package/{assets → dist/pstack}/skills/principle-boundary-discipline/SKILL.md +1 -1
  90. package/{assets → dist/pstack}/skills/principle-encode-lessons-in-structure/SKILL.md +1 -1
  91. package/{assets → dist/pstack}/skills/principle-exhaust-the-design-space/SKILL.md +1 -1
  92. package/{assets → dist/pstack}/skills/principle-experience-first/SKILL.md +1 -1
  93. package/{assets → dist/pstack}/skills/principle-explain-the-number/SKILL.md +3 -2
  94. package/{assets → dist/pstack}/skills/principle-fix-root-causes/SKILL.md +1 -1
  95. package/{assets → dist/pstack}/skills/principle-laziness-protocol/SKILL.md +1 -1
  96. package/{assets → dist/pstack}/skills/principle-make-operations-idempotent/SKILL.md +1 -1
  97. package/{assets → dist/pstack}/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +1 -1
  98. package/{assets → dist/pstack}/skills/principle-minimize-reader-load/SKILL.md +1 -1
  99. package/{assets → dist/pstack}/skills/principle-model-the-domain/SKILL.md +1 -1
  100. package/{assets → dist/pstack}/skills/principle-never-block-on-the-human/SKILL.md +1 -1
  101. package/{assets → dist/pstack}/skills/principle-outcome-oriented-execution/SKILL.md +1 -1
  102. package/{assets → dist/pstack}/skills/principle-prove-it-works/SKILL.md +2 -2
  103. package/{assets → dist/pstack}/skills/principle-redesign-from-first-principles/SKILL.md +1 -1
  104. package/{assets → dist/pstack}/skills/principle-separate-before-serializing-shared-state/SKILL.md +1 -1
  105. package/{assets → dist/pstack}/skills/principle-sequence-verifiable-units/SKILL.md +1 -1
  106. package/{assets → dist/pstack}/skills/principle-subtract-before-you-add/SKILL.md +1 -1
  107. package/{assets → dist/pstack}/skills/principle-test-behavior-not-implementation/SKILL.md +1 -1
  108. package/{assets → dist/pstack}/skills/principle-type-system-discipline/SKILL.md +1 -1
  109. package/dist/pstack/skills/recall/SKILL.md +37 -0
  110. package/dist/pstack/skills/reflect/SKILL.md +71 -0
  111. package/{assets → dist/pstack}/skills/reflect/references/divergent-reviewer.md +4 -4
  112. package/{assets → dist/pstack}/skills/reflect/references/judgment-reviewer.md +2 -2
  113. package/{assets → dist/pstack}/skills/reflect/references/synthesizer.md +5 -5
  114. package/{assets → dist/pstack}/skills/reflect/references/tooling-reviewer.md +2 -2
  115. package/dist/pstack/skills/setup-pstack/SKILL.md +80 -0
  116. package/{assets → dist/pstack}/skills/sharpen/SKILL.md +1 -1
  117. package/{assets → dist/pstack}/skills/show-me-your-work/SKILL.md +8 -8
  118. package/{assets → dist/pstack}/skills/show-me-your-work/scripts/log.mjs +1 -3
  119. package/dist/pstack/skills/show-me-your-work/scripts/log.test.mjs +56 -0
  120. package/dist/pstack/skills/statechart/SKILL.md +82 -0
  121. package/dist/pstack/skills/statechart/references/render.md +93 -0
  122. package/dist/pstack/skills/statechart/scripts/backends/cfg.mjs +31 -0
  123. package/dist/pstack/skills/statechart/scripts/backends/mermaid.mjs +186 -0
  124. package/dist/pstack/skills/statechart/scripts/backends/plain-text.mjs +62 -0
  125. package/dist/pstack/skills/statechart/scripts/backends/report.mjs +84 -0
  126. package/dist/pstack/skills/statechart/scripts/frontends/config.mjs +268 -0
  127. package/dist/pstack/skills/statechart/scripts/frontends/dot.mjs +67 -0
  128. package/dist/pstack/skills/statechart/scripts/frontends/marks.mjs +94 -0
  129. package/dist/pstack/skills/statechart/scripts/frontends/messages.mjs +48 -0
  130. package/dist/pstack/skills/statechart/scripts/frontends/spec.mjs +101 -0
  131. package/dist/pstack/skills/statechart/scripts/frontends/trace.mjs +39 -0
  132. package/dist/pstack/skills/statechart/scripts/frontends/value.mjs +180 -0
  133. package/dist/pstack/skills/statechart/scripts/frontends/verdict.mjs +40 -0
  134. package/dist/pstack/skills/statechart/scripts/frontends/view.mjs +99 -0
  135. package/dist/pstack/skills/statechart/scripts/ir.mjs +277 -0
  136. package/dist/pstack/skills/statechart/scripts/passes/annotate.mjs +92 -0
  137. package/dist/pstack/skills/statechart/scripts/passes/label.mjs +210 -0
  138. package/dist/pstack/skills/statechart/scripts/passes/lift.mjs +84 -0
  139. package/dist/pstack/skills/statechart/scripts/passes/loop-check.mjs +47 -0
  140. package/dist/pstack/skills/statechart/scripts/passes/paths.mjs +72 -0
  141. package/dist/pstack/skills/statechart/scripts/passes/reach.mjs +135 -0
  142. package/dist/pstack/skills/statechart/scripts/passes/region-check.mjs +82 -0
  143. package/dist/pstack/skills/statechart/scripts/passes/regions.mjs +268 -0
  144. package/dist/pstack/skills/statechart/scripts/pipeline.mjs +72 -0
  145. package/dist/pstack/skills/statechart/scripts/run.mjs +440 -0
  146. package/dist/pstack/skills/statechart/scripts/statechart.mjs +155 -0
  147. package/dist/pstack/skills/statechart/scripts/statechart.test.mjs +198 -0
  148. package/dist/pstack/skills/statechart/scripts/worker.mjs +31 -0
  149. package/dist/pstack/skills/swarm/SKILL.md +43 -0
  150. package/{assets → dist/pstack}/skills/tdd/SKILL.md +1 -1
  151. package/{assets → dist/pstack}/skills/teach/SKILL.md +2 -2
  152. package/{assets → dist/pstack}/skills/technical-writing/SKILL.md +1 -1
  153. package/{assets → dist/pstack}/skills/typescript-best-practices/SKILL.md +3 -6
  154. package/{assets → dist/pstack}/skills/why/SKILL.md +8 -8
  155. package/dist/remote.d.mts +27 -0
  156. package/dist/remote.mjs +234 -0
  157. package/dist/role-config-NNn5r1JX.mjs +415 -0
  158. package/dist/role-manifest-C3fUzjLI.d.mts +19 -0
  159. package/dist/role-wire-DZwTn2wF.d.mts +55 -0
  160. package/dist/roles.d.mts +18 -0
  161. package/dist/roles.mjs +323 -0
  162. package/dist/script.d.mts +9 -111
  163. package/dist/script.mjs +32 -329
  164. package/dist/skill.d.mts +38 -0
  165. package/dist/skill.mjs +183 -0
  166. package/dist/skills/authored/decision-audit/SKILL.md +103 -0
  167. package/dist/skills/authored/decision-audit/SKILL.zh.md +103 -0
  168. package/dist/skills/authored/decision-audit/references/recover-commit.md +17 -0
  169. package/dist/skills/authored/decision-audit/references/recover-commit.zh.md +17 -0
  170. package/dist/skills/authored/decision-write/SKILL.md +133 -0
  171. package/dist/skills/authored/decision-write/SKILL.zh.md +133 -0
  172. package/dist/skills/authored/decision-write/references/land.md +48 -0
  173. package/dist/skills/authored/decision-write/references/land.zh.md +48 -0
  174. package/dist/skills/authored/decision-write/references/scope.md +16 -0
  175. package/dist/skills/authored/decision-write/references/scope.zh.md +16 -0
  176. package/dist/skills/authored/docs-drift/SKILL.md +120 -0
  177. package/dist/skills/authored/docs-drift/SKILL.zh.md +120 -0
  178. package/dist/skills/authored/setup-project-docs/SKILL.md +63 -0
  179. package/dist/skills/authored/setup-project-docs/SKILL.zh.md +63 -0
  180. package/dist/skills/authored/setup-project-docs/references/decision-conventions.md +13 -0
  181. package/dist/skills/authored/setup-project-docs/references/decision-conventions.zh.md +13 -0
  182. package/dist/skills/vendored/retro/SKILL.md +44 -0
  183. package/dist/skills/vendored/retro/agents/openai.yaml +5 -0
  184. package/dist/skills/vendored/writing-for-agents/SKILL-MECHANICS.md +22 -0
  185. package/dist/skills/vendored/writing-for-agents/SKILL.md +81 -0
  186. package/dist/skills/vendored/writing-for-agents/agents/openai.yaml +3 -0
  187. package/dist/subagent.d.mts +54 -206
  188. package/dist/subagent.mjs +258 -963
  189. package/locale/en.json +3 -2
  190. package/locale/zh.json +3 -2
  191. package/package.json +93 -66
  192. package/assets/agents/poteto-agent.md +0 -8
  193. package/assets/docs/harness/dsh.md +0 -60
  194. package/assets/docs/pstack-models.example.md +0 -54
  195. package/assets/skills/arena/SKILL.md +0 -50
  196. package/assets/skills/figure-it-out/SKILL.md +0 -54
  197. package/assets/skills/how/SKILL.md +0 -38
  198. package/assets/skills/interrogate/SKILL.md +0 -71
  199. package/assets/skills/interrogate/references/code-quality-review.md +0 -47
  200. package/assets/skills/interrogate/references/lead-judgment.md +0 -39
  201. package/assets/skills/interrogate/references/reviewer-prompt.md +0 -62
  202. package/assets/skills/interrogate/references/rubric.md +0 -77
  203. package/assets/skills/no-comments/SKILL.md +0 -26
  204. package/assets/skills/poteto-mode/playbooks/authoring-a-skill.md +0 -12
  205. package/assets/skills/poteto-mode/playbooks/autonomous-run.md +0 -15
  206. package/assets/skills/poteto-mode/playbooks/autopilot-full.md +0 -25
  207. package/assets/skills/poteto-mode/playbooks/autopilot-stack.md +0 -12
  208. package/assets/skills/poteto-mode/playbooks/bug-fix.md +0 -15
  209. package/assets/skills/poteto-mode/playbooks/feature.md +0 -21
  210. package/assets/skills/poteto-mode/playbooks/orchestrate.md +0 -112
  211. package/assets/skills/poteto-mode/playbooks/perf-issue.md +0 -25
  212. package/assets/skills/poteto-mode/playbooks/refactoring.md +0 -16
  213. package/assets/skills/poteto-mode/playbooks/runtime-forensics.md +0 -11
  214. package/assets/skills/poteto-mode/playbooks/session-pickup.md +0 -11
  215. package/assets/skills/poteto-mode/playbooks/visual-parity.md +0 -11
  216. package/assets/skills/poteto-mode/playbooks/worktree-cleanup.md +0 -20
  217. package/assets/skills/poteto-mode/scripts/orch/orch.mjs +0 -4282
  218. package/assets/skills/poteto-mode/scripts/orch/orch.test.mjs +0 -2042
  219. package/assets/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +0 -5101
  220. package/assets/skills/poteto-mode/scripts/watch-pr/github.test.mjs +0 -1177
  221. package/assets/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +0 -1441
  222. package/assets/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +0 -4601
  223. package/assets/skills/recall/SKILL.md +0 -37
  224. package/assets/skills/reflect/SKILL.md +0 -69
  225. package/assets/skills/setup-pstack/SKILL.md +0 -67
  226. package/assets/skills/swarm/SKILL.md +0 -43
  227. package/dist/frontmatter-BQX3ekwP.mjs +0 -229
  228. package/dist/frontmatter-BQX3ekwP.mjs.map +0 -1
  229. package/dist/history.d.mts +0 -429
  230. package/dist/history.mjs +0 -1129
  231. package/dist/history.mjs.map +0 -1
  232. package/dist/host-contract-o47JH_r8.d.mts +0 -527
  233. package/dist/index.mjs.map +0 -1
  234. package/dist/paths-D_JlVmBk.mjs +0 -42
  235. package/dist/paths-D_JlVmBk.mjs.map +0 -1
  236. package/dist/script.mjs.map +0 -1
  237. package/dist/subagent.mjs.map +0 -1
  238. /package/{static/icon.png → assets/logo.png} +0 -0
  239. /package/{assets → dist/pstack}/agents/comment-sicko.md +0 -0
  240. /package/{assets → dist/pstack}/docs/guide/03-understand.md +0 -0
  241. /package/{assets → dist/pstack}/docs/guide/08-principles.md +0 -0
  242. /package/{assets → dist/pstack}/docs/guide/images/design.jpg +0 -0
  243. /package/{assets → dist/pstack}/docs/guide/images/overnight.jpg +0 -0
  244. /package/{assets → dist/pstack}/docs/guide/images/recipes.jpg +0 -0
  245. /package/{assets → dist/pstack}/docs/guide/images/router.jpg +0 -0
  246. /package/{assets → dist/pstack}/docs/guide/images/understanding.jpg +0 -0
  247. /package/{assets → dist/pstack}/docs/guide/images/verification.jpg +0 -0
  248. /package/{assets → dist/pstack}/skills/architect/references/design-red-flags.md +0 -0
  249. /package/{assets → dist/pstack}/skills/architect/references/rationale-template.md +0 -0
  250. /package/{assets → dist/pstack}/skills/architect/references/runner-prompt.md +0 -0
  251. /package/{assets → dist/pstack}/skills/bro/SKILL.md +0 -0
  252. /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/README.md +0 -0
  253. /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/create-note.md +0 -0
  254. /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/search.md +0 -0
  255. /package/{assets → dist/pstack}/skills/how/references/explainer-prompt.md +0 -0
  256. /package/{assets → dist/pstack}/skills/how/references/explorer-prompt.md +0 -0
  257. /package/{assets → dist/pstack}/skills/poteto-mode/references/bugbot-triage.md +0 -0
  258. /package/{assets → dist/pstack}/skills/principle-build-the-lever/SKILL.md +0 -0
  259. /package/{assets → dist/pstack}/skills/principle-foundational-thinking/SKILL.md +0 -0
  260. /package/{assets → dist/pstack}/skills/principle-guard-the-context-window/SKILL.md +0 -0
  261. /package/{assets → dist/pstack}/skills/show-me-your-work/references/decision-log-template.tsv +0 -0
  262. /package/{assets → dist/pstack}/skills/typescript-best-practices/references/patterns.md +0 -0
  263. /package/{assets → dist/pstack}/skills/unslop/SKILL.md +0 -0
  264. /package/{assets → dist/pstack}/skills/why/references/epistemics.md +0 -0
  265. /package/{assets → dist/pstack}/skills/why/references/investigator-prompt.md +0 -0
  266. /package/{assets → dist/pstack}/skills/why/references/source-playbook.md +0 -0
  267. /package/{assets → dist/pstack}/skills/why/references/sources/code-archaeology.md +0 -0
  268. /package/{assets → dist/pstack}/skills/why/references/sources/databricks.md +0 -0
  269. /package/{assets → dist/pstack}/skills/why/references/sources/datadog.md +0 -0
  270. /package/{assets → dist/pstack}/skills/why/references/sources/incident-postmortem.md +0 -0
  271. /package/{assets → dist/pstack}/skills/why/references/sources/linear.md +0 -0
  272. /package/{assets → dist/pstack}/skills/why/references/sources/notion.md +0 -0
  273. /package/{assets → dist/pstack}/skills/why/references/sources/sentry.md +0 -0
  274. /package/{assets → dist/pstack}/skills/why/references/sources/slack.md +0 -0
  275. /package/{assets → dist/pstack}/skills/why/references/synthesizer-prompt.md +0 -0
@@ -0,0 +1,523 @@
1
+ import { expect } from "../expect.mjs";
2
+ import "../support.mjs";
3
+ import { parsePrNumber } from "./types.mjs";
4
+ import { WatcherQueryError } from "./github.mjs";
5
+ import { applyQueueSnapshot, assessGitHubMerge, classifyPr, createQueueState, evaluateQueue, planQueue, queryBackoffSeconds, readSnapshot, runQueued, runSimple, selectTierMajorStackDecision } from "./policy.mjs";
6
+ import { failedCheck, fakeReader, passingCheck, pendingCheck } from "./fakes.test-helper.mjs";
7
+ import { describe, it } from "node:test";
8
+ // runtime/skills/poteto-mode/scripts/watch-pr/policy.test.ts
9
+ const context = (number) => ({
10
+ owner: "owner",
11
+ repo: "repo",
12
+ number: parsePrNumber(number)
13
+ });
14
+ const options = {
15
+ interval: 10,
16
+ sweepInterval: 300,
17
+ timeout: 0,
18
+ maxQueryErrors: 5,
19
+ allowDraft: false
20
+ };
21
+ describe("readiness truth table", () => {
22
+ it("covers every specified row and every UNKNOWN rollup value", () => {
23
+ for (const [mergeStateStatus, headRollupState, expected] of [
24
+ [
25
+ "BLOCKED",
26
+ "FAILURE",
27
+ "refused"
28
+ ],
29
+ [
30
+ "BLOCKED",
31
+ "ERROR",
32
+ "refused"
33
+ ],
34
+ [
35
+ "BLOCKED",
36
+ "PENDING",
37
+ "allowed"
38
+ ],
39
+ [
40
+ "UNSTABLE",
41
+ "FAILURE",
42
+ "allowed"
43
+ ],
44
+ [
45
+ "UNKNOWN",
46
+ "ERROR",
47
+ "allowed"
48
+ ],
49
+ [
50
+ "UNKNOWN",
51
+ "EXPECTED",
52
+ "allowed"
53
+ ],
54
+ [
55
+ "UNKNOWN",
56
+ "FAILURE",
57
+ "allowed"
58
+ ],
59
+ [
60
+ "UNKNOWN",
61
+ "PENDING",
62
+ "allowed"
63
+ ],
64
+ [
65
+ "UNKNOWN",
66
+ "SUCCESS",
67
+ "allowed"
68
+ ],
69
+ [
70
+ "UNKNOWN",
71
+ null,
72
+ "allowed"
73
+ ],
74
+ [
75
+ "CLEAN",
76
+ "SUCCESS",
77
+ "allowed"
78
+ ]
79
+ ]) expect(assessGitHubMerge({
80
+ mergeStateStatus,
81
+ headRollupState
82
+ }).kind).toBe(expected);
83
+ });
84
+ it("turns a clean visible list plus GitHub refusal into an explicit CI blocker", async () => {
85
+ const reader = fakeReader({
86
+ facts: { mergeStateStatus: "BLOCKED" },
87
+ fastPath: {
88
+ kind: "checks",
89
+ checks: [passingCheck()]
90
+ },
91
+ commitRollups: [{
92
+ oid: "head",
93
+ state: "FAILURE"
94
+ }]
95
+ });
96
+ const snapshot = await readSnapshot({
97
+ reader,
98
+ context: context(1),
99
+ pendingHistory: "include",
100
+ allowDraft: false
101
+ });
102
+ expect(snapshot.kind).toBe("open");
103
+ if (snapshot.kind !== "open") throw new Error("expected open snapshot");
104
+ expect(snapshot.ci.kind).toBe("ci-github-rejected");
105
+ expect(classifyPr(snapshot)).toMatchObject({
106
+ kind: "blocker",
107
+ blocker: { kind: "failing-checks" }
108
+ });
109
+ });
110
+ });
111
+ describe("snapshot query planning", () => {
112
+ it("does not query commit rollups while queued checks are pending", async () => {
113
+ const reader = fakeReader({ fastPath: {
114
+ kind: "checks",
115
+ checks: [pendingCheck()]
116
+ } });
117
+ const snapshot = await readSnapshot({
118
+ reader,
119
+ context: context(2),
120
+ pendingHistory: "omit",
121
+ allowDraft: false
122
+ });
123
+ expect(snapshot.kind).toBe("open");
124
+ if (snapshot.kind !== "open") throw new Error("expected open snapshot");
125
+ expect(snapshot.ci.kind).toBe("ci-pending");
126
+ expect(reader.calls).toEqual([
127
+ "pullRequest",
128
+ "reviewThreadPage:null",
129
+ "checksFastPath"
130
+ ]);
131
+ });
132
+ it("queries rollups for settled and failed lists", async () => {
133
+ const settled = fakeReader();
134
+ await readSnapshot({
135
+ reader: settled,
136
+ context: context(3),
137
+ pendingHistory: "omit",
138
+ allowDraft: false
139
+ });
140
+ expect(settled.calls).toContain("commitRollups");
141
+ const failed = fakeReader({ fastPath: {
142
+ kind: "checks",
143
+ checks: [failedCheck()]
144
+ } });
145
+ await readSnapshot({
146
+ reader: failed,
147
+ context: context(4),
148
+ pendingHistory: "omit",
149
+ allowDraft: false
150
+ });
151
+ expect(failed.calls).toContain("commitRollups");
152
+ });
153
+ it("short-circuits merged rows before threads and checks", async () => {
154
+ const reader = fakeReader({ facts: {
155
+ state: "MERGED",
156
+ mergedAt: "2026-07-26T00:00:00Z"
157
+ } });
158
+ expect((await readSnapshot({
159
+ reader,
160
+ context: context(5),
161
+ pendingHistory: "include",
162
+ allowDraft: false
163
+ })).kind).toBe("merged");
164
+ expect(reader.calls).toEqual(["pullRequest"]);
165
+ });
166
+ });
167
+ it("scans stacks tier-major so an upstack conflict outranks frontier CI", async () => {
168
+ const frontier = await readSnapshot({
169
+ reader: fakeReader({
170
+ fastPath: {
171
+ kind: "checks",
172
+ checks: [failedCheck()]
173
+ },
174
+ commitRollups: [{
175
+ oid: "head",
176
+ state: "FAILURE"
177
+ }]
178
+ }),
179
+ context: context(10),
180
+ pendingHistory: "omit",
181
+ allowDraft: false
182
+ });
183
+ const upstack = await readSnapshot({
184
+ reader: fakeReader({ facts: { mergeable: "CONFLICTING" } }),
185
+ context: context(11),
186
+ pendingHistory: "omit",
187
+ allowDraft: false
188
+ });
189
+ const decision = selectTierMajorStackDecision([frontier, upstack]);
190
+ expect(decision).toMatchObject({
191
+ kind: "blocker",
192
+ blocker: {
193
+ kind: "merge-conflicts",
194
+ pr: { number: 11 }
195
+ }
196
+ });
197
+ });
198
+ it("attributes a stack wait to the PR whose checks are pending, not the bottom", async () => {
199
+ const readyBottom = await readSnapshot({
200
+ reader: fakeReader(),
201
+ context: context(20),
202
+ pendingHistory: "omit",
203
+ allowDraft: false
204
+ });
205
+ const pendingUpstack = await readSnapshot({
206
+ reader: fakeReader({ fastPath: {
207
+ kind: "checks",
208
+ checks: [pendingCheck("upstack-build")]
209
+ } }),
210
+ context: context(21),
211
+ pendingHistory: "omit",
212
+ allowDraft: false
213
+ });
214
+ const decision = selectTierMajorStackDecision([readyBottom, pendingUpstack]);
215
+ expect(decision).toMatchObject({
216
+ kind: "waiting",
217
+ frontier: { number: 21 },
218
+ pending: [{ name: "upstack-build" }]
219
+ });
220
+ });
221
+ it("waits on a draft while checks are pending, then reports the draft gate", async () => {
222
+ const pending = await readSnapshot({
223
+ reader: fakeReader({
224
+ facts: { isDraft: true },
225
+ fastPath: {
226
+ kind: "checks",
227
+ checks: [pendingCheck()]
228
+ }
229
+ }),
230
+ context: context(12),
231
+ pendingHistory: "omit",
232
+ allowDraft: false
233
+ });
234
+ expect(classifyPr(pending).kind).toBe("waiting");
235
+ const settled = await readSnapshot({
236
+ reader: fakeReader({ facts: { isDraft: true } }),
237
+ context: context(12),
238
+ pendingHistory: "omit",
239
+ allowDraft: false
240
+ });
241
+ expect(classifyPr(settled)).toMatchObject({
242
+ kind: "blocker",
243
+ blocker: {
244
+ kind: "merge-gate",
245
+ reason: "draft-pr"
246
+ }
247
+ });
248
+ });
249
+ describe("queued-stack cadence", () => {
250
+ async function openSnapshot(pr) {
251
+ return readSnapshot({
252
+ reader: fakeReader(),
253
+ context: pr,
254
+ pendingHistory: "omit",
255
+ allowDraft: false
256
+ });
257
+ }
258
+ it("drops a sweep head only after its snapshot succeeds", async () => {
259
+ const queue = [
260
+ context(20),
261
+ context(21),
262
+ context(22)
263
+ ];
264
+ let state = createQueueState(queue, 0);
265
+ const first = await openSnapshot(queue[0]);
266
+ state = applyQueueSnapshot(state, first, 0, options).state;
267
+ expect(state.work).toMatchObject({
268
+ kind: "whole-stack-sweep",
269
+ remaining: [{ number: 21 }, { number: 22 }]
270
+ });
271
+ const second = await openSnapshot(queue[1]);
272
+ state = applyQueueSnapshot(state, second, 60, options).state;
273
+ expect(state.work).toMatchObject({
274
+ kind: "whole-stack-sweep",
275
+ remaining: [{ number: 22 }]
276
+ });
277
+ });
278
+ it("resumes the sweep at the PR whose read failed", async () => {
279
+ const middle = context(21);
280
+ const base = fakeReader();
281
+ let failNext = true;
282
+ const timeline = [];
283
+ const reader = {
284
+ ...base,
285
+ async pullRequest(pr) {
286
+ if (pr.number === middle.number && failNext) {
287
+ failNext = false;
288
+ timeline.push(`fail:${pr.number}`);
289
+ throw new WatcherQueryError({
290
+ kind: "command-exit",
291
+ retryable: true,
292
+ detail: "rate limited",
293
+ code: 1
294
+ });
295
+ }
296
+ timeline.push(`read:${pr.number}`);
297
+ return base.pullRequest(pr);
298
+ }
299
+ };
300
+ let now = 0;
301
+ let sleeps = 0;
302
+ const running = runQueued({
303
+ dependencies: {
304
+ reader,
305
+ clock: {
306
+ now: () => now,
307
+ observedAt: () => "2026-07-26T00:00:00.000Z",
308
+ async sleep(seconds) {
309
+ timeline.push("sleep");
310
+ now += seconds;
311
+ sleeps += 1;
312
+ if (sleeps === 2) throw new Error("stop after resume proof");
313
+ }
314
+ },
315
+ emit(verdict) {
316
+ timeline.push(`emit:${verdict.kind}`);
317
+ }
318
+ },
319
+ contexts: [
320
+ context(20),
321
+ middle,
322
+ context(22)
323
+ ],
324
+ options
325
+ });
326
+ await expect(running).rejects.toThrow("stop after resume proof");
327
+ expect(timeline).toEqual([
328
+ "emit:QUEUE",
329
+ "read:20",
330
+ "fail:21",
331
+ "emit:RETRY",
332
+ "sleep",
333
+ "read:21",
334
+ "read:22",
335
+ "emit:STATUS",
336
+ "emit:WAITING",
337
+ "sleep"
338
+ ]);
339
+ });
340
+ it("emits a completed sweep only after its final successful snapshot", async () => {
341
+ const queue = [context(30), context(31)];
342
+ let state = createQueueState(queue, 0);
343
+ const first = applyQueueSnapshot(state, await openSnapshot(queue[0]), 0, options);
344
+ expect(first.completedSweepRows).toBeNull();
345
+ state = first.state;
346
+ const second = applyQueueSnapshot(state, await openSnapshot(queue[1]), 5, options);
347
+ expect(second.completedSweepRows?.map((row) => Number(row.context.number))).toEqual([30, 31]);
348
+ expect(second.state.nextSweepAt).toBe(305);
349
+ });
350
+ it("ADVANCE continues directly to the new frontier without sleeping", async () => {
351
+ const one = context(40);
352
+ const two = context(41);
353
+ const base = fakeReader();
354
+ const reads = /* @__PURE__ */ new Map();
355
+ const timeline = [];
356
+ const reader = {
357
+ ...base,
358
+ async pullRequest(pr) {
359
+ timeline.push(`read:${pr.number}`);
360
+ const facts = await base.pullRequest(pr);
361
+ const count = (reads.get(pr.number) ?? 0) + 1;
362
+ reads.set(pr.number, count);
363
+ return pr.number === one.number && count > 1 ? {
364
+ ...facts,
365
+ state: "MERGED",
366
+ mergedAt: "2026-07-26T00:00:00Z"
367
+ } : facts;
368
+ }
369
+ };
370
+ let now = 0;
371
+ let sleeps = 0;
372
+ const emitted = [];
373
+ const running = runQueued({
374
+ dependencies: {
375
+ reader,
376
+ clock: {
377
+ now: () => now,
378
+ observedAt: () => "2026-07-26T00:00:00.000Z",
379
+ async sleep(seconds) {
380
+ timeline.push("sleep");
381
+ now += seconds;
382
+ sleeps += 1;
383
+ if (sleeps === 2) throw new Error("stop after advance proof");
384
+ }
385
+ },
386
+ emit(verdict) {
387
+ emitted.push(verdict);
388
+ timeline.push(`emit:${verdict.kind}`);
389
+ }
390
+ },
391
+ contexts: [one, two],
392
+ options
393
+ });
394
+ await expect(running).rejects.toThrow("stop after advance proof");
395
+ expect(emitted.some((event) => event.kind === "ADVANCE")).toBe(true);
396
+ const firstSleep = timeline.indexOf("sleep");
397
+ expect(timeline.slice(firstSleep, firstSleep + 5)).toEqual([
398
+ "sleep",
399
+ "read:40",
400
+ "emit:ADVANCE",
401
+ "read:41",
402
+ "emit:WAITING"
403
+ ]);
404
+ });
405
+ it("deduplicates identical waits and schedules the next due sweep", async () => {
406
+ const queue = [context(50)];
407
+ let state = createQueueState(queue, 0);
408
+ state = applyQueueSnapshot(state, await openSnapshot(queue[0]), 0, options).state;
409
+ const first = evaluateQueue(state, 0, options);
410
+ expect(first.kind).toBe("waiting");
411
+ if (first.kind !== "waiting") throw new Error("expected waiting");
412
+ expect(first.emit).toBe(true);
413
+ const second = evaluateQueue(first.state, 10, options);
414
+ expect(second.kind).toBe("waiting");
415
+ if (second.kind !== "waiting") throw new Error("expected waiting");
416
+ expect(second.emit).toBe(false);
417
+ expect(planQueue(second.state, 300).work?.kind).toBe("whole-stack-sweep");
418
+ });
419
+ });
420
+ it("uses the specified retry floor and cap", () => {
421
+ expect(queryBackoffSeconds(1, 1)).toBe(60);
422
+ expect(queryBackoffSeconds(1, 2)).toBe(120);
423
+ expect(queryBackoffSeconds(60, 4)).toBe(300);
424
+ });
425
+ describe("polling deadline sleeps", () => {
426
+ for (const mode of [
427
+ "single",
428
+ "queued-stack",
429
+ "query-retry"
430
+ ]) it(`caps ${mode} sleeps at the configured deadline`, async () => {
431
+ let now = 0;
432
+ const sleeps = [];
433
+ const pending = fakeReader({ fastPath: {
434
+ kind: "checks",
435
+ checks: [pendingCheck()]
436
+ } });
437
+ const dependencies = {
438
+ reader: mode === "query-retry" ? {
439
+ ...pending,
440
+ async pullRequest() {
441
+ throw new WatcherQueryError({
442
+ kind: "command-exit",
443
+ retryable: true,
444
+ code: 1,
445
+ detail: "temporary failure"
446
+ });
447
+ }
448
+ } : pending,
449
+ clock: {
450
+ now: () => now,
451
+ observedAt: () => "2026-07-26T00:00:00.000Z",
452
+ async sleep(seconds) {
453
+ sleeps.push(seconds);
454
+ now += seconds;
455
+ }
456
+ },
457
+ emit() {}
458
+ };
459
+ const polling = {
460
+ ...options,
461
+ interval: 60,
462
+ timeout: 5
463
+ };
464
+ const result = mode === "queued-stack" ? await runQueued({
465
+ dependencies,
466
+ contexts: [context(1)],
467
+ options: polling
468
+ }) : await runSimple({
469
+ dependencies,
470
+ contexts: [context(1)],
471
+ mode: "single",
472
+ statusOnly: false,
473
+ options: polling
474
+ });
475
+ expect(result.kind).toBe("TIMEOUT");
476
+ expect(sleeps).toEqual([5]);
477
+ expect(now).toBe(5);
478
+ });
479
+ it("preserves the polling interval when the deadline is disabled", async () => {
480
+ let now = 0;
481
+ let reads = 0;
482
+ const sleeps = [];
483
+ const pending = fakeReader({ fastPath: {
484
+ kind: "checks",
485
+ checks: [pendingCheck()]
486
+ } });
487
+ const passed = fakeReader({ fastPath: {
488
+ kind: "checks",
489
+ checks: [passingCheck()]
490
+ } });
491
+ const result = await runSimple({
492
+ dependencies: {
493
+ reader: {
494
+ ...pending,
495
+ async checksFastPath(pr) {
496
+ reads += 1;
497
+ return reads === 1 ? pending.checksFastPath(pr) : passed.checksFastPath(pr);
498
+ }
499
+ },
500
+ clock: {
501
+ now: () => now,
502
+ observedAt: () => "2026-07-26T00:00:00.000Z",
503
+ async sleep(seconds) {
504
+ sleeps.push(seconds);
505
+ now += seconds;
506
+ }
507
+ },
508
+ emit() {}
509
+ },
510
+ contexts: [context(1)],
511
+ mode: "single",
512
+ statusOnly: false,
513
+ options: {
514
+ ...options,
515
+ interval: 60,
516
+ timeout: 0
517
+ }
518
+ });
519
+ expect(result.kind).toBe("READY");
520
+ expect(sleeps).toEqual([60]);
521
+ });
522
+ });
523
+ export {};
@@ -0,0 +1,106 @@
1
+ // runtime/skills/poteto-mode/scripts/watch-pr/render.ts
2
+ const renderJson = (verdict) => `${JSON.stringify(verdict)}\n`;
3
+ function ciCell(row) {
4
+ if (row.kind !== "open") return "—";
5
+ const was = row.ci.hadPreviousPassingCi ? ", was ✅" : "";
6
+ switch (row.ci.kind) {
7
+ case "ci-clean": return "✅";
8
+ case "ci-pending": return `⏳ ${row.ci.pending.length} pending${was}`;
9
+ case "ci-failing": return `❌ ${row.ci.failed.length} failed${row.ci.pending.length ? `, ${row.ci.pending.length} pending` : ""}${was}`;
10
+ case "ci-github-rejected": return `❌ GitHub reports failing checks${was}`;
11
+ default: return row.ci;
12
+ }
13
+ }
14
+ function reviewCell(row) {
15
+ if (row.kind !== "open") return "—";
16
+ const open = row.threads.length;
17
+ return row.reviewAutomationRunning ? open ? `🤖 running, ${open} open` : "🤖 running" : open ? `📝 ${open} open` : "✅";
18
+ }
19
+ function mergeCell(row) {
20
+ if (row.kind === "merged") return "✅ merged";
21
+ if (row.kind === "closed") return "❌ closed";
22
+ if (row.facts.isDraft) return "⏸ draft";
23
+ if (row.facts.reviewDecision === "CHANGES_REQUESTED") return "⚠️ changes requested";
24
+ return row.facts.mergeable === "CONFLICTING" || row.facts.mergeStateStatus === "DIRTY" || row.facts.mergeStateStatus === "CONFLICTING" ? "⚠️ conflict" : "✅";
25
+ }
26
+ function renderStatusTable(rows) {
27
+ const lines = ["| PR | CI | Review | Merge |", "| --- | --- | --- | --- |"];
28
+ for (const row of rows) {
29
+ const url = `https://github.com/${row.context.owner}/${row.context.repo}/pull/${row.context.number}`;
30
+ lines.push(`| [#${row.context.number}](${url}) | ${ciCell(row)} | ${reviewCell(row)} | ${mergeCell(row)} |`);
31
+ }
32
+ return `${lines.join("\n")}\n`;
33
+ }
34
+ function threadLine(thread) {
35
+ const comment = thread.firstComment;
36
+ return [
37
+ thread.id,
38
+ comment?.path ?? "None",
39
+ comment?.line ?? "None",
40
+ comment?.authorLogin ?? "None",
41
+ `isBugBot=${thread.isBugbot}`,
42
+ `bugbotReviewPasses=${thread.bugbotReviewPasses}`,
43
+ (comment?.body ?? "").split(/\r?\n/, 1)[0]?.slice(0, 180) ?? ""
44
+ ].join(" ");
45
+ }
46
+ function renderBlocker(blocker) {
47
+ switch (blocker.kind) {
48
+ case "merge-conflicts": return [
49
+ "BLOCKER: merge-conflicts",
50
+ `pr=${blocker.pr.number}`,
51
+ `mergeable=${blocker.facts.mergeable}`,
52
+ `mergeStateStatus=${blocker.facts.mergeStateStatus}`,
53
+ "action=resolve merge conflicts before waiting for CI"
54
+ ].join("\n");
55
+ case "review-threads": return [
56
+ "BLOCKER: review-threads",
57
+ `pr=${blocker.pr.number}`,
58
+ `unresolved=${blocker.threads.length}`,
59
+ ...blocker.threads.map(threadLine)
60
+ ].join("\n");
61
+ case "failing-checks": {
62
+ const failed = blocker.ci.kind === "ci-failing" ? blocker.ci.failed : [];
63
+ const details = failed.map((check) => `${check.name} ${check.reportedState} ${check.description} ${check.link}`);
64
+ if (blocker.ci.kind === "ci-github-rejected") details.push(`mergeStateStatus=${blocker.ci.github.mergeStateStatus}`, `headRollupState=${blocker.ci.github.headRollupState}`);
65
+ return [
66
+ "BLOCKER: failing-checks",
67
+ `pr=${blocker.pr.number}`,
68
+ `failed=${failed.length}`,
69
+ ...details
70
+ ].join("\n");
71
+ }
72
+ case "merge-gate": {
73
+ const action = blocker.reason === "closed-without-merge" ? "restore or remove the closed PR from the queued stack" : blocker.reason === "draft-pr" ? "mark the PR ready for review before waiting for the merge queue" : "resolve the changes-requested review before waiting for the merge queue";
74
+ return [
75
+ `BLOCKER: ${blocker.reason}`,
76
+ `pr=${blocker.pr.number}`,
77
+ `action=${action}`
78
+ ].join("\n");
79
+ }
80
+ case "status-query": return [
81
+ "BLOCKER: status-query",
82
+ `failures=${blocker.failures}`,
83
+ `detail=${blocker.failure.detail}`,
84
+ "action=verify current PR context, GitHub authentication, and API availability, then rearm"
85
+ ].join("\n");
86
+ default: return blocker;
87
+ }
88
+ }
89
+ function renderPretty(verdict) {
90
+ switch (verdict.kind) {
91
+ case "QUEUE": return `QUEUE: captured ${verdict.queue.length} PR${verdict.queue.length === 1 ? "" : "s"} bottom-to-top: ${verdict.queue.map((pr) => `#${pr.number}`).join(",")}\n`;
92
+ case "STATUS": return renderStatusTable(verdict.rows);
93
+ case "WAITING": return verdict.reason.kind === "pending-checks" ? `WAITING: frontier=#${verdict.frontier.number}; ${verdict.reason.pending.length} check${verdict.reason.pending.length === 1 ? "" : "s"} pending\n` : `WAITING: frontier=#${verdict.frontier.number} is blocker-free; waiting for merge queue (${verdict.reason.unmergedCount} PR${verdict.reason.unmergedCount === 1 ? "" : "s"} unmerged)\n`;
94
+ case "ADVANCE": return `ADVANCE: merged #${verdict.merged.number}; next=#${verdict.frontier.number}; remaining=${verdict.remaining}\n`;
95
+ case "RETRY": return `RETRY: GitHub status query failed; retrying in ${verdict.retryInSeconds}s\ndetail=${verdict.failure.detail}\n`;
96
+ case "BLOCKER": return `${renderBlocker(verdict.blocker)}\n`;
97
+ case "READY": return `READY: no merge conflicts, no unresolved review threads, no failing or pending checks${verdict.scope.kind === "single" && verdict.scope.pr.kind === "ready-pr" ? `\nmergeStateStatus=${verdict.scope.pr.proof.ci.github.mergeStateStatus}\nreviewDecision=${verdict.scope.pr.proof.gate.reviewDecision}\nisDraft=${verdict.scope.pr.proof.gate.draft === "draft-allowed"}${verdict.scope.pr.proof.gate.draft === "draft-allowed" ? "\nnote=draft allowed (--allow-draft); leave draft — do not mark ready" : ""}` : ""}\n`;
98
+ case "COMPLETE": return `COMPLETE: queued stack merged (${verdict.queue.length} PR${verdict.queue.length === 1 ? "" : "s"})\n`;
99
+ case "TIMEOUT":
100
+ if (verdict.reason.kind === "pending-checks") return "TIMEOUT: checks still pending\n";
101
+ if (verdict.reason.kind === "status-unavailable") return "TIMEOUT: GitHub status remained unavailable\n";
102
+ return `TIMEOUT: queued stack still has ${verdict.reason.unmergedCount} PR${verdict.reason.unmergedCount === 1 ? "" : "s"} unmerged; frontier=#${verdict.reason.frontier.number}\n`;
103
+ default: return verdict;
104
+ }
105
+ }
106
+ export { renderJson, renderPretty, renderStatusTable };
@@ -0,0 +1,9 @@
1
+ // runtime/skills/poteto-mode/scripts/watch-pr/types.ts
2
+ function nonEmpty(items) {
3
+ return items.length === 0 ? null : [items[0], ...items.slice(1)];
4
+ }
5
+ function parsePrNumber(value, label = "PR number") {
6
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) throw new Error(`${label} must be a positive integer`);
7
+ return value;
8
+ }
9
+ export { nonEmpty, parsePrNumber };
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ // runtime/skills/poteto-mode/scripts/watch-pr/watch-pr
3
+ const { main } = await import("./cli.mjs");
4
+ process.exitCode = await main(process.argv.slice(2));
5
+ export {};