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,2042 @@
1
+ #!/usr/bin/env node
2
+ import { afterEach, describe, it } from "node:test";
3
+ import { execFileSync, spawn, spawnSync } from "node:child_process";
4
+ import { inspect } from "node:util";
5
+ import { access, chmod, mkdir, mkdtemp, open, readFile, readdir, rename, rm, unlink, writeFile } from "node:fs/promises";
6
+ import { realpathSync } from "node:fs";
7
+ import { tmpdir } from "node:os";
8
+ import { basename, dirname, join, resolve } from "node:path";
9
+ import { randomUUID } from "node:crypto";
10
+ //#region .runtime-sources/skills/poteto-mode/scripts/expect.ts
11
+ /**
12
+ * A small `expect` for `node:test`.
13
+ *
14
+ * The five upstream test files were written against `bun:test`. Their assertions
15
+ * are ordinary value comparisons, so this module supplies exactly the matchers
16
+ * they use rather than a general test framework. It is deliberately not a drop-in
17
+ * replacement: a matcher that is not listed here fails to compile, which keeps the
18
+ * port auditable against the calls that actually exist.
19
+ *
20
+ * Covered: `toBe`, `toEqual`, `toContain`, `toMatch`, `toThrow`, `toBeNull`,
21
+ * `toBeInstanceOf`, `toHaveLength`, `toEndWith`, `toMatchObject`, `.not` on each,
22
+ * and `rejects` / `resolves` for promises.
23
+ *
24
+ * Two differences from `bun:test`, stated so a failure is not misread:
25
+ *
26
+ * - `toEqual` is structural for arrays and plain objects, `Object.is` otherwise.
27
+ * A class instance only equals itself; `toBeInstanceOf` is the matcher for type
28
+ * identity. The ported tests compare plain data, which is why this is enough.
29
+ * - A failure throws `AssertionError` with the two values rendered, not Bun's
30
+ * coloured diff.
31
+ */
32
+ /** Raised by every failing assertion. */
33
+ var AssertionError = class extends Error {
34
+ constructor(message) {
35
+ super(message);
36
+ this.name = "AssertionError";
37
+ }
38
+ };
39
+ function display(value) {
40
+ if (typeof value === "function") return `[Function ${value.name || "anonymous"}]`;
41
+ return inspect(value, {
42
+ depth: 3,
43
+ breakLength: 120,
44
+ maxArrayLength: 20
45
+ });
46
+ }
47
+ function messageOf(error) {
48
+ if (error instanceof Error) return `${error.name}: ${error.message}`;
49
+ return String(error);
50
+ }
51
+ /** Structural equality for the shapes the ported tests compare. */
52
+ function deepEqual(left, right) {
53
+ if (Object.is(left, right)) return true;
54
+ if (left instanceof Date || right instanceof Date) return left instanceof Date && right instanceof Date && left.getTime() === right.getTime();
55
+ if (Array.isArray(left) || Array.isArray(right)) {
56
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
57
+ return left.every((item, index) => deepEqual(item, right[index]));
58
+ }
59
+ if (left === null || right === null || typeof left !== "object" || typeof right !== "object") return false;
60
+ const leftProto = Object.getPrototypeOf(left);
61
+ if (leftProto !== Object.getPrototypeOf(right)) return false;
62
+ if (leftProto !== Object.prototype && leftProto !== null) return false;
63
+ const leftKeys = Object.keys(left);
64
+ const rightKeys = Object.keys(right);
65
+ if (leftKeys.length !== rightKeys.length) return false;
66
+ return leftKeys.every((key) => deepEqual(left[key], right[key]));
67
+ }
68
+ function contains(haystack, needle) {
69
+ if (typeof haystack === "string") return haystack.includes(String(needle));
70
+ if (Array.isArray(haystack)) return haystack.some((item) => deepEqual(item, needle));
71
+ if (haystack instanceof Set) return [...haystack].some((item) => deepEqual(item, needle));
72
+ return false;
73
+ }
74
+ /**
75
+ * Whether `expected` is a partial description of `actual`.
76
+ *
77
+ * `bun:test`'s `toMatchObject` is recursive: every key in `expected` must exist and
78
+ * match, arrays match element by element with the same partial rule, and extra keys
79
+ * in `actual` are allowed. That is what lets an assertion name only the fields it
80
+ * cares about, which several ported cases rely on.
81
+ */
82
+ function matchesObject(actual, expected) {
83
+ if (Array.isArray(expected)) {
84
+ if (!Array.isArray(actual) || actual.length !== expected.length) return false;
85
+ return expected.every((item, index) => matchesObject(actual[index], item));
86
+ }
87
+ if (expected instanceof Date) return deepEqual(actual, expected);
88
+ if (expected === null || typeof expected !== "object") return deepEqual(actual, expected);
89
+ if (actual === null || typeof actual !== "object") return false;
90
+ return Object.entries(expected).every(([key, value]) => matchesObject(actual[key], value));
91
+ }
92
+ function thrownBy(call) {
93
+ try {
94
+ call();
95
+ return { thrown: false };
96
+ } catch (error) {
97
+ return {
98
+ thrown: true,
99
+ error
100
+ };
101
+ }
102
+ }
103
+ function buildMatchers(actual, negate) {
104
+ /**
105
+ * Fail when `ok` is not what the matcher asked for.
106
+ *
107
+ * `negate` inverts the requirement, so `.not.toContain(x)` passes on a haystack
108
+ * that does not contain `x` and fails when it does.
109
+ */
110
+ const check = (ok, matcher, expected) => {
111
+ if (ok === !negate) return;
112
+ const relation = negate ? `not to ${matcher}` : matcher;
113
+ throw new AssertionError(`expected ${display(actual)} ${relation} ${display(expected)}`);
114
+ };
115
+ const checkPlain = (ok, matcher) => {
116
+ if (ok === !negate) return;
117
+ throw new AssertionError(`expected ${display(actual)} ${negate ? `not to ${matcher}` : matcher}`);
118
+ };
119
+ const matchers = {
120
+ toBe(expected) {
121
+ check(Object.is(actual, expected), "toBe", expected);
122
+ },
123
+ toEqual(expected) {
124
+ check(deepEqual(actual, expected), "toEqual", expected);
125
+ },
126
+ toContain(expected) {
127
+ check(contains(actual, expected), "toContain", expected);
128
+ },
129
+ toMatch(expected) {
130
+ const text = String(actual);
131
+ check(typeof expected === "string" ? text.includes(expected) : expected.test(text), "toMatch", expected);
132
+ },
133
+ toThrow(expected) {
134
+ if (typeof actual !== "function") throw new AssertionError(`toThrow needs a function, received ${display(actual)}`);
135
+ const result = thrownBy(actual);
136
+ if (!result.thrown) {
137
+ checkPlain(false, "toThrow");
138
+ return;
139
+ }
140
+ if (expected === void 0) {
141
+ checkPlain(true, "toThrow");
142
+ return;
143
+ }
144
+ const message = messageOf(result.error);
145
+ const ok = typeof expected === "string" ? message.includes(expected) : expected instanceof RegExp ? expected.test(message) : result.error instanceof expected;
146
+ check(ok, "toThrow", expected);
147
+ },
148
+ toBeNull() {
149
+ checkPlain(actual === null, "toBeNull");
150
+ },
151
+ toBeInstanceOf(expected) {
152
+ check(actual instanceof expected, "toBeInstanceOf", expected);
153
+ },
154
+ toHaveLength(expected) {
155
+ const length = actual?.length;
156
+ check(length === expected, "toHaveLength", expected);
157
+ },
158
+ toEndWith(expected) {
159
+ check(String(actual).endsWith(expected), "toEndWith", expected);
160
+ },
161
+ toMatchObject(expected) {
162
+ check(matchesObject(actual, expected), "toMatchObject", expected);
163
+ }
164
+ };
165
+ Object.defineProperties(matchers, {
166
+ not: {
167
+ get: () => buildMatchers(actual, !negate),
168
+ enumerable: true
169
+ },
170
+ rejects: {
171
+ get: () => buildAsyncMatchers(actual, "rejects", negate),
172
+ enumerable: true
173
+ },
174
+ resolves: {
175
+ get: () => buildAsyncMatchers(actual, "resolves", negate),
176
+ enumerable: true
177
+ }
178
+ });
179
+ return matchers;
180
+ }
181
+ /**
182
+ * Compare against a settled promise.
183
+ *
184
+ * `rejects` reads the rejection reason and fails when the promise resolved;
185
+ * `resolves` is the mirror. Each matcher awaits the settlement, which is what
186
+ * makes `await expect(promise).rejects.toThrow(...)` wait before it compares.
187
+ */
188
+ function buildAsyncMatchers(actual, mode, negate) {
189
+ if (typeof actual?.then !== "function") throw new AssertionError(`expected a promise, received ${display(actual)}`);
190
+ const wantRejection = mode === "rejects" !== negate;
191
+ const settle = async () => {
192
+ let resolved;
193
+ let rejected;
194
+ await Promise.resolve(actual).then((value) => {
195
+ resolved = { value };
196
+ }, (error) => {
197
+ rejected = { error };
198
+ });
199
+ if (wantRejection) {
200
+ if (resolved !== void 0) throw new AssertionError(`expected the promise to reject, it resolved to ${display(resolved.value)}`);
201
+ return rejected?.error;
202
+ }
203
+ if (rejected !== void 0) throw new AssertionError(`expected the promise to resolve, it rejected with ${messageOf(rejected.error)}`);
204
+ return resolved?.value;
205
+ };
206
+ /**
207
+ * Run one matcher against the settled value.
208
+ *
209
+ * `toThrow` and `toBeInstanceOf` both read the rejection reason, so a thrown
210
+ * reason is turned back into a thunk for `toThrow` rather than special-cased
211
+ * twice. `negate` is already folded into the settlement mode, so the inner
212
+ * matchers run un-negated.
213
+ */
214
+ const wrap = async (name, expected) => {
215
+ const subject = await settle();
216
+ if (name === "toThrow") {
217
+ buildMatchers(() => {
218
+ throw subject;
219
+ }, false).toThrow(expected);
220
+ return;
221
+ }
222
+ if (name === "toBeNull") {
223
+ buildMatchers(subject, false).toBeNull();
224
+ return;
225
+ }
226
+ const matchers = buildMatchers(subject, false);
227
+ const matcher = matchers[name];
228
+ if (typeof matcher !== "function") throw new AssertionError(`unknown matcher ${name}`);
229
+ matcher.call(matchers, expected);
230
+ };
231
+ const matchers = {
232
+ toBe: (expected) => wrap("toBe", expected),
233
+ toEqual: (expected) => wrap("toEqual", expected),
234
+ toContain: (expected) => wrap("toContain", expected),
235
+ toMatch: (expected) => wrap("toMatch", expected),
236
+ toThrow: (expected) => wrap("toThrow", expected),
237
+ toBeNull: () => wrap("toBeNull"),
238
+ toBeInstanceOf: (expected) => wrap("toBeInstanceOf", expected),
239
+ toHaveLength: (expected) => wrap("toHaveLength", expected),
240
+ toEndWith: (expected) => wrap("toEndWith", expected),
241
+ toMatchObject: (expected) => wrap("toMatchObject", expected)
242
+ };
243
+ Object.defineProperty(matchers, "not", {
244
+ get: () => buildAsyncMatchers(actual, mode, !negate),
245
+ enumerable: true
246
+ });
247
+ return matchers;
248
+ }
249
+ const expect = Object.assign((actual) => buildMatchers(actual, false), {
250
+ not: (actual) => buildMatchers(actual, true),
251
+ rejects: (actual) => buildAsyncMatchers(actual, "rejects", false),
252
+ resolves: (actual) => buildAsyncMatchers(actual, "resolves", false)
253
+ });
254
+ //#endregion
255
+ //#region .runtime-sources/skills/poteto-mode/scripts/support.ts
256
+ /**
257
+ * The one support module the ported upstream tests import.
258
+ *
259
+ * The five upstream test files were written for `bun:test` and use exactly two
260
+ * Bun facilities: its `expect`, and `Bun.spawnSync` / `Bun.spawn` for running git
261
+ * and short-lived processes. Rather than rewrite every call site, this module
262
+ * supplies those two shapes and the test patch only replaces the import line. The
263
+ * bodies below stay byte-for-byte the upstream text, so a future upstream change
264
+ * is still readable against this tree.
265
+ *
266
+ * `Bun.spawnSync` returns `{ exitCode, stdout, stderr, success }` with Buffer
267
+ * output and never throws on a nonzero exit; `node:child_process.spawnSync`
268
+ * returns `{ status, stdout, stderr }`. The adapter is the mapping between them,
269
+ * not a second implementation.
270
+ *
271
+ * `Bun.spawn` returns `{ pid, exited: Promise<number> }`. The adapter runs the
272
+ * child to completion and resolves `exited` with the exit status, which is what
273
+ * the one call site awaits.
274
+ */
275
+ /**
276
+ * Run a command to completion, Bun-shaped.
277
+ *
278
+ * A signal-terminated child reports `exitCode: 1` rather than `null`, so a test
279
+ * that formats the code never prints `undefined` for a killed process.
280
+ */
281
+ function spawnSync$1(argv, options = {}) {
282
+ const [command, ...args] = argv;
283
+ if (command === void 0) throw new TypeError("spawnSync needs a command");
284
+ const result = spawnSync(command, args, {
285
+ cwd: options.cwd,
286
+ env: options.env,
287
+ stdio: [
288
+ options.stdin === "inherit" ? "inherit" : "pipe",
289
+ options.stdout === "inherit" ? "inherit" : "pipe",
290
+ options.stderr === "inherit" ? "inherit" : "pipe"
291
+ ],
292
+ encoding: "buffer"
293
+ });
294
+ const exitCode = result.status ?? 1;
295
+ return {
296
+ exitCode,
297
+ stdout: Buffer.isBuffer(result.stdout) ? result.stdout : Buffer.alloc(0),
298
+ stderr: Buffer.isBuffer(result.stderr) ? result.stderr : Buffer.alloc(0),
299
+ success: exitCode === 0
300
+ };
301
+ }
302
+ /**
303
+ * A child that exits immediately, as a real process.
304
+ *
305
+ * The current interpreter running an empty program is a process like any other: its
306
+ * `pid` is real and its exit status is 0.
307
+ */
308
+ function immediateExit() {
309
+ return spawn(process.execPath, ["-e", ""], { stdio: "ignore" });
310
+ }
311
+ /** Whether `command` starts at all, checked without a shell. */
312
+ function resolves(command) {
313
+ return spawnSync(command, ["--version"], { stdio: "ignore" }).error === void 0;
314
+ }
315
+ /**
316
+ * Start a command and expose Bun's `{ pid, exited }` shape.
317
+ *
318
+ * The one upstream call is `spawn(["true"])`, used to obtain a pid that has already
319
+ * exited so the test can plant a stale lock. It reads `pid` *before* awaiting
320
+ * `exited`, so the pid must be real and already dead when this returns.
321
+ *
322
+ * That rules out "spawn it and fall back on failure": the failure is asynchronous, so
323
+ * the caller would read `-1`. The probe is synchronous instead, which lets this
324
+ * function choose the right child before returning. `true` is used when it exists and
325
+ * the current interpreter otherwise — a `PATH` without the usual Unix utilities is
326
+ * exactly the environment this release has to work in.
327
+ */
328
+ function spawn$1(argv) {
329
+ const [command, ...args] = argv;
330
+ if (command === void 0) throw new TypeError("spawn needs a command");
331
+ const child = resolves(command) ? spawn(command, args, { stdio: "ignore" }) : immediateExit();
332
+ return {
333
+ pid: child.pid ?? -1,
334
+ exited: new Promise((resolve) => {
335
+ child.once("close", (code) => resolve(code ?? 1));
336
+ child.once("error", () => resolve(1));
337
+ })
338
+ };
339
+ }
340
+ /** The `Bun` global the upstream tests reference, limited to what they call. */
341
+ const Bun = {
342
+ spawnSync: spawnSync$1,
343
+ spawn: spawn$1
344
+ };
345
+ //#endregion
346
+ //#region .runtime-sources/skills/poteto-mode/scripts/orch/store.ts
347
+ const UNIT_HEADER = "id track state branch pr sha brief";
348
+ const LEDGER_HEADER = "pr sha verdict evidence verifier ts";
349
+ const LOCK_FILE = ".orch.lock";
350
+ var UserError = class extends Error {};
351
+ var NotFoundError = class extends UserError {
352
+ output;
353
+ constructor(message, output) {
354
+ super(message);
355
+ this.output = output;
356
+ }
357
+ };
358
+ function errorCode(error) {
359
+ if (error !== null && typeof error === "object" && "code" in error && typeof error.code === "string") return error.code;
360
+ return null;
361
+ }
362
+ function errorMessage(error) {
363
+ return error instanceof Error ? error.message : String(error);
364
+ }
365
+ function isRecord(value) {
366
+ return value !== null && typeof value === "object" && !Array.isArray(value);
367
+ }
368
+ function isUnknownArray(value) {
369
+ return Array.isArray(value);
370
+ }
371
+ function verdictOrNull(value) {
372
+ switch (value) {
373
+ case "live-ui-verified":
374
+ case "unit-test-verified":
375
+ case "type-check-only":
376
+ case "verifier-blocked":
377
+ case "verifier-failed": return value;
378
+ default: return null;
379
+ }
380
+ }
381
+ function frontierPrStateOrNull(value) {
382
+ switch (value) {
383
+ case "OPEN":
384
+ case "MERGED":
385
+ case "CLOSED": return value;
386
+ default: return null;
387
+ }
388
+ }
389
+ function parseVerdict(value) {
390
+ const verdict = verdictOrNull(value);
391
+ if (verdict === null) throw new UserError("verdict must be live-ui-verified, unit-test-verified, type-check-only, verifier-blocked, or verifier-failed");
392
+ return verdict;
393
+ }
394
+ function cleanCell(value) {
395
+ const cleaned = value.replace(/[\t\n\r]/g, " ");
396
+ return /^[=+\-@]/.test(cleaned) ? `'${cleaned}` : cleaned;
397
+ }
398
+ function requiredCell(value, label) {
399
+ const cleaned = cleanCell(value);
400
+ if (cleaned.trim().length === 0) throw new UserError(`${label} must not be empty`);
401
+ return cleaned;
402
+ }
403
+ function requiredLine(value, label) {
404
+ const cleaned = value.replace(/[\n\r]/g, " ").trim();
405
+ if (cleaned.length === 0) throw new UserError(`${label} must not be empty`);
406
+ return cleaned;
407
+ }
408
+ function positiveInteger(value, label) {
409
+ if (!Number.isSafeInteger(value) || value < 1) throw new UserError(`${label} must be a positive integer`);
410
+ return value;
411
+ }
412
+ async function exists(path) {
413
+ try {
414
+ await access(path);
415
+ return true;
416
+ } catch (error) {
417
+ if (errorCode(error) === "ENOENT") return false;
418
+ throw error;
419
+ }
420
+ }
421
+ async function atomicWrite(path, contents) {
422
+ const temporary = join(dirname(path), `.${basename(path)}.${process.pid}.${randomUUID()}.tmp`);
423
+ try {
424
+ await writeFile(temporary, contents, { flag: "wx" });
425
+ await rename(temporary, path);
426
+ } finally {
427
+ await rm(temporary, { force: true });
428
+ }
429
+ }
430
+ async function writeIfMissing(path, contents) {
431
+ if (!await exists(path)) await atomicWrite(path, contents);
432
+ }
433
+ async function requiredFile(path) {
434
+ try {
435
+ return await readFile(path, "utf8");
436
+ } catch (error) {
437
+ if (errorCode(error) === "ENOENT") throw new UserError(`store is not initialized at ${dirname(path)}; run orch init`);
438
+ throw error;
439
+ }
440
+ }
441
+ function holderIsDead(holder) {
442
+ const pid = Number.parseInt(holder, 10);
443
+ if (!Number.isSafeInteger(pid) || pid <= 0 || String(pid) !== holder) return false;
444
+ try {
445
+ process.kill(pid, 0);
446
+ return false;
447
+ } catch (error) {
448
+ return errorCode(error) === "ESRCH";
449
+ }
450
+ }
451
+ async function acquireLock(store, options) {
452
+ const path = join(store, LOCK_FILE);
453
+ const pid = String(process.pid);
454
+ const create = async () => {
455
+ const handle = await open(path, "wx");
456
+ await handle.writeFile(`${pid}\n`);
457
+ await handle.close();
458
+ };
459
+ const takeOver = async () => {
460
+ await unlink(path);
461
+ try {
462
+ await create();
463
+ } catch (retryError) {
464
+ if (errorCode(retryError) === "EEXIST") throw new UserError(`store lock held by pid ${(await readFile(path, "utf8")).trim() || "unknown"}`);
465
+ throw retryError;
466
+ }
467
+ };
468
+ try {
469
+ await create();
470
+ } catch (error) {
471
+ if (errorCode(error) !== "EEXIST") throw error;
472
+ let holder = "unknown";
473
+ try {
474
+ holder = (await readFile(path, "utf8")).trim() || "unknown";
475
+ } catch {
476
+ holder = "unknown";
477
+ }
478
+ if (holderIsDead(holder)) {
479
+ options.onStaleLock?.(holder);
480
+ await takeOver();
481
+ } else if (options.force) {
482
+ options.onLockStolen?.(holder);
483
+ await takeOver();
484
+ } else throw new UserError(`store lock held by pid ${holder}`);
485
+ }
486
+ return async () => {
487
+ try {
488
+ if ((await readFile(path, "utf8")).trim() === pid) await unlink(path);
489
+ } catch (error) {
490
+ if (errorCode(error) !== "ENOENT") throw error;
491
+ }
492
+ };
493
+ }
494
+ async function readTsv(path, header, width) {
495
+ const lines = (await requiredFile(path)).replace(/\r/g, "").split("\n");
496
+ if (lines.shift() !== header) throw new UserError(`${basename(path)} has an invalid header`);
497
+ return lines.filter((value) => value.length > 0).map((value) => {
498
+ const cells = value.split(" ");
499
+ if (cells.length !== width) throw new UserError(`${basename(path)} has a malformed row`);
500
+ return cells;
501
+ });
502
+ }
503
+ async function writeTsv(path, header, rows) {
504
+ const body = rows.map((row) => row.map(cleanCell).join(" ")).join("\n");
505
+ await atomicWrite(path, `${header}\n${body}${body.length > 0 ? "\n" : ""}`);
506
+ }
507
+ async function readUnits(store) {
508
+ return (await readTsv(join(store, "units.tsv"), UNIT_HEADER, 7)).map((row) => ({
509
+ id: row[0] ?? "",
510
+ track: row[1] ?? "",
511
+ state: row[2] ?? "",
512
+ branch: row[3] ?? "",
513
+ pr: row[4] ?? "",
514
+ sha: row[5] ?? "",
515
+ brief: row[6] ?? ""
516
+ }));
517
+ }
518
+ function unitCells(unit) {
519
+ return [
520
+ unit.id,
521
+ unit.track,
522
+ unit.state,
523
+ unit.branch,
524
+ unit.pr,
525
+ unit.sha,
526
+ unit.brief
527
+ ];
528
+ }
529
+ async function saveUnits(store, rows) {
530
+ await writeTsv(join(store, "units.tsv"), UNIT_HEADER, rows.map(unitCells));
531
+ }
532
+ async function readLedger(store) {
533
+ return (await readTsv(join(store, "ledger.tsv"), LEDGER_HEADER, 6)).map((row) => {
534
+ const rawVerdict = row[2] ?? "";
535
+ const verdict = verdictOrNull(rawVerdict);
536
+ if (verdict === null) throw new UserError(`ledger.tsv has invalid verdict ${rawVerdict}`);
537
+ return {
538
+ pr: row[0] ?? "",
539
+ sha: row[1] ?? "",
540
+ verdict,
541
+ evidence: row[3] ?? "",
542
+ verifier: row[4] ?? "",
543
+ ts: row[5] ?? ""
544
+ };
545
+ });
546
+ }
547
+ function ledgerCells(row) {
548
+ return [
549
+ row.pr,
550
+ row.sha,
551
+ row.verdict,
552
+ row.evidence,
553
+ row.verifier,
554
+ row.ts
555
+ ];
556
+ }
557
+ async function saveLedger(store, rows) {
558
+ await writeTsv(join(store, "ledger.tsv"), LEDGER_HEADER, rows.map(ledgerCells));
559
+ }
560
+ function pointerCells(pointer) {
561
+ return [
562
+ pointer.ts,
563
+ pointer.agent,
564
+ pointer.unit,
565
+ pointer.status,
566
+ pointer.report
567
+ ];
568
+ }
569
+ async function readPointers(directory) {
570
+ let entries;
571
+ try {
572
+ entries = await readdir(directory, { withFileTypes: true });
573
+ } catch (error) {
574
+ if (errorCode(error) === "ENOENT") throw new UserError(`store is not initialized at ${dirname(directory)}; run orch init`);
575
+ throw error;
576
+ }
577
+ const result = [];
578
+ const files = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".tsv")).sort((left, right) => left.name.localeCompare(right.name));
579
+ for (const entry of files) {
580
+ const raw = (await readFile(join(directory, entry.name), "utf8")).replace(/\r?\n$/, "");
581
+ const row = raw.split(" ");
582
+ if (/[\r\n]/.test(raw) || row.length !== 5) throw new UserError(`inbox pointer ${entry.name} is malformed`);
583
+ result.push({
584
+ ts: row[0] ?? "",
585
+ agent: row[1] ?? "",
586
+ unit: row[2] ?? "",
587
+ status: row[3] ?? "",
588
+ report: row[4] ?? ""
589
+ });
590
+ }
591
+ return result;
592
+ }
593
+ function renderGates(rows) {
594
+ if (rows.length === 0) return "";
595
+ return `# Gates\n\n${rows.map((gate) => {
596
+ const answer = gate.kind === "resolved" ? `\n- Answer: ${gate.answer}` : "";
597
+ return `## ${gate.id}
598
+
599
+ - Status: ${gate.kind}
600
+ - Question: ${gate.question}
601
+ - Options: ${gate.options}
602
+ - Default: ${gate.defaultAnswer}${answer}`;
603
+ }).join("\n\n")}\n`;
604
+ }
605
+ async function readGates(store) {
606
+ const raw = (await requiredFile(join(store, "gates.md"))).replace(/\r/g, "").trim();
607
+ if (raw.length === 0) return [];
608
+ if (!raw.startsWith("# Gates\n\n## ")) throw new UserError("gates.md has an invalid heading");
609
+ const result = [];
610
+ for (const block of raw.slice(12).split("\n\n## ")) {
611
+ const lines = block.split("\n").filter((value) => value.length > 0);
612
+ const id = lines.shift() ?? "";
613
+ const fields = /* @__PURE__ */ new Map();
614
+ for (const value of lines) {
615
+ const match = /^- ([^:]+): (.*)$/.exec(value);
616
+ if (match === null) throw new UserError(`gates.md has a malformed gate ${id}`);
617
+ fields.set(match[1] ?? "", match[2] ?? "");
618
+ }
619
+ const status = fields.get("Status");
620
+ const question = fields.get("Question");
621
+ const options = fields.get("Options");
622
+ const defaultAnswer = fields.get("Default");
623
+ if (id.length === 0 || question === void 0 || options === void 0 || defaultAnswer === void 0) throw new UserError(`gates.md has a malformed gate ${id}`);
624
+ if (status === "open") result.push({
625
+ kind: "open",
626
+ id,
627
+ question,
628
+ options,
629
+ defaultAnswer
630
+ });
631
+ else if (status === "resolved" && fields.has("Answer")) result.push({
632
+ kind: "resolved",
633
+ id,
634
+ question,
635
+ options,
636
+ defaultAnswer,
637
+ answer: fields.get("Answer") ?? ""
638
+ });
639
+ else throw new UserError(`gates.md has invalid status ${status ?? ""}`);
640
+ }
641
+ if (new Set(result.map((gate) => gate.id)).size !== result.length) throw new UserError("gates.md has duplicate gate ids");
642
+ return result;
643
+ }
644
+ function parseFrontier(raw) {
645
+ let value;
646
+ try {
647
+ value = JSON.parse(raw);
648
+ } catch {
649
+ throw new UserError("frontier.json is not valid JSON");
650
+ }
651
+ if (!isRecord(value)) throw new UserError("frontier.json must contain an object");
652
+ if (Object.keys(value).length === 0) return {
653
+ generation: 0,
654
+ prs: [],
655
+ lowestUnmerged: null
656
+ };
657
+ if (typeof value.generation !== "number" || !Number.isSafeInteger(value.generation) || value.generation < 0 || !isUnknownArray(value.prs) || !(value.lowestUnmerged === null || typeof value.lowestUnmerged === "number" && Number.isSafeInteger(value.lowestUnmerged))) throw new UserError("frontier.json has an invalid shape");
658
+ const prs = [];
659
+ for (const row of value.prs) {
660
+ const state = isRecord(row) ? frontierPrStateOrNull(row.state) : null;
661
+ if (!isRecord(row) || typeof row.pr !== "number" || !Number.isSafeInteger(row.pr) || row.pr < 1 || typeof row.branches !== "string" || row.branches.length === 0 || typeof row.sha !== "string" || state === null) throw new UserError("frontier.json has an invalid PR row");
662
+ prs.push({
663
+ pr: row.pr,
664
+ branches: row.branches,
665
+ sha: row.sha,
666
+ state
667
+ });
668
+ }
669
+ return {
670
+ generation: value.generation,
671
+ prs,
672
+ lowestUnmerged: value.lowestUnmerged
673
+ };
674
+ }
675
+ async function readFrontier(store) {
676
+ return parseFrontier(await requiredFile(join(store, "frontier.json")));
677
+ }
678
+ async function readStanding(store) {
679
+ const raw = (await requiredFile(join(store, "preferences.md"))).replace(/\r/g, "");
680
+ if (raw.trim().length === 0) return [];
681
+ const result = [];
682
+ for (const value of raw.split("\n").filter((item) => item.length > 0)) {
683
+ const match = /^([1-9]\d*)\. (.+)$/.exec(value);
684
+ const number = Number(match?.[1] ?? 0);
685
+ if (match === null || number !== result.length + 1) throw new UserError("preferences.md has malformed numbering");
686
+ result.push({
687
+ number,
688
+ line: match[2] ?? ""
689
+ });
690
+ }
691
+ return result;
692
+ }
693
+ function countValues(values) {
694
+ const result = {};
695
+ for (const value of values) result[value] = (result[value] ?? 0) + 1;
696
+ return Object.fromEntries(Object.entries(result).sort(([left], [right]) => left.localeCompare(right)));
697
+ }
698
+ function summarize(unitRows, ledgerRows, currentFrontier, gateRows) {
699
+ return {
700
+ unitStates: countValues(unitRows.map((unit) => unit.state)),
701
+ ledgerVerdicts: countValues(ledgerRows.map((row) => row.verdict)),
702
+ frontierGeneration: currentFrontier.generation,
703
+ openGateIds: gateRows.filter((gate) => gate.kind === "open").map((gate) => gate.id).sort()
704
+ };
705
+ }
706
+ function countRecord(value) {
707
+ if (!isRecord(value)) return null;
708
+ const result = {};
709
+ for (const [name, count] of Object.entries(value)) {
710
+ if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0) return null;
711
+ result[name] = count;
712
+ }
713
+ return result;
714
+ }
715
+ function previousSummary(raw) {
716
+ const match = /<!-- orch-summary (.+) -->/.exec(raw);
717
+ if (match === null) return null;
718
+ let value;
719
+ try {
720
+ value = JSON.parse(match[1] ?? "");
721
+ } catch {
722
+ return null;
723
+ }
724
+ if (!isRecord(value) || typeof value.frontierGeneration !== "number" || !isUnknownArray(value.openGateIds)) return null;
725
+ const unitStates = countRecord(value.unitStates);
726
+ const ledgerVerdicts = countRecord(value.ledgerVerdicts);
727
+ const openGateIds = value.openGateIds.filter((item) => typeof item === "string");
728
+ if (unitStates === null || ledgerVerdicts === null || openGateIds.length !== value.openGateIds.length) return null;
729
+ return {
730
+ unitStates,
731
+ ledgerVerdicts,
732
+ frontierGeneration: value.frontierGeneration,
733
+ openGateIds
734
+ };
735
+ }
736
+ function changed(before, after) {
737
+ if (before === null) return "first render";
738
+ const result = [];
739
+ const groups = [{
740
+ label: "units",
741
+ oldCounts: before.unitStates,
742
+ newCounts: after.unitStates
743
+ }, {
744
+ label: "ledger",
745
+ oldCounts: before.ledgerVerdicts,
746
+ newCounts: after.ledgerVerdicts
747
+ }];
748
+ for (const { label, oldCounts, newCounts } of groups) {
749
+ const names = [.../* @__PURE__ */ new Set([...Object.keys(oldCounts), ...Object.keys(newCounts)])].sort();
750
+ for (const name of names) {
751
+ const oldCount = oldCounts[name] ?? 0;
752
+ const newCount = newCounts[name] ?? 0;
753
+ if (oldCount !== newCount) result.push(`${label} ${name} ${oldCount}->${newCount}`);
754
+ }
755
+ }
756
+ if (before.frontierGeneration !== after.frontierGeneration) result.push(`frontier generation ${before.frontierGeneration}->${after.frontierGeneration}`);
757
+ if (before.openGateIds.join("\0") !== after.openGateIds.join("\0")) result.push(`open gates ${before.openGateIds.length}->${after.openGateIds.length}`);
758
+ return result.length === 0 ? "no derived changes" : result.join("; ");
759
+ }
760
+ function markdown(value) {
761
+ return value.replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
762
+ }
763
+ function table(headers, rows) {
764
+ if (rows.length === 0) return "(none)";
765
+ return [
766
+ `| ${headers.join(" | ")} |`,
767
+ `| ${headers.map(() => "---").join(" | ")} |`,
768
+ ...rows.map((row) => `| ${row.map(markdown).join(" | ")} |`)
769
+ ].join("\n");
770
+ }
771
+ function statusMarkdown(unitRows, ledgerRows, currentFrontier, gateRows, currentSummary) {
772
+ return `# Orchestrate status
773
+
774
+ Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
775
+
776
+ ## Units
777
+
778
+ States: ${countLine(currentSummary.unitStates)}
779
+
780
+ ${table([
781
+ "ID",
782
+ "Track",
783
+ "State",
784
+ "Branch",
785
+ "PR",
786
+ "SHA",
787
+ "Brief"
788
+ ], unitRows.map(unitCells))}
789
+
790
+ ## Verification ledger
791
+
792
+ Verdicts: ${countLine(currentSummary.ledgerVerdicts)}
793
+
794
+ ${table([
795
+ "PR",
796
+ "SHA",
797
+ "Verdict",
798
+ "Evidence",
799
+ "Verifier",
800
+ "Timestamp"
801
+ ], ledgerRows.map(ledgerCells))}
802
+
803
+ ## Frontier
804
+
805
+ Generation: ${currentFrontier.generation}
806
+ Lowest unmerged: ${currentFrontier.lowestUnmerged ?? "none"}
807
+
808
+ ${table([
809
+ "Branch",
810
+ "PR",
811
+ "SHA",
812
+ "State"
813
+ ], currentFrontier.prs.map((row) => [
814
+ row.branches,
815
+ String(row.pr),
816
+ row.sha,
817
+ row.state
818
+ ]))}
819
+
820
+ ## Gates
821
+
822
+ ${table([
823
+ "ID",
824
+ "Status",
825
+ "Question",
826
+ "Options",
827
+ "Default",
828
+ "Answer"
829
+ ], gateRows.map((gate) => [
830
+ gate.id,
831
+ gate.kind,
832
+ gate.question,
833
+ gate.options,
834
+ gate.defaultAnswer,
835
+ gate.kind === "resolved" ? gate.answer : ""
836
+ ]))}
837
+
838
+ <!-- orch-summary ${JSON.stringify(currentSummary)} -->
839
+ `;
840
+ }
841
+ function countLine(value) {
842
+ const entries = Object.entries(value);
843
+ return entries.length === 0 ? "none" : entries.map(([name, count]) => `${name}=${count}`).join(", ");
844
+ }
845
+ const FORGE_PULL_REQUEST_QUERIES = {
846
+ gh: {
847
+ stateField: "state",
848
+ headField: "headRefName",
849
+ baseField: "baseRefName",
850
+ states: /* @__PURE__ */ new Map([
851
+ ["OPEN", "OPEN"],
852
+ ["MERGED", "MERGED"],
853
+ ["CLOSED", "CLOSED"]
854
+ ])
855
+ },
856
+ origin: {
857
+ stateField: "status",
858
+ headField: "headRef",
859
+ baseField: "baseRef",
860
+ states: /* @__PURE__ */ new Map([
861
+ ["open", "OPEN"],
862
+ ["draft", "OPEN"],
863
+ ["merged", "MERGED"],
864
+ ["closed", "CLOSED"]
865
+ ])
866
+ }
867
+ };
868
+ function parseForge(value) {
869
+ if (value === "gh" || value === "origin") return value;
870
+ throw new UserError("forge must be gh or origin");
871
+ }
872
+ function runTool({ command, args, repo }) {
873
+ try {
874
+ return execFileSync(command, args, {
875
+ cwd: repo,
876
+ encoding: "utf8",
877
+ env: process.env,
878
+ stdio: [
879
+ "ignore",
880
+ "pipe",
881
+ "pipe"
882
+ ]
883
+ });
884
+ } catch (error) {
885
+ throw new UserError(`${command} ${args.join(" ")} failed: ${errorMessage(error)}`);
886
+ }
887
+ }
888
+ function listPullRequests({ filter, branch, forge, repo }) {
889
+ const query = FORGE_PULL_REQUEST_QUERIES[forge];
890
+ const command = `${forge} pr list ${filter} ${branch}`;
891
+ const raw = runTool({
892
+ command: forge,
893
+ args: [
894
+ "pr",
895
+ "list",
896
+ filter,
897
+ branch,
898
+ "--state",
899
+ "all",
900
+ "--json",
901
+ [
902
+ "number",
903
+ query.stateField,
904
+ query.headField,
905
+ query.baseField
906
+ ].join(",")
907
+ ],
908
+ repo
909
+ });
910
+ let value;
911
+ try {
912
+ value = JSON.parse(raw);
913
+ } catch {
914
+ throw new UserError(`${command} output is not valid JSON`);
915
+ }
916
+ if (!isUnknownArray(value)) throw new UserError(`${command} output is not a JSON array`);
917
+ return value.map((row) => {
918
+ const status = isRecord(row) ? row[query.stateField] : void 0;
919
+ const state = typeof status === "string" ? query.states.get(status) ?? null : null;
920
+ const head = isRecord(row) ? row[query.headField] : void 0;
921
+ const base = isRecord(row) ? row[query.baseField] : void 0;
922
+ if (!isRecord(row) || typeof row.number !== "number" || !Number.isSafeInteger(row.number) || row.number < 1 || state === null || typeof head !== "string" || head.length === 0 || typeof base !== "string" || base.length === 0) throw new UserError(`${command} output has an invalid PR row`);
923
+ return {
924
+ pr: row.number,
925
+ state,
926
+ branch: head,
927
+ base
928
+ };
929
+ });
930
+ }
931
+ function branchPullRequest({ branch, forge, repo }) {
932
+ const rows = listPullRequests({
933
+ filter: "--head",
934
+ branch,
935
+ forge,
936
+ repo
937
+ });
938
+ const row = rows[0];
939
+ if (row === void 0) throw new UserError(`branch ${branch} has no pull request; open one with ${forge} pr create, or resolve the frontier from a branch whose chain is fully submitted`);
940
+ if (rows.length > 1) throw new UserError(`branch ${branch} has multiple pull requests (${rows.map((item) => `#${item.pr}`).join(", ")}); close the stale ones first`);
941
+ return row;
942
+ }
943
+ function openChildren({ branch, forge, repo }) {
944
+ return listPullRequests({
945
+ filter: "--base",
946
+ branch,
947
+ forge,
948
+ repo
949
+ }).filter((row) => row.state === "OPEN");
950
+ }
951
+ function runGit({ args, repo }) {
952
+ return runTool({
953
+ command: "git",
954
+ args,
955
+ repo
956
+ }).trim();
957
+ }
958
+ function branchSha({ branch, repo }) {
959
+ const sha = runGit({
960
+ args: ["rev-parse", branch],
961
+ repo
962
+ });
963
+ if (!/^[0-9a-f]{40,64}$/i.test(sha)) throw new UserError(`git rev-parse ${branch} returned an invalid SHA`);
964
+ return sha;
965
+ }
966
+ function currentBranch(repo) {
967
+ return runGit({
968
+ args: [
969
+ "symbolic-ref",
970
+ "--short",
971
+ "HEAD"
972
+ ],
973
+ repo
974
+ });
975
+ }
976
+ function trunkBranch(repo) {
977
+ try {
978
+ return runGit({
979
+ args: [
980
+ "symbolic-ref",
981
+ "--short",
982
+ "refs/remotes/origin/HEAD"
983
+ ],
984
+ repo
985
+ }).replace(/^origin\//, "");
986
+ } catch {
987
+ return "main";
988
+ }
989
+ }
990
+ function resolveFrontier(repo, forge) {
991
+ const trunk = trunkBranch(repo);
992
+ const current = currentBranch(repo);
993
+ if (current === trunk) return [];
994
+ const seen = /* @__PURE__ */ new Set([current]);
995
+ let bottom = branchPullRequest({
996
+ branch: current,
997
+ forge,
998
+ repo
999
+ });
1000
+ const chain = [bottom];
1001
+ while (bottom.base !== trunk) {
1002
+ if (seen.has(bottom.base)) throw new UserError(`pull request base chain loops at branch ${bottom.base}`);
1003
+ seen.add(bottom.base);
1004
+ bottom = branchPullRequest({
1005
+ branch: bottom.base,
1006
+ forge,
1007
+ repo
1008
+ });
1009
+ chain.unshift(bottom);
1010
+ }
1011
+ let tip = current;
1012
+ for (;;) {
1013
+ const children = openChildren({
1014
+ branch: tip,
1015
+ forge,
1016
+ repo
1017
+ });
1018
+ const child = children[0];
1019
+ if (child === void 0) break;
1020
+ if (children.length > 1) throw new UserError(`stack is not linear: branches ${children.map((row) => row.branch).join(", ")} all target ${tip}`);
1021
+ if (seen.has(child.branch)) throw new UserError(`pull request base chain loops at branch ${child.branch}`);
1022
+ seen.add(child.branch);
1023
+ chain.push(child);
1024
+ tip = child.branch;
1025
+ }
1026
+ if (new Set(chain.map((row) => row.pr)).size !== chain.length) throw new UserError("pull request base chain contains duplicate pull requests");
1027
+ return chain.map((row) => ({
1028
+ pr: row.pr,
1029
+ branches: row.branch,
1030
+ sha: branchSha({
1031
+ branch: row.branch,
1032
+ repo
1033
+ }),
1034
+ state: row.state
1035
+ }));
1036
+ }
1037
+ function validateFrontierPin({ actual, expected }) {
1038
+ if (actual.length === expected.length && actual.every((pr, index) => pr === expected[index])) return;
1039
+ const actualSet = new Set(actual);
1040
+ const expectedSet = new Set(expected);
1041
+ const missing = expected.filter((pr) => !actualSet.has(pr));
1042
+ const extra = actual.filter((pr) => !expectedSet.has(pr));
1043
+ const drift = [];
1044
+ if (missing.length > 0) drift.push(`missing from resolved chain: ${missing.join(",")}`);
1045
+ if (extra.length > 0) drift.push(`extra in resolved chain: ${extra.join(",")}`);
1046
+ if (missing.length === 0 && extra.length === 0) drift.push(`order differs: expected ${expected.join(",")}; resolved ${actual.join(",")}`);
1047
+ throw new UserError(`frontier pin mismatch: ${drift.join("; ")}`);
1048
+ }
1049
+ function openStore(directory, options = {}) {
1050
+ const store = resolve(directory);
1051
+ let closed = false;
1052
+ let releaseLock = null;
1053
+ let lockRequest = null;
1054
+ const ensureOpen = () => {
1055
+ if (closed) throw new UserError("store is closed");
1056
+ };
1057
+ const ensureLock = async () => {
1058
+ ensureOpen();
1059
+ if (releaseLock !== null) return;
1060
+ if (lockRequest === null) lockRequest = acquireLock(store, options).then((release) => {
1061
+ releaseLock = release;
1062
+ });
1063
+ try {
1064
+ await lockRequest;
1065
+ } catch (error) {
1066
+ lockRequest = null;
1067
+ throw error;
1068
+ }
1069
+ };
1070
+ const beginWrite = async () => {
1071
+ ensureOpen();
1072
+ if (!await exists(store)) throw new UserError(`store is not initialized at ${store}; run orch init`);
1073
+ await ensureLock();
1074
+ };
1075
+ return {
1076
+ units: {
1077
+ add: async (params) => {
1078
+ await beginWrite();
1079
+ const row = {
1080
+ id: requiredCell(params.id, "unit id"),
1081
+ track: requiredCell(params.track, "track"),
1082
+ state: "pending",
1083
+ branch: "",
1084
+ pr: "",
1085
+ sha: "",
1086
+ brief: params.brief === void 0 ? "" : requiredCell(params.brief, "brief")
1087
+ };
1088
+ const rows = [...await readUnits(store)];
1089
+ if (rows.some((unit) => unit.id === row.id)) throw new UserError(`unit ${row.id} already exists`);
1090
+ rows.push(row);
1091
+ await saveUnits(store, rows);
1092
+ return row;
1093
+ },
1094
+ set: async (params) => {
1095
+ await beginWrite();
1096
+ const id = requiredCell(params.id, "unit id");
1097
+ const state = requiredCell(params.state, "state");
1098
+ const rows = [...await readUnits(store)];
1099
+ const index = rows.findIndex((unit) => unit.id === id);
1100
+ const old = rows[index];
1101
+ if (index < 0 || old === void 0) throw new NotFoundError(`unit ${id} not found`);
1102
+ const row = {
1103
+ ...old,
1104
+ state,
1105
+ branch: params.branch === void 0 ? old.branch : requiredCell(params.branch, "branch"),
1106
+ pr: params.pr === void 0 ? old.pr : String(positiveInteger(params.pr, "PR")),
1107
+ sha: params.sha === void 0 ? old.sha : requiredCell(params.sha, "SHA")
1108
+ };
1109
+ rows[index] = row;
1110
+ await saveUnits(store, rows);
1111
+ return row;
1112
+ },
1113
+ get: async (id) => {
1114
+ ensureOpen();
1115
+ const cleanId = requiredCell(id, "unit id");
1116
+ const row = (await readUnits(store)).find((unit) => unit.id === cleanId);
1117
+ if (row === void 0) throw new NotFoundError(`unit ${cleanId} not found`);
1118
+ return row;
1119
+ },
1120
+ list: async (params = {}) => {
1121
+ ensureOpen();
1122
+ const state = params.state === void 0 ? void 0 : requiredCell(params.state, "state");
1123
+ const track = params.track === void 0 ? void 0 : requiredCell(params.track, "track");
1124
+ return (await readUnits(store)).filter((unit) => (state === void 0 || unit.state === state) && (track === void 0 || unit.track === track));
1125
+ },
1126
+ counts: async () => {
1127
+ ensureOpen();
1128
+ return countValues((await readUnits(store)).map((unit) => unit.state));
1129
+ }
1130
+ },
1131
+ ledger: {
1132
+ record: async (params) => {
1133
+ await beginWrite();
1134
+ const verdict = parseVerdict(params.verdict);
1135
+ const row = {
1136
+ pr: String(positiveInteger(params.pr, "PR")),
1137
+ sha: requiredCell(params.sha, "SHA"),
1138
+ verdict,
1139
+ evidence: requiredCell(params.evidence, "evidence"),
1140
+ verifier: params.verifier === void 0 ? "" : requiredCell(params.verifier, "verifier"),
1141
+ ts: (/* @__PURE__ */ new Date()).toISOString()
1142
+ };
1143
+ const rows = [...await readLedger(store)];
1144
+ const index = rows.findIndex((old) => old.pr === row.pr && old.sha === row.sha);
1145
+ if (index < 0) rows.push(row);
1146
+ else rows[index] = row;
1147
+ await saveLedger(store, rows);
1148
+ return row;
1149
+ },
1150
+ check: async (params) => {
1151
+ ensureOpen();
1152
+ const pr = String(positiveInteger(params.pr, "PR"));
1153
+ const sha = requiredCell(params.sha, "SHA");
1154
+ const row = (await readLedger(store)).find((value) => value.pr === pr && value.sha === sha);
1155
+ if (row === void 0) throw new NotFoundError("NOT-VERIFIED", {
1156
+ compact: "NOT-VERIFIED",
1157
+ json: {
1158
+ pr,
1159
+ sha,
1160
+ verdict: "NOT-VERIFIED"
1161
+ }
1162
+ });
1163
+ return row;
1164
+ },
1165
+ summary: async () => {
1166
+ ensureOpen();
1167
+ return countValues((await readLedger(store)).map((row) => row.verdict));
1168
+ }
1169
+ },
1170
+ inbox: {
1171
+ push: async (params) => {
1172
+ await beginWrite();
1173
+ const pointer = {
1174
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1175
+ agent: requiredCell(params.agent, "agent"),
1176
+ unit: requiredCell(params.unit, "unit"),
1177
+ status: requiredCell(params.status, "status"),
1178
+ report: params.report === void 0 ? "" : requiredCell(params.report, "report")
1179
+ };
1180
+ const inbox = join(store, "inbox");
1181
+ if (!await exists(inbox)) throw new UserError(`store is not initialized at ${store}; run orch init`);
1182
+ const filename = `${pointer.ts.replace(/[:.]/g, "-")}-${process.pid}-${randomUUID()}.tsv`;
1183
+ const contents = `${pointerCells(pointer).map(cleanCell).join(" ")}\n`;
1184
+ await atomicWrite(join(inbox, filename), contents);
1185
+ return {
1186
+ pointer,
1187
+ filename
1188
+ };
1189
+ },
1190
+ drain: async () => {
1191
+ await beginWrite();
1192
+ const inbox = join(store, "inbox");
1193
+ const rows = await readPointers(inbox);
1194
+ const drained = join(store, `.inbox-drain-${process.pid}-${randomUUID()}`);
1195
+ await rename(inbox, drained);
1196
+ try {
1197
+ await mkdir(inbox);
1198
+ } catch (error) {
1199
+ await rename(drained, inbox);
1200
+ throw error;
1201
+ }
1202
+ await rm(drained, {
1203
+ recursive: true,
1204
+ force: true
1205
+ });
1206
+ return rows;
1207
+ },
1208
+ peek: async () => {
1209
+ ensureOpen();
1210
+ return readPointers(join(store, "inbox"));
1211
+ },
1212
+ count: async () => {
1213
+ ensureOpen();
1214
+ return (await readPointers(join(store, "inbox"))).length;
1215
+ }
1216
+ },
1217
+ gates: {
1218
+ park: async (params) => {
1219
+ await beginWrite();
1220
+ const gate = {
1221
+ kind: "open",
1222
+ id: requiredLine(params.id, "gate id"),
1223
+ question: requiredLine(params.question, "question"),
1224
+ options: requiredLine(params.options, "options"),
1225
+ defaultAnswer: requiredLine(params.defaultAnswer, "default")
1226
+ };
1227
+ const rows = [...await readGates(store)];
1228
+ const index = rows.findIndex((old) => old.id === gate.id);
1229
+ if (index < 0) rows.push(gate);
1230
+ else rows[index] = gate;
1231
+ await atomicWrite(join(store, "gates.md"), renderGates(rows));
1232
+ return gate;
1233
+ },
1234
+ list: async () => {
1235
+ ensureOpen();
1236
+ return (await readGates(store)).filter((gate) => gate.kind === "open");
1237
+ },
1238
+ resolve: async (params) => {
1239
+ await beginWrite();
1240
+ const id = requiredLine(params.id, "gate id");
1241
+ const rows = [...await readGates(store)];
1242
+ const index = rows.findIndex((gate) => gate.id === id);
1243
+ const old = rows[index];
1244
+ if (index < 0 || old === void 0) throw new NotFoundError(`gate ${id} not found`);
1245
+ const gate = {
1246
+ kind: "resolved",
1247
+ id: old.id,
1248
+ question: old.question,
1249
+ options: old.options,
1250
+ defaultAnswer: old.defaultAnswer,
1251
+ answer: requiredLine(params.answer, "answer")
1252
+ };
1253
+ rows[index] = gate;
1254
+ await atomicWrite(join(store, "gates.md"), renderGates(rows));
1255
+ return gate;
1256
+ }
1257
+ },
1258
+ frontier: {
1259
+ set: async (params) => {
1260
+ await beginWrite();
1261
+ const repo = resolve(requiredLine(params.repo, "repo directory"));
1262
+ const forge = parseForge(params.forge ?? "gh");
1263
+ const pin = params.prs === void 0 ? void 0 : params.prs.map((pr) => positiveInteger(pr, "PR"));
1264
+ if (pin !== void 0 && new Set(pin).size !== pin.length) throw new UserError("--prs must not contain duplicates");
1265
+ const old = await readFrontier(store);
1266
+ const prs = resolveFrontier(repo, forge);
1267
+ if (pin !== void 0) validateFrontierPin({
1268
+ actual: prs.map((row) => row.pr),
1269
+ expected: pin
1270
+ });
1271
+ const value = {
1272
+ generation: old.generation + 1,
1273
+ prs,
1274
+ lowestUnmerged: prs.find((row) => row.state === "OPEN")?.pr ?? null
1275
+ };
1276
+ await atomicWrite(join(store, "frontier.json"), `${JSON.stringify(value, null, 2)}\n`);
1277
+ return value;
1278
+ },
1279
+ show: async () => {
1280
+ ensureOpen();
1281
+ return readFrontier(store);
1282
+ }
1283
+ },
1284
+ standing: {
1285
+ show: async () => {
1286
+ ensureOpen();
1287
+ return readStanding(store);
1288
+ },
1289
+ add: async (params) => {
1290
+ await beginWrite();
1291
+ const rows = [...await readStanding(store)];
1292
+ const item = {
1293
+ number: rows.length + 1,
1294
+ line: requiredLine(params.line, "standing order")
1295
+ };
1296
+ rows.push(item);
1297
+ await atomicWrite(join(store, "preferences.md"), `${rows.map((row) => `${row.number}. ${row.line}`).join("\n")}\n`);
1298
+ return item;
1299
+ }
1300
+ },
1301
+ status: { render: async () => {
1302
+ await beginWrite();
1303
+ const unitRows = await readUnits(store);
1304
+ const ledgerRows = await readLedger(store);
1305
+ const currentFrontier = await readFrontier(store);
1306
+ const gateRows = await readGates(store);
1307
+ const currentSummary = summarize(unitRows, ledgerRows, currentFrontier, gateRows);
1308
+ const path = join(store, "status.md");
1309
+ const change = changed(await exists(path) ? previousSummary(await readFile(path, "utf8")) : null, currentSummary);
1310
+ await atomicWrite(path, statusMarkdown(unitRows, ledgerRows, currentFrontier, gateRows, currentSummary));
1311
+ return {
1312
+ units: unitRows,
1313
+ ledger: ledgerRows,
1314
+ frontier: currentFrontier,
1315
+ gates: gateRows,
1316
+ summary: currentSummary,
1317
+ changed: change
1318
+ };
1319
+ } },
1320
+ init: async () => {
1321
+ ensureOpen();
1322
+ await mkdir(store, { recursive: true });
1323
+ await ensureLock();
1324
+ await writeIfMissing(join(store, "units.tsv"), `${UNIT_HEADER}\n`);
1325
+ await writeIfMissing(join(store, "ledger.tsv"), `${LEDGER_HEADER}\n`);
1326
+ await mkdir(join(store, "inbox"), { recursive: true });
1327
+ await writeIfMissing(join(store, "gates.md"), "");
1328
+ await writeIfMissing(join(store, "preferences.md"), "");
1329
+ await writeIfMissing(join(store, "frontier.json"), "{}\n");
1330
+ return { store };
1331
+ },
1332
+ close: async () => {
1333
+ if (closed) return;
1334
+ if (lockRequest !== null) try {
1335
+ await lockRequest;
1336
+ } catch {}
1337
+ const release = releaseLock;
1338
+ releaseLock = null;
1339
+ closed = true;
1340
+ if (release !== null) await release();
1341
+ }
1342
+ };
1343
+ }
1344
+ //#endregion
1345
+ //#region .runtime-sources/skills/poteto-mode/scripts/orch/orch.test.ts
1346
+ const SCRIPT = join(import.meta.dirname, "orch.mjs");
1347
+ const directories = [];
1348
+ const handles = [];
1349
+ async function makeDirectory() {
1350
+ const directory = await mkdtemp(join(tmpdir(), "orch-test-"));
1351
+ directories.push(directory);
1352
+ return directory;
1353
+ }
1354
+ function useStore(directory, options) {
1355
+ const store = openStore(directory, options);
1356
+ handles.push(store);
1357
+ return store;
1358
+ }
1359
+ async function initializedStore() {
1360
+ const directory = await makeDirectory();
1361
+ const store = useStore(directory);
1362
+ await store.init();
1363
+ return {
1364
+ directory,
1365
+ store
1366
+ };
1367
+ }
1368
+ function git({ args, repo }) {
1369
+ const result = Bun.spawnSync([
1370
+ "git",
1371
+ "-C",
1372
+ repo,
1373
+ ...args
1374
+ ]);
1375
+ if (result.exitCode !== 0) throw new Error(`git ${args.join(" ")} failed: ${result.stderr.toString()}`);
1376
+ return result.stdout.toString().trim();
1377
+ }
1378
+ async function makeGitStack(directory) {
1379
+ const repo = join(directory, "repo");
1380
+ await mkdir(repo);
1381
+ git({
1382
+ repo,
1383
+ args: ["init", "--initial-branch=main"]
1384
+ });
1385
+ git({
1386
+ repo,
1387
+ args: [
1388
+ "config",
1389
+ "user.name",
1390
+ "Orch Test"
1391
+ ]
1392
+ });
1393
+ git({
1394
+ repo,
1395
+ args: [
1396
+ "config",
1397
+ "user.email",
1398
+ "orch@example.com"
1399
+ ]
1400
+ });
1401
+ await writeFile(join(repo, "main.txt"), "main\n");
1402
+ git({
1403
+ repo,
1404
+ args: ["add", "."]
1405
+ });
1406
+ git({
1407
+ repo,
1408
+ args: [
1409
+ "commit",
1410
+ "-m",
1411
+ "main"
1412
+ ]
1413
+ });
1414
+ for (const [index, branch] of [
1415
+ "stack/merged",
1416
+ "stack/closed",
1417
+ "stack/open",
1418
+ "stack/draft"
1419
+ ].entries()) {
1420
+ git({
1421
+ repo,
1422
+ args: [
1423
+ "checkout",
1424
+ "-b",
1425
+ branch
1426
+ ]
1427
+ });
1428
+ await writeFile(join(repo, `stack-${index}.txt`), `${branch}\n`);
1429
+ git({
1430
+ repo,
1431
+ args: ["add", "."]
1432
+ });
1433
+ git({
1434
+ repo,
1435
+ args: [
1436
+ "commit",
1437
+ "-m",
1438
+ branch
1439
+ ]
1440
+ });
1441
+ }
1442
+ git({
1443
+ repo,
1444
+ args: ["checkout", "stack/open"]
1445
+ });
1446
+ return {
1447
+ repo,
1448
+ mergedSha: git({
1449
+ repo,
1450
+ args: ["rev-parse", "stack/merged"]
1451
+ }),
1452
+ closedSha: git({
1453
+ repo,
1454
+ args: ["rev-parse", "stack/closed"]
1455
+ }),
1456
+ openSha: git({
1457
+ repo,
1458
+ args: ["rev-parse", "stack/open"]
1459
+ }),
1460
+ draftSha: git({
1461
+ repo,
1462
+ args: ["rev-parse", "stack/draft"]
1463
+ })
1464
+ };
1465
+ }
1466
+ const FORGE_FIXTURES = {
1467
+ gh: {
1468
+ fields: "number,state,headRefName,baseRefName",
1469
+ row: ({ number, state, head, base }) => ({
1470
+ number,
1471
+ state: state === "draft" ? "OPEN" : state.toUpperCase(),
1472
+ headRefName: head,
1473
+ baseRefName: base
1474
+ })
1475
+ },
1476
+ origin: {
1477
+ fields: "number,status,headRef,baseRef",
1478
+ row: ({ number, state, head, base }) => ({
1479
+ number,
1480
+ status: state,
1481
+ headRef: head,
1482
+ baseRef: base
1483
+ })
1484
+ }
1485
+ };
1486
+ function forgeRows(forge, prs) {
1487
+ return JSON.stringify(prs.map((pr) => FORGE_FIXTURES[forge].row(pr)));
1488
+ }
1489
+ async function withFakeForge({ directory, forge, operation, currentRows }) {
1490
+ const bin = join(directory, "bin");
1491
+ const outputPath = join(directory, "forge-output.txt");
1492
+ await mkdir(bin, { recursive: true });
1493
+ await writeFile(outputPath, `${currentRows ?? forgeRows(forge, [{
1494
+ number: 11,
1495
+ state: "open",
1496
+ head: "stack/open",
1497
+ base: "stack/closed"
1498
+ }])}\n`);
1499
+ const { fields } = FORGE_FIXTURES[forge];
1500
+ const list = (filter, branch) => `pr list ${filter} ${branch} --state all --json ${fields}`;
1501
+ const fake = join(bin, forge);
1502
+ await writeFile(fake, `#!/usr/bin/env node
1503
+ const { readFileSync, realpathSync } = require("node:fs");
1504
+ const args = process.argv.slice(2).join(" ");
1505
+ const here = realpathSync(process.cwd());
1506
+ const expectedCwd = ${JSON.stringify(realpathSync(join(directory, "repo")))};
1507
+ if (here !== expectedCwd) {
1508
+ process.stderr.write(${JSON.stringify(`${forge} ran outside the fixture repo: `)} + here + "\\n");
1509
+ process.exit(2);
1510
+ }
1511
+ const expected = [
1512
+ ${JSON.stringify(list("--head", "stack/open"))},
1513
+ ${JSON.stringify(list("--head", "stack/closed"))},
1514
+ ${JSON.stringify(list("--head", "stack/merged"))},
1515
+ ${JSON.stringify(list("--base", "stack/open"))},
1516
+ ${JSON.stringify(list("--base", "stack/draft"))}
1517
+ ];
1518
+ const answers = [
1519
+ () => readFileSync(${JSON.stringify(outputPath)}, "utf8"),
1520
+ () => ${JSON.stringify(`${forgeRows(forge, [{
1521
+ number: 13,
1522
+ state: "closed",
1523
+ head: "stack/closed",
1524
+ base: "stack/merged"
1525
+ }])}\n`)},
1526
+ () => ${JSON.stringify(`${forgeRows(forge, [{
1527
+ number: 10,
1528
+ state: "merged",
1529
+ head: "stack/merged",
1530
+ base: "main"
1531
+ }])}\n`)},
1532
+ () => ${JSON.stringify(`${forgeRows(forge, [{
1533
+ number: 14,
1534
+ state: "draft",
1535
+ head: "stack/draft",
1536
+ base: "stack/open"
1537
+ }, {
1538
+ number: 15,
1539
+ state: "closed",
1540
+ head: "stack/abandoned",
1541
+ base: "stack/open"
1542
+ }])}\n`)},
1543
+ () => ${JSON.stringify("[]\n")}
1544
+ ];
1545
+ const index = expected.indexOf(args);
1546
+ if (index === -1) {
1547
+ process.stderr.write(${JSON.stringify(`unexpected ${forge} arguments: `)} + args + "\\n");
1548
+ process.exit(2);
1549
+ }
1550
+ process.stdout.write(answers[index]());
1551
+ `);
1552
+ await chmod(fake, 493);
1553
+ const originalPath = process.env.PATH;
1554
+ process.env.PATH = `${bin}:${originalPath ?? ""}`;
1555
+ try {
1556
+ return await operation();
1557
+ } finally {
1558
+ if (originalPath === void 0) delete process.env.PATH;
1559
+ else process.env.PATH = originalPath;
1560
+ }
1561
+ }
1562
+ function runCli(args, env = process.env) {
1563
+ const result = Bun.spawnSync([
1564
+ process.execPath,
1565
+ SCRIPT,
1566
+ ...args
1567
+ ], { env });
1568
+ return {
1569
+ code: result.exitCode,
1570
+ stdout: result.stdout.toString(),
1571
+ stderr: result.stderr.toString()
1572
+ };
1573
+ }
1574
+ afterEach(async () => {
1575
+ for (const store of handles.splice(0).reverse()) await store.close();
1576
+ for (const directory of directories.splice(0)) await rm(directory, {
1577
+ recursive: true,
1578
+ force: true
1579
+ });
1580
+ });
1581
+ describe("Store", () => {
1582
+ it("initializes an idempotent plain-file store and releases its lock", async () => {
1583
+ const directory = await makeDirectory();
1584
+ const store = useStore(directory);
1585
+ expect(await store.init()).toEqual({ store: directory });
1586
+ const firstUnits = await readFile(join(directory, "units.tsv"), "utf8");
1587
+ const firstLedger = await readFile(join(directory, "ledger.tsv"), "utf8");
1588
+ expect(await store.init()).toEqual({ store: directory });
1589
+ expect(await readFile(join(directory, "units.tsv"), "utf8")).toBe(firstUnits);
1590
+ expect(await readFile(join(directory, "ledger.tsv"), "utf8")).toBe(firstLedger);
1591
+ expect((await readdir(directory)).sort()).toEqual([
1592
+ ".orch.lock",
1593
+ "frontier.json",
1594
+ "gates.md",
1595
+ "inbox",
1596
+ "ledger.tsv",
1597
+ "preferences.md",
1598
+ "units.tsv"
1599
+ ]);
1600
+ await store.close();
1601
+ expect(await readdir(directory)).not.toContain(".orch.lock");
1602
+ });
1603
+ it("composes unit add, set, get, list, and counts", async () => {
1604
+ const { store } = await initializedStore();
1605
+ expect(await store.units.add({
1606
+ id: "u1",
1607
+ track: "build",
1608
+ brief: "briefs/u1.md"
1609
+ })).toMatchObject({
1610
+ id: "u1",
1611
+ state: "pending"
1612
+ });
1613
+ expect(await store.units.add({
1614
+ id: "=SUM(A1)",
1615
+ track: "+build"
1616
+ })).toMatchObject({
1617
+ id: "'=SUM(A1)",
1618
+ track: "'+build"
1619
+ });
1620
+ const updated = await store.units.set({
1621
+ id: "u1",
1622
+ state: "done",
1623
+ branch: "poteto/u1",
1624
+ pr: 184530,
1625
+ sha: "abc123"
1626
+ });
1627
+ expect(updated).toEqual({
1628
+ id: "u1",
1629
+ track: "build",
1630
+ state: "done",
1631
+ branch: "poteto/u1",
1632
+ pr: "184530",
1633
+ sha: "abc123",
1634
+ brief: "briefs/u1.md"
1635
+ });
1636
+ expect(await store.units.get("u1")).toEqual(updated);
1637
+ expect(await store.units.list({
1638
+ state: "done",
1639
+ track: "build"
1640
+ })).toEqual([updated]);
1641
+ expect(await store.units.counts()).toEqual({
1642
+ done: 1,
1643
+ pending: 1
1644
+ });
1645
+ await expect(store.units.add({
1646
+ id: "u1",
1647
+ track: "build"
1648
+ })).rejects.toThrow("unit u1 already exists");
1649
+ await expect(store.units.set({
1650
+ id: "missing",
1651
+ state: "done"
1652
+ })).rejects.toBeInstanceOf(NotFoundError);
1653
+ });
1654
+ it("records, replaces, checks, and summarizes typed ledger verdicts", async () => {
1655
+ const { store } = await initializedStore();
1656
+ try {
1657
+ await store.ledger.check({
1658
+ pr: 184530,
1659
+ sha: "abc123"
1660
+ });
1661
+ throw new Error("expected ledger check to fail");
1662
+ } catch (error) {
1663
+ expect(error).toBeInstanceOf(NotFoundError);
1664
+ if (error instanceof NotFoundError) expect(error.output).toEqual({
1665
+ compact: "NOT-VERIFIED",
1666
+ json: {
1667
+ pr: "184530",
1668
+ sha: "abc123",
1669
+ verdict: "NOT-VERIFIED"
1670
+ }
1671
+ });
1672
+ }
1673
+ expect(() => parseVerdict("looks-good")).toThrow("verdict must be");
1674
+ const recorded = await store.ledger.record({
1675
+ pr: 184530,
1676
+ sha: "abc123",
1677
+ verdict: "unit-test-verified",
1678
+ evidence: "reports/verify.md",
1679
+ verifier: "opus"
1680
+ });
1681
+ expect(await store.ledger.check({
1682
+ pr: 184530,
1683
+ sha: "abc123"
1684
+ })).toEqual(recorded);
1685
+ expect(await store.ledger.summary()).toEqual({ "unit-test-verified": 1 });
1686
+ await store.ledger.record({
1687
+ pr: 184530,
1688
+ sha: "abc123",
1689
+ verdict: "live-ui-verified",
1690
+ evidence: "reports/live.md"
1691
+ });
1692
+ expect(await store.ledger.summary()).toEqual({ "live-ui-verified": 1 });
1693
+ });
1694
+ it("pushes, peeks, and atomically drains inbox pointers", async () => {
1695
+ const { directory, store } = await initializedStore();
1696
+ const first = await store.inbox.push({
1697
+ agent: "worker-1",
1698
+ unit: "u1",
1699
+ status: "done",
1700
+ report: "reports/u1.md"
1701
+ });
1702
+ expect(first.pointer).toMatchObject({
1703
+ unit: "u1",
1704
+ status: "done"
1705
+ });
1706
+ expect(first.filename).toEndWith(".tsv");
1707
+ await store.inbox.push({
1708
+ agent: "worker-2",
1709
+ unit: "u2",
1710
+ status: "failed"
1711
+ });
1712
+ expect(await store.inbox.count()).toBe(2);
1713
+ expect(await store.inbox.peek()).toHaveLength(2);
1714
+ expect(await store.inbox.count()).toBe(2);
1715
+ expect(await store.inbox.drain()).toHaveLength(2);
1716
+ expect(await store.inbox.count()).toBe(0);
1717
+ expect(await readdir(join(directory, "inbox"))).toEqual([]);
1718
+ expect((await readdir(directory)).filter((name) => name.startsWith(".inbox-drain-"))).toEqual([]);
1719
+ });
1720
+ it("replaces a stale lock whose holder pid is dead", async () => {
1721
+ const { directory } = await initializedStore();
1722
+ const exited = Bun.spawn(["true"]);
1723
+ await exited.exited;
1724
+ await writeFile(join(directory, ".orch.lock"), `${exited.pid}\n`);
1725
+ const stale = [];
1726
+ const recovered = useStore(directory, { onStaleLock: (holder) => stale.push(holder) });
1727
+ expect(await recovered.units.add({
1728
+ id: "u1",
1729
+ track: "build"
1730
+ })).toMatchObject({ id: "u1" });
1731
+ expect(stale).toEqual([String(exited.pid)]);
1732
+ await recovered.close();
1733
+ expect(await readdir(directory)).not.toContain(".orch.lock");
1734
+ });
1735
+ it("blocks a writer and steals the pid lock only with force", async () => {
1736
+ const { directory, store } = await initializedStore();
1737
+ await store.close();
1738
+ await writeFile(join(directory, ".orch.lock"), `${process.pid}\n`);
1739
+ const blocked = useStore(directory);
1740
+ await expect(blocked.units.add({
1741
+ id: "u1",
1742
+ track: "build"
1743
+ })).rejects.toThrow(`store lock held by pid ${process.pid}`);
1744
+ const stolen = [];
1745
+ const forced = useStore(directory, {
1746
+ force: true,
1747
+ onLockStolen: (holder) => stolen.push(holder)
1748
+ });
1749
+ expect(await forced.units.add({
1750
+ id: "u1",
1751
+ track: "build"
1752
+ })).toMatchObject({ id: "u1" });
1753
+ expect(stolen).toEqual([String(process.pid)]);
1754
+ await forced.close();
1755
+ expect(await readdir(directory)).not.toContain(".orch.lock");
1756
+ });
1757
+ it("parks gates, stores standing orders, and renders status", async () => {
1758
+ const { directory, store } = await initializedStore();
1759
+ await store.units.add({
1760
+ id: "u1",
1761
+ track: "build"
1762
+ });
1763
+ expect(await store.gates.park({
1764
+ id: "release",
1765
+ question: "Ship now?",
1766
+ options: "ship,wait",
1767
+ defaultAnswer: "wait"
1768
+ })).toMatchObject({
1769
+ kind: "open",
1770
+ id: "release"
1771
+ });
1772
+ expect(await store.standing.add({ line: "Never force push." })).toEqual({
1773
+ number: 1,
1774
+ line: "Never force push."
1775
+ });
1776
+ const first = await store.status.render();
1777
+ expect(first.changed).toBe("first render");
1778
+ expect(first.summary.openGateIds).toEqual(["release"]);
1779
+ expect(await readFile(join(directory, "status.md"), "utf8")).toContain("| release | open | Ship now? |");
1780
+ expect((await store.status.render()).changed).toBe("no derived changes");
1781
+ expect(await store.gates.resolve({
1782
+ id: "release",
1783
+ answer: "ship"
1784
+ })).toMatchObject({
1785
+ kind: "resolved",
1786
+ answer: "ship"
1787
+ });
1788
+ expect((await store.status.render()).changed).toBe("open gates 1->0");
1789
+ expect(await store.gates.list()).toEqual([]);
1790
+ expect(await store.standing.show()).toEqual([{
1791
+ number: 1,
1792
+ line: "Never force push."
1793
+ }]);
1794
+ });
1795
+ for (const forge of ["gh", "origin"]) {
1796
+ it(`resolves the ordered frontier from the ${forge} base chain and validates an optional pin`, async () => {
1797
+ const { directory, store } = await initializedStore();
1798
+ const stack = await makeGitStack(directory);
1799
+ const forgeParams = forge === "gh" ? {} : { forge };
1800
+ await withFakeForge({
1801
+ directory,
1802
+ forge,
1803
+ operation: async () => {
1804
+ expect(await store.frontier.set({
1805
+ repo: stack.repo,
1806
+ ...forgeParams
1807
+ })).toEqual({
1808
+ generation: 1,
1809
+ prs: [
1810
+ {
1811
+ pr: 10,
1812
+ branches: "stack/merged",
1813
+ sha: stack.mergedSha,
1814
+ state: "MERGED"
1815
+ },
1816
+ {
1817
+ pr: 13,
1818
+ branches: "stack/closed",
1819
+ sha: stack.closedSha,
1820
+ state: "CLOSED"
1821
+ },
1822
+ {
1823
+ pr: 11,
1824
+ branches: "stack/open",
1825
+ sha: stack.openSha,
1826
+ state: "OPEN"
1827
+ },
1828
+ {
1829
+ pr: 14,
1830
+ branches: "stack/draft",
1831
+ sha: stack.draftSha,
1832
+ state: "OPEN"
1833
+ }
1834
+ ],
1835
+ lowestUnmerged: 11
1836
+ });
1837
+ expect((await store.frontier.set({
1838
+ repo: stack.repo,
1839
+ ...forgeParams,
1840
+ prs: [
1841
+ 10,
1842
+ 13,
1843
+ 11,
1844
+ 14
1845
+ ]
1846
+ })).generation).toBe(2);
1847
+ expect((await store.frontier.show()).generation).toBe(2);
1848
+ await expect(store.frontier.set({
1849
+ repo: stack.repo,
1850
+ ...forgeParams,
1851
+ prs: [
1852
+ 10,
1853
+ 11,
1854
+ 12
1855
+ ]
1856
+ })).rejects.toThrow("frontier pin mismatch: missing from resolved chain: 12; extra in resolved chain: 13,14");
1857
+ await expect(store.frontier.set({
1858
+ repo: stack.repo,
1859
+ ...forgeParams,
1860
+ prs: [
1861
+ 13,
1862
+ 10,
1863
+ 11,
1864
+ 14
1865
+ ]
1866
+ })).rejects.toThrow("frontier pin mismatch: order differs: expected 13,10,11,14; resolved 10,13,11,14");
1867
+ await expect(store.frontier.set({
1868
+ repo: stack.repo,
1869
+ ...forgeParams,
1870
+ prs: [10, 10]
1871
+ })).rejects.toThrow("--prs must not contain duplicates");
1872
+ }
1873
+ });
1874
+ });
1875
+ it(`rejects unparseable ${forge} output loudly`, async () => {
1876
+ const { directory, store } = await initializedStore();
1877
+ const stack = await makeGitStack(directory);
1878
+ await withFakeForge({
1879
+ directory,
1880
+ forge,
1881
+ currentRows: "this line is not forge output",
1882
+ operation: async () => {
1883
+ await expect(store.frontier.set({
1884
+ repo: stack.repo,
1885
+ forge
1886
+ })).rejects.toThrow(`${forge} pr list --head stack/open output is not valid JSON`);
1887
+ }
1888
+ });
1889
+ });
1890
+ }
1891
+ it("names the missing pull request when a chain branch has none", async () => {
1892
+ const { directory, store } = await initializedStore();
1893
+ const stack = await makeGitStack(directory);
1894
+ await withFakeForge({
1895
+ directory,
1896
+ forge: "gh",
1897
+ currentRows: "[]",
1898
+ operation: async () => {
1899
+ await expect(store.frontier.set({ repo: stack.repo })).rejects.toThrow("branch stack/open has no pull request; open one with gh pr create");
1900
+ }
1901
+ });
1902
+ });
1903
+ it("rejects malformed TSV, verdict, frontier, and inbox data", async () => {
1904
+ const { directory, store } = await initializedStore();
1905
+ await writeFile(join(directory, "units.tsv"), "wrong\n");
1906
+ await expect(store.units.list()).rejects.toThrow("units.tsv has an invalid header");
1907
+ await writeFile(join(directory, "units.tsv"), "id track state branch pr sha brief\nshort row\n");
1908
+ await expect(store.units.list()).rejects.toThrow("units.tsv has a malformed row");
1909
+ await writeFile(join(directory, "ledger.tsv"), "pr sha verdict evidence verifier ts\n1 sha invalid report me now\n");
1910
+ await expect(store.ledger.summary()).rejects.toThrow("ledger.tsv has invalid verdict invalid");
1911
+ await writeFile(join(directory, "frontier.json"), "{\"generation\":\"1\"}\n");
1912
+ await expect(store.frontier.show()).rejects.toThrow("frontier.json has an invalid shape");
1913
+ await writeFile(join(directory, "inbox", "bad.tsv"), "too short\n");
1914
+ await expect(store.inbox.peek()).rejects.toThrow("inbox pointer bad.tsv is malformed");
1915
+ });
1916
+ it("rejects operations after close", async () => {
1917
+ const { store } = await initializedStore();
1918
+ await store.close();
1919
+ await expect(store.units.list()).rejects.toThrow("store is closed");
1920
+ await expect(store.status.render()).rejects.toBeInstanceOf(UserError);
1921
+ });
1922
+ });
1923
+ describe("orch CLI", () => {
1924
+ it("prints commander help and rejects invalid parsing with exit 1", async () => {
1925
+ const help = runCli(["--help"]);
1926
+ expect(help.code).toBe(0);
1927
+ expect(help.stdout).toContain("Commands:");
1928
+ expect(help.stdout).toContain("unit");
1929
+ expect(help.stdout).toContain("ledger");
1930
+ const frontierHelp = runCli([
1931
+ "frontier",
1932
+ "set",
1933
+ "--help"
1934
+ ]);
1935
+ expect(frontierHelp.code).toBe(0);
1936
+ expect(frontierHelp.stdout).toContain("--repo <dir>");
1937
+ expect(frontierHelp.stdout).toContain("--forge <forge>");
1938
+ expect(frontierHelp.stdout).toContain("--prs <n,...>");
1939
+ const directory = await makeDirectory();
1940
+ const invalid = runCli([
1941
+ "--store",
1942
+ directory,
1943
+ "unit",
1944
+ "add",
1945
+ "u1"
1946
+ ]);
1947
+ expect(invalid.code).toBe(1);
1948
+ expect(invalid.stderr).toContain("required option '--track <track>'");
1949
+ const badForge = runCli([
1950
+ "--store",
1951
+ directory,
1952
+ "frontier",
1953
+ "set",
1954
+ "--repo",
1955
+ directory,
1956
+ "--forge",
1957
+ "gt"
1958
+ ]);
1959
+ expect(badForge.code).toBe(1);
1960
+ expect(badForge.stderr).toContain("Allowed choices are gh, origin");
1961
+ });
1962
+ it("accepts ORCH_STORE and emits complete JSON", async () => {
1963
+ const directory = await makeDirectory();
1964
+ const env = {
1965
+ ...process.env,
1966
+ ORCH_STORE: directory
1967
+ };
1968
+ expect(runCli(["init"], env).code).toBe(0);
1969
+ const added = runCli([
1970
+ "unit",
1971
+ "add",
1972
+ "u1",
1973
+ "--track",
1974
+ "build",
1975
+ "--json"
1976
+ ], env);
1977
+ expect(added.code).toBe(0);
1978
+ expect(JSON.parse(added.stdout)).toEqual({
1979
+ id: "u1",
1980
+ track: "build",
1981
+ state: "pending",
1982
+ branch: "",
1983
+ pr: "",
1984
+ sha: "",
1985
+ brief: ""
1986
+ });
1987
+ });
1988
+ it("maps user and not-found outcomes to the preserved exit codes", async () => {
1989
+ const directory = await makeDirectory();
1990
+ expect(runCli([
1991
+ "--store",
1992
+ directory,
1993
+ "init"
1994
+ ]).code).toBe(0);
1995
+ const missingRepo = runCli([
1996
+ "--store",
1997
+ directory,
1998
+ "frontier",
1999
+ "set"
2000
+ ]);
2001
+ expect(missingRepo.code).toBe(1);
2002
+ expect(missingRepo.stderr).toContain("set --repo <dir> or ORCH_REPO");
2003
+ const userError = runCli([
2004
+ "--store",
2005
+ directory,
2006
+ "unit",
2007
+ "add",
2008
+ "",
2009
+ "--track",
2010
+ "build"
2011
+ ]);
2012
+ expect(userError.code).toBe(1);
2013
+ expect(userError.stderr).toContain("unit id must not be empty");
2014
+ const missingUnit = runCli([
2015
+ "--store",
2016
+ directory,
2017
+ "unit",
2018
+ "get",
2019
+ "missing"
2020
+ ]);
2021
+ expect(missingUnit.code).toBe(2);
2022
+ expect(missingUnit.stderr).toContain("unit missing not found");
2023
+ const missingLedger = runCli([
2024
+ "--store",
2025
+ directory,
2026
+ "--json",
2027
+ "ledger",
2028
+ "check",
2029
+ "184530",
2030
+ "abc123"
2031
+ ]);
2032
+ expect(missingLedger.code).toBe(2);
2033
+ expect(JSON.parse(missingLedger.stdout)).toEqual({
2034
+ pr: "184530",
2035
+ sha: "abc123",
2036
+ verdict: "NOT-VERIFIED"
2037
+ });
2038
+ expect(missingLedger.stderr).toBe("");
2039
+ });
2040
+ });
2041
+ //#endregion
2042
+ export {};