@joshuafolkken/kit 1.1178.0 → 1.1240.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/settings.json +2 -2
  3. package/.claude/skills/dependency-update/SKILL.md +4 -8
  4. package/.claude/skills/epic-commands/SKILL.md +2 -2
  5. package/.claude/skills/epic-commands/epic-bundle.md +1 -1
  6. package/.claude/skills/workflow-commands/SKILL.md +2 -4
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +7 -3
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +23 -2
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +9 -4
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +21 -13
  13. package/.claude/skills/workflow-commands/chain-rule.md +1 -1
  14. package/.claude/skills/workflow-commands/delegation.md +2 -2
  15. package/.claude/skills/workflow-commands/followup.md +10 -0
  16. package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
  17. package/.claude/skills/workflow-commands/halfrun.md +1 -1
  18. package/.claude/skills/workflow-commands/issue-scout.md +1 -1
  19. package/.claude/skills/workflow-commands/kickoff.md +2 -1
  20. package/.claude/skills/workflow-commands/latest-gate.md +1 -1
  21. package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
  22. package/.claude/skills/workflow-commands/pre-gate-cut.md +6 -1
  23. package/.claude/skills/workflow-commands/prerequisite.md +2 -2
  24. package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
  25. package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
  26. package/.codex/hooks.json +2 -2
  27. package/.cursorrules +6 -41
  28. package/.github/actions/setup-node/action.yml +1 -1
  29. package/.github/workflows/auto-tag.yml +3 -4
  30. package/.github/workflows/ci.yml +32 -31
  31. package/.github/workflows/dependabot-auto-merge.yml +13 -6
  32. package/.github/workflows/publish.yml +50 -4
  33. package/.github/workflows/sonar-qube.yml +3 -0
  34. package/.pnpmfile.mjs +4 -3
  35. package/.prettierignore +8 -2
  36. package/CLAUDE.md +18 -20
  37. package/README.md +44 -38
  38. package/SECURITY.md +15 -1
  39. package/dist/CLAUDE.md +18 -20
  40. package/dist/commands/document-section-cli.js +734 -0
  41. package/dist/hooks/audit-provision.js +346 -0
  42. package/dist/hooks/batch-guard.js +5 -4
  43. package/dist/hooks/{chunk-G6KRCKEC.js → chunk-4TZVB44A.js} +496 -378
  44. package/dist/hooks/chunk-5VNDSNG3.js +12 -0
  45. package/dist/hooks/chunk-CDY4NFQG.js +15 -0
  46. package/dist/hooks/chunk-ELCN5LCH.js +135 -0
  47. package/dist/hooks/{chunk-VQLLNFNT.js → chunk-F3ELQ2AF.js} +3 -3
  48. package/dist/hooks/{chunk-I4XSL4TT.js → chunk-GPTFMK5E.js} +1 -1
  49. package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
  50. package/dist/hooks/{chunk-UAGLLIGZ.js → chunk-KHGB5S3Y.js} +7 -6
  51. package/dist/hooks/{chunk-GDS5N2KB.js → chunk-KHX4U3BG.js} +735 -683
  52. package/dist/hooks/{chunk-VESKRQOF.js → chunk-KRBERBBI.js} +3 -3
  53. package/dist/hooks/{chunk-DO6NWWHU.js → chunk-QVSWU24O.js} +4 -4
  54. package/dist/hooks/{chunk-S6X6WYKR.js → chunk-WK2XSHSI.js} +1643 -923
  55. package/dist/hooks/{chunk-ZAL3NRGL.js → chunk-XZYKEM6J.js} +25 -10
  56. package/dist/hooks/{chunk-32GQTL4F.js → chunk-ZQGD4GBY.js} +4 -2
  57. package/dist/hooks/codex-hook-adapter.js +14 -12
  58. package/dist/hooks/duplicate-read-guard.js +6 -5
  59. package/dist/hooks/format-edited.js +4 -3
  60. package/dist/hooks/inputs.json +203 -179
  61. package/dist/hooks/investigation-guard.js +6 -5
  62. package/dist/hooks/pretool-guard.js +12 -10
  63. package/dist/hooks/session-lang.js +2 -4
  64. package/dist/hooks/stop-guard.js +66 -72
  65. package/dist/josh.js +1088 -78
  66. package/dist/version/index.d.ts +7 -2
  67. package/dist/version/index.js +152 -75
  68. package/eslint/base.js +23 -1
  69. package/eslint/rules/code-quality.js +3 -0
  70. package/eslint/rules/early-return-one-liner.js +98 -0
  71. package/eslint/rules/unicorn.js +16 -1
  72. package/package.json +7 -6
  73. package/pnpm-workspace.yaml +9 -4
  74. package/prettier/basic.d.ts +3 -1
  75. package/prettier/index.d.ts +3 -1
  76. package/prettier/static.d.ts +3 -1
  77. package/prompts/coding-standards.md +1 -1
  78. package/prompts/collaboration-workflow/file-edits.md +4 -4
  79. package/prompts/collaboration-workflow/glossary.md +1 -1
  80. package/prompts/collaboration-workflow/issue-template.md +1 -0
  81. package/prompts/collaboration-workflow/operating-rules.md +17 -9
  82. package/prompts/collaboration-workflow/overview.md +2 -2
  83. package/prompts/collaboration-workflow/principles.md +14 -12
  84. package/prompts/collaboration-workflow/report-format.md +1 -1
  85. package/prompts/collaboration-workflow/residency.md +1 -1
  86. package/prompts/collaboration-workflow/rule-delivery.md +6 -6
  87. package/prompts/collaboration-workflow/shell-body.md +6 -6
  88. package/prompts/collaboration-workflow/turn-batching.md +1 -1
  89. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
  90. package/prompts/collaboration-workflow/wip-cap.md +10 -10
  91. package/prompts/review-rubric.md +1 -2
  92. package/prompts/review.md +1 -1
  93. package/scripts/agent/agent-headless.ts +29 -0
  94. package/scripts/agent/agent-launch-environment.ts +6 -2
  95. package/scripts/agent/codex-agent-argv.ts +2 -1
  96. package/scripts/auto-ok/auto-ok-cli.ts +1 -1
  97. package/scripts/backlog/backlog-arrival.ts +3 -3
  98. package/scripts/backlog/backlog-budget.ts +1 -1
  99. package/scripts/backlog/backlog-drive-args.ts +135 -0
  100. package/scripts/backlog/backlog-drive-cli.ts +11 -137
  101. package/scripts/backlog/backlog-drive-launch.ts +39 -0
  102. package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
  103. package/scripts/backlog/backlog-drive-named.ts +3 -3
  104. package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
  105. package/scripts/backlog/backlog-drive-owner.ts +1 -1
  106. package/scripts/backlog/backlog-drive-restore.ts +1 -1
  107. package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
  108. package/scripts/backlog/backlog-drive.ts +1 -1
  109. package/scripts/backlog/backlog-next.ts +16 -5
  110. package/scripts/backlog/backlog-offer-cli.ts +6 -6
  111. package/scripts/backlog/backlog-offer.ts +1 -1
  112. package/scripts/backlog/backlog-plan-cli.ts +1 -1
  113. package/scripts/backlog/backlog-pool.ts +57 -11
  114. package/scripts/backlog/backlog-ready.ts +1 -1
  115. package/scripts/backlog/backlog-scope.ts +1 -1
  116. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  117. package/scripts/backlog/backlog-stalled.ts +1 -1
  118. package/scripts/build/build-codex-hooks.ts +3 -1
  119. package/scripts/build/build-commands.ts +66 -0
  120. package/scripts/build/build-hooks.ts +2 -1
  121. package/scripts/build/build-library.ts +12 -3
  122. package/scripts/build/build.ts +70 -0
  123. package/scripts/build/local-bin.ts +3 -13
  124. package/scripts/delegation/investigation-reads.ts +3 -1
  125. package/scripts/discovery/repo-origin.ts +5 -5
  126. package/scripts/doctor/doctor-consumer.ts +2 -2
  127. package/scripts/doctor/doctor-io.ts +2 -3
  128. package/scripts/document/capped-print-part.ts +30 -0
  129. package/scripts/document/capped-print.ts +3 -4
  130. package/scripts/document/document-byte-budget.ts +11 -2
  131. package/scripts/document/document-scan.ts +117 -0
  132. package/scripts/document/document-section.ts +143 -16
  133. package/scripts/document/entry-read-budget.ts +5 -3
  134. package/scripts/document/entry-read-set.ts +10 -89
  135. package/scripts/document/read-set-cost.ts +98 -0
  136. package/scripts/epic/epic-add-body.ts +1 -0
  137. package/scripts/epic/epic-audit-checks.ts +1 -1
  138. package/scripts/epic/epic-audit-rationale.ts +4 -3
  139. package/scripts/epic/epic-bundle-cli.ts +14 -3
  140. package/scripts/epic/epic-bundle-poll.ts +41 -0
  141. package/scripts/epic/epic-busy.ts +1 -1
  142. package/scripts/epic/epic-candidate-confirm.ts +3 -2
  143. package/scripts/epic/epic-classify.ts +1 -1
  144. package/scripts/epic/epic-cli-argv.ts +78 -46
  145. package/scripts/epic/epic-cli-remove.ts +11 -19
  146. package/scripts/epic/epic-cli.ts +74 -73
  147. package/scripts/epic/epic-close-comment.ts +1 -1
  148. package/scripts/epic/epic-close.ts +1 -1
  149. package/scripts/epic/epic-cross-repo.ts +5 -5
  150. package/scripts/epic/epic-graph.ts +1 -1
  151. package/scripts/epic/epic-index.ts +1 -1
  152. package/scripts/epic/epic-issue.ts +2 -6
  153. package/scripts/epic/epic-lane-offer.ts +2 -2
  154. package/scripts/epic/epic-next.ts +29 -42
  155. package/scripts/epic/epic-solo-stale.ts +1 -1
  156. package/scripts/epic/epic-triage.ts +8 -4
  157. package/scripts/epic/epic-validate.ts +1 -1
  158. package/scripts/followup/git-followup-finish.ts +2 -2
  159. package/scripts/followup/git-followup-label.ts +2 -2
  160. package/scripts/followup/git-followup-merged.ts +2 -2
  161. package/scripts/followup/git-pr-followup-checks.ts +142 -0
  162. package/scripts/followup/git-pr-followup.ts +4 -129
  163. package/scripts/gate/core-admission.ts +162 -0
  164. package/scripts/gate/core-budget.ts +190 -68
  165. package/scripts/gate/gate-plan.ts +80 -7
  166. package/scripts/gate/gate-report.ts +28 -9
  167. package/scripts/gate/gate-skip.ts +38 -7
  168. package/scripts/gate/hook-gate-reuse.ts +12 -10
  169. package/scripts/gate/machine-capacity.ts +175 -0
  170. package/scripts/gate/pre-commit-type-check.ts +4 -1
  171. package/scripts/gate/project-checks.ts +6 -9
  172. package/scripts/gate/type-check-step.ts +2 -3
  173. package/scripts/gate/verification-gate.ts +34 -10
  174. package/scripts/gh/git-gh-api-path.ts +2 -2
  175. package/scripts/gh/git-gh-issue-rest.ts +3 -3
  176. package/scripts/gh/git-gh-issue-write.ts +53 -7
  177. package/scripts/gh/git-gh-pr-read.ts +64 -19
  178. package/scripts/gh/git-gh-pr-rest.ts +1 -1
  179. package/scripts/gh/git-gh-pr.ts +5 -1
  180. package/scripts/gh/git-pr-ai-review.ts +1 -1
  181. package/scripts/gh/git-pr-checks-eval.ts +1 -3
  182. package/scripts/gh/git-pr-checks-parse.ts +1 -1
  183. package/scripts/gh/git-pr-checks-watch.ts +6 -5
  184. package/scripts/gh/git-pr-checks.ts +7 -5
  185. package/scripts/gh/git-pr-coderabbit.ts +1 -1
  186. package/scripts/gh/git-pr-error.ts +1 -3
  187. package/scripts/gh/git-pr.ts +9 -13
  188. package/scripts/gh/github-issue-url.ts +2 -2
  189. package/scripts/git/constants.ts +2 -6
  190. package/scripts/git/git-command.ts +20 -218
  191. package/scripts/git/git-diff-reads.ts +225 -0
  192. package/scripts/git/git-error.ts +4 -12
  193. package/scripts/git/git-pre-push-hook.ts +139 -0
  194. package/scripts/git/git-prompt.ts +2 -6
  195. package/scripts/git/git-spawn-sync.ts +3 -1
  196. package/scripts/git/git-spawn.ts +1 -1
  197. package/scripts/git/git-staging.ts +4 -6
  198. package/scripts/git/git-status.ts +3 -9
  199. package/scripts/hooks/codex-hook-adapter.ts +1 -1
  200. package/scripts/hooks/edited-cspell.ts +2 -4
  201. package/scripts/hooks/format-edited-file.ts +3 -12
  202. package/scripts/hooks/pre-push-unit.ts +31 -9
  203. package/scripts/hooks/reserved-run.ts +2 -2
  204. package/scripts/hooks/run-hook.sh +5 -4
  205. package/scripts/hooks/stop-guard.ts +5 -3
  206. package/scripts/init/init-actions.ts +0 -5
  207. package/scripts/init/init-ai-copy.ts +13 -2
  208. package/scripts/init/init-logic-json-merge.ts +19 -0
  209. package/scripts/init/init-logic-secretlint.ts +7 -7
  210. package/scripts/init/init-logic-workspace.ts +41 -2
  211. package/scripts/init/init-logic.ts +16 -26
  212. package/scripts/init/init.ts +10 -16
  213. package/scripts/init/initial-commit.ts +4 -4
  214. package/scripts/init/kit-development-versions.ts +40 -0
  215. package/scripts/init/kit-setup-state.ts +2 -2
  216. package/scripts/init/package-manager-pin.ts +35 -0
  217. package/scripts/init/start-exec.ts +68 -19
  218. package/scripts/init/start-setup-pr.ts +9 -9
  219. package/scripts/init/start-steps.ts +6 -10
  220. package/scripts/issue/defect-rate-cli.ts +1 -1
  221. package/scripts/issue/git-next-issues.ts +1 -1
  222. package/scripts/issue/issue-auto-ok.ts +32 -7
  223. package/scripts/issue/issue-backlinks-cli.ts +2 -2
  224. package/scripts/issue/issue-backlinks.ts +9 -9
  225. package/scripts/issue/issue-comment-cli.ts +19 -13
  226. package/scripts/issue/issue-file-cli.ts +72 -22
  227. package/scripts/issue/issue-file.ts +29 -1
  228. package/scripts/issue/issue-labels.ts +13 -0
  229. package/scripts/issue/issue-release-cli.ts +155 -0
  230. package/scripts/issue/issue-release.ts +58 -0
  231. package/scripts/issue/issue-scout-closed.ts +1 -1
  232. package/scripts/issue/issue-state-cli.ts +30 -74
  233. package/scripts/josh/josh-command-map.ts +2 -0
  234. package/scripts/josh/josh-command-types.ts +14 -0
  235. package/scripts/josh/josh-commands-ai.ts +4 -4
  236. package/scripts/josh/josh-commands-backlog.ts +4 -1
  237. package/scripts/josh/josh-commands-development.ts +18 -0
  238. package/scripts/josh/josh-commands-document.ts +1 -0
  239. package/scripts/josh/josh-commands-issue.ts +8 -1
  240. package/scripts/josh/josh-commands-lane.ts +24 -1
  241. package/scripts/josh/josh-commands-maintenance.ts +6 -6
  242. package/scripts/josh/josh-commands-run.ts +12 -7
  243. package/scripts/josh/josh-in-process.ts +28 -9
  244. package/scripts/josh/josh-logic.ts +34 -15
  245. package/scripts/josh/process-identity.ts +1 -3
  246. package/scripts/josh/session-language-cli.ts +3 -4
  247. package/scripts/lane/lane-await.ts +5 -1
  248. package/scripts/lane/lane-child-invocation.ts +2 -2
  249. package/scripts/lane/lane-cli.ts +12 -4
  250. package/scripts/lane/lane-dispatch.ts +14 -10
  251. package/scripts/lane/lane-environment.ts +5 -1
  252. package/scripts/lane/lane-handoff.ts +1 -1
  253. package/scripts/lane/lane-install.ts +2 -13
  254. package/scripts/lane/lane-launch-cli.ts +15 -1
  255. package/scripts/lane/lane-ledger.ts +145 -0
  256. package/scripts/lane/lane-leftover.ts +44 -30
  257. package/scripts/lane/lane-load.ts +108 -0
  258. package/scripts/lane/lane-measure-cli.ts +149 -0
  259. package/scripts/lane/lane-occupancy.ts +3 -0
  260. package/scripts/lane/lane-open.ts +5 -5
  261. package/scripts/lane/lane-paths.ts +13 -0
  262. package/scripts/lane/lane-reap.ts +1 -3
  263. package/scripts/lane/lane-registry.ts +11 -0
  264. package/scripts/lane/lane-relaunch.ts +1 -1
  265. package/scripts/lane/lane-stats.ts +224 -0
  266. package/scripts/lane/lane-stray.ts +140 -0
  267. package/scripts/lane/lane-vacant.ts +36 -0
  268. package/scripts/lane/openai-lane-ship.ts +2 -2
  269. package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
  270. package/scripts/lane/openai-lane-supervisor.ts +7 -165
  271. package/scripts/lane/openai-review-broker.ts +2 -4
  272. package/scripts/lib/ancestor-directories.ts +35 -0
  273. package/scripts/lib/animation-helpers.ts +2 -2
  274. package/scripts/lib/buffered-process.ts +3 -7
  275. package/scripts/lib/cli-flags.ts +71 -1
  276. package/scripts/lib/spawn-exit.ts +2 -2
  277. package/scripts/lib/timeouts.ts +35 -2
  278. package/scripts/lines/line-budget.ts +4 -6
  279. package/scripts/notify/telegram-test.ts +6 -0
  280. package/scripts/observations/observation-ledger-line.ts +6 -6
  281. package/scripts/observations/observation-ledger-migrate.ts +55 -4
  282. package/scripts/observations/observation-ledger.ts +26 -7
  283. package/scripts/overrides/lockfile-overrides.ts +1 -1
  284. package/scripts/overrides/overrides-logic.ts +1 -1
  285. package/scripts/propagate/propagate-git.ts +1 -1
  286. package/scripts/propagate/propagate-publish.ts +2 -2
  287. package/scripts/propagate/propagate-steps.ts +20 -15
  288. package/scripts/propagate/propagate.ts +36 -27
  289. package/scripts/refactor/import-graph.ts +18 -1
  290. package/scripts/refactor/refactor-lint.ts +4 -4
  291. package/scripts/release/github-release-environment.ts +1 -0
  292. package/scripts/release/github-release-runs.ts +115 -0
  293. package/scripts/release/github-release.ts +13 -67
  294. package/scripts/repo/repository-labels.ts +2 -2
  295. package/scripts/repo/required-checks-logic.ts +1 -3
  296. package/scripts/review/live-evidence.ts +2 -4
  297. package/scripts/review/review-brief-cli.ts +1 -1
  298. package/scripts/review/review-finding-ledger.ts +1 -1
  299. package/scripts/review/review-findings-cli.ts +1 -3
  300. package/scripts/rules/decision-oracle-batch.ts +128 -0
  301. package/scripts/rules/decision-oracle-tokens.ts +21 -0
  302. package/scripts/rules/decision-oracle.ts +8 -120
  303. package/scripts/rules/delivered-rules.ts +26 -182
  304. package/scripts/rules/direct-filing.ts +1 -1
  305. package/scripts/rules/early-heartbeat.ts +3 -3
  306. package/scripts/rules/git-argv.ts +1 -1
  307. package/scripts/rules/git-force.ts +1 -1
  308. package/scripts/rules/implementation-cut.ts +2 -2
  309. package/scripts/rules/index-guard.ts +1 -1
  310. package/scripts/rules/issue-comments.ts +118 -0
  311. package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
  312. package/scripts/rules/lane-background.ts +2 -2
  313. package/scripts/rules/lane-park.ts +18 -0
  314. package/scripts/rules/lane-split-park.ts +128 -0
  315. package/scripts/rules/oracle-firing.ts +1 -1
  316. package/scripts/rules/poll-loop.ts +1 -1
  317. package/scripts/rules/pre-gate-cut.ts +1 -1
  318. package/scripts/rules/protected-files.ts +59 -4
  319. package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
  320. package/scripts/rules/shell-body-trigger.ts +1 -1
  321. package/scripts/rules/stop-rules.ts +18 -7
  322. package/scripts/rules/test-declared-commit.ts +4 -4
  323. package/scripts/rules/wip-cap.ts +63 -0
  324. package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
  325. package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
  326. package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
  327. package/scripts/run/{run-carry.ts → carry/run-carry.ts} +1 -1
  328. package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
  329. package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +18 -5
  330. package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
  331. package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
  332. package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
  333. package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +1 -0
  334. package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
  335. package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
  336. package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +20 -2
  337. package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
  338. package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
  339. package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
  340. package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
  341. package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
  342. package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
  343. package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
  344. package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
  345. package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
  346. package/scripts/run/run-ending.ts +2 -2
  347. package/scripts/run/run-halfrun-resume.ts +6 -1
  348. package/scripts/run/run-headless.ts +1 -1
  349. package/scripts/run/run-invocation.ts +85 -84
  350. package/scripts/run/run-liveness.ts +1 -1
  351. package/scripts/run/run-parent-cut-hook.ts +1 -1
  352. package/scripts/run/run-preflight.ts +6 -6
  353. package/scripts/run/run-prep-cli.ts +1 -1
  354. package/scripts/run/run-prrun-resume.ts +1 -1
  355. package/scripts/run/run-report-cli.ts +4 -4
  356. package/scripts/run/run-report.ts +2 -2
  357. package/scripts/run/run-status-cli.ts +1 -1
  358. package/scripts/run/run-step-cli.ts +3 -3
  359. package/scripts/run/run-step.ts +7 -4
  360. package/scripts/run/run-stop-notify.ts +1 -1
  361. package/scripts/run/run-stranded-detect.ts +4 -4
  362. package/scripts/run/run-stranded.ts +2 -2
  363. package/scripts/run/run-watcher-guard-cli.ts +1 -1
  364. package/scripts/run/run-watcher-guard.ts +5 -3
  365. package/scripts/run/run-watcher-hook.ts +1 -1
  366. package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +2 -2
  367. package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
  368. package/scripts/run/ship/run-ship-lock.ts +50 -0
  369. package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
  370. package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
  371. package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +4 -3
  372. package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
  373. package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
  374. package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +11 -12
  375. package/scripts/run/ship/run-ship-stop-text.ts +13 -0
  376. package/scripts/run/{run-ship-sync.ts → ship/run-ship-sync.ts} +25 -11
  377. package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
  378. package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +25 -11
  379. package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +58 -7
  380. package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +59 -8
  381. package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
  382. package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +13 -15
  383. package/scripts/run/wake/run-wake-restart.ts +58 -0
  384. package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
  385. package/scripts/run/{run-wake.ts → wake/run-wake.ts} +16 -65
  386. package/scripts/safe-chain/preinstall-command.ts +20 -21
  387. package/scripts/safe-chain/project-config.ts +19 -15
  388. package/scripts/sync/sync-ai-files.ts +1 -1
  389. package/scripts/sync/sync-configs.ts +2 -1
  390. package/scripts/sync/sync.ts +15 -2
  391. package/scripts/test/unit-worker-share.ts +1 -0
  392. package/scripts/time-runtime/time-background.ts +5 -1
  393. package/scripts/time-runtime/time-instant.ts +3 -1
  394. package/scripts/time-runtime/time-spans.ts +6 -139
  395. package/scripts/time-runtime/time-tool-call.ts +160 -0
  396. package/scripts/version/effective-upstream.ts +2 -20
  397. package/scripts/version/latest-corepack.ts +124 -93
  398. package/scripts/version/latest-regression.ts +1 -1
  399. package/scripts/version/latest-update.ts +4 -4
  400. package/scripts/version/npm-registry.ts +20 -5
  401. package/scripts/version/publishable-range-check.ts +2 -2
  402. package/scripts/version/publishable-range.ts +1 -1
  403. package/scripts/version/release-age.ts +48 -38
  404. package/scripts/version/release-hold.ts +1 -1
  405. package/scripts/version/safe-chain-version-update.ts +40 -0
  406. package/scripts/version/upgrade-shell-command.ts +48 -8
  407. package/scripts/version/version-check-logic.ts +23 -14
  408. package/scripts/version/version-commands.ts +23 -11
  409. package/scripts/version/version-remote.ts +28 -13
  410. package/templates/pnpm-workspace.basic.yaml +4 -0
  411. package/templates/workflows/dependabot-auto-merge.yml +5 -5
  412. package/templates/workflows/github-release.yml +6 -0
  413. package/scripts/build/build-config-merge.ts +0 -27
  414. package/scripts/build/build-managed-marker.ts +0 -27
  415. package/scripts/build/build-self-sync-guard.ts +0 -27
  416. package/scripts/build/build-version.ts +0 -26
  417. package/scripts/version/preinstall-version-update.ts +0 -86
  418. /package/scripts/git/{schemas.ts → git-schemas.ts} +0 -0
  419. /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
  420. /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
  421. /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
  422. /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
  423. /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
  424. /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
  425. /package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +0 -0
  426. /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
  427. /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
  428. /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
  429. /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
  430. /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
  431. /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
  432. /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
  433. /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
  434. /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
  435. /package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +0 -0
  436. /package/scripts/run/{run-ship-pre-detach.ts → ship/run-ship-pre-detach.ts} +0 -0
  437. /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
  438. /package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +0 -0
  439. /package/scripts/run/{run-ship.ts → ship/run-ship.ts} +0 -0
  440. /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
  441. /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
  442. /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
@@ -5,6 +5,7 @@ import { git_gh_command } from '#scripts/gh/git-gh-command'
5
5
  import { PROJECT_ROOT } from '#scripts/init/init-paths'
6
6
  import { josh_environment_file } from '#scripts/josh/josh-environment-file'
7
7
  import { lane_capacity } from '#scripts/lane/lane-capacity'
8
+ import { cli_flags } from '#scripts/lib/cli-flags'
8
9
  import { issue_citation } from '#scripts/rules/issue-citation'
9
10
  import { epic_classify } from './epic-classify'
10
11
  import { epic_cross_repo } from './epic-cross-repo'
@@ -31,12 +32,10 @@ import { epic_report, type EpicNextResult } from './epic-report'
31
32
  const SUCCESS_EXIT_CODE = 0
32
33
  const FAILURE_EXIT_CODE = 1
33
34
  const ARGV_OFFSET = 2
34
- const REPO_FLAG = '--repo'
35
- // Asking for every free lane is opt-in, so the answer stays one token for a caller that has not been
36
- // changed. `--repo` alone still prints exactly one number — what joshuafolkken/kit#1491 changed for
37
- // it is *when* that number appears, not how many arrive (joshuafolkken/kit#1491).
38
- const LANES_FLAG = '--lanes'
39
- const FLAG_PREFIX = '--'
35
+ // Asking for every free lane (`--lanes`) is opt-in, so the answer stays one token for a caller that
36
+ // has not been changed. `--repo` alone still prints exactly one number — what joshuafolkken/kit#1491
37
+ // changed for it is *when* that number appears, not how many arrive (joshuafolkken/kit#1491).
38
+ const OPTIONS = { repo: { type: 'string' }, lanes: { type: 'boolean' } } as const
40
39
  const USAGE =
41
40
  'Usage: josh epic:next <epic-number|owner/repo#number>... [--repo <owner/repo>] [--lanes]'
42
41
  // A repository that could not be read is refused rather than stood in for. Since
@@ -66,30 +65,6 @@ interface NextOptions {
66
65
  usage?: string
67
66
  }
68
67
 
69
- // A value that is itself a flag is no value at all. `--repo --lanes` used to narrow to a repository
70
- // literally named `--lanes` and report `No runnable child in --lanes` with exit 0; refused here, it
71
- // reaches the usage line instead — the caller meant a repository and named none.
72
- function parse_repo(rest: ReadonlyArray<string>): string | undefined {
73
- const flag_index = rest.indexOf(REPO_FLAG)
74
- const value = flag_index === -1 ? undefined : rest[flag_index + 1]
75
-
76
- return value?.startsWith(FLAG_PREFIX) === true ? undefined : value
77
- }
78
-
79
- // The leading arguments are epic references and everything from the first flag on is options. Split
80
- // on the flag rather than taking a fixed count, so `epic:next 858 909 --repo X` reads two epics and
81
- // `epic:next 858 --repo X` still reads one (joshuafolkken/kit#1493).
82
- function split_at_flag(argv: ReadonlyArray<string>): {
83
- head: ReadonlyArray<string>
84
- rest: ReadonlyArray<string>
85
- } {
86
- const flag_at = argv.findIndex((entry) => entry.startsWith(FLAG_PREFIX))
87
-
88
- return flag_at === -1
89
- ? { head: argv, rest: [] }
90
- : { head: argv.slice(0, flag_at), rest: argv.slice(flag_at) }
91
- }
92
-
93
68
  // Every leading entry, or nothing. One entry that does not parse fails the whole read rather than
94
69
  // being dropped: a mistyped second epic would otherwise run the first one alone and report nothing
95
70
  // at all about the one that was missed — an unattended run silently doing half of what was asked.
@@ -106,21 +81,34 @@ function parse_references(head: ReadonlyArray<string>): ReadonlyArray<EpicRefere
106
81
  return references.length === 0 ? undefined : references
107
82
  }
108
83
 
84
+ function with_repo(
85
+ references: ReadonlyArray<EpicReference>,
86
+ values: { repo?: string; lanes?: boolean },
87
+ ): NextOptions {
88
+ const { repo, lanes: is_all_lanes = false } = values
89
+
90
+ if (repo === undefined) return is_all_lanes ? { usage: USAGE } : { references }
91
+
92
+ return { references, repo, is_all_lanes }
93
+ }
94
+
95
+ // The positional arguments are epic references and the flags are options, so `epic:next 858 909 --repo
96
+ // X` reads two epics and `epic:next 858 --repo X` still reads one (joshuafolkken/kit#1493). The read is
97
+ // strict: an unknown flag reaches the usage line rather than being ignored, and a value that is itself
98
+ // a flag is no value at all — `--repo --lanes` used to narrow to a repository literally named
99
+ // `--lanes` and report `No runnable child in --lanes` with exit 0.
100
+ //
109
101
  // `--lanes` without `--repo` is refused rather than ignored: the lane count is a property of one
110
102
  // repository, so the aggregate listing has nothing to apply it to, and a flag that silently does
111
- // nothing is a caller believing it asked for something.
103
+ // nothing is a caller believing it asked for something. An empty name (`--repo=` from an unset
104
+ // variable) is no repository either: narrowed to it, the answer is `No runnable child in .` and exit 0.
112
105
  function parse_options(argv: ReadonlyArray<string>): NextOptions {
113
- const { head, rest } = split_at_flag(argv)
114
- const references = parse_references(head)
115
- if (references === undefined) return { usage: USAGE }
116
- const repo = parse_repo(rest)
117
- const is_all_lanes = rest.includes(LANES_FLAG)
118
-
119
- if (repo === undefined) {
120
- return is_all_lanes || rest.includes(REPO_FLAG) ? { usage: USAGE } : { references }
121
- }
106
+ const parsed = cli_flags.arguments_of(argv, OPTIONS)
107
+ if (parsed === undefined) return { usage: USAGE }
108
+ const references = parse_references(parsed.positionals)
109
+ if (references === undefined || parsed.values.repo === '') return { usage: USAGE }
122
110
 
123
- return { references, repo, is_all_lanes }
111
+ return with_repo(references, parsed.values)
124
112
  }
125
113
 
126
114
  // The answer for one epic, from an already-fetched snapshot. Split from the fetch so the whole
@@ -446,7 +434,6 @@ const epic_next = {
446
434
  is_order_declared,
447
435
  repo_verdict,
448
436
  UNCHECKED_EXCLUSION,
449
- split_at_flag,
450
437
  parse_options,
451
438
  refuse_reads,
452
439
  views_of,
@@ -1,5 +1,5 @@
1
1
  import { setTimeout as sleep } from 'node:timers/promises'
2
- import type { OpenIssueData } from '#scripts/git/schemas'
2
+ import type { OpenIssueData } from '#scripts/git/git-schemas'
3
3
  import { has_any_label, IN_PROGRESS_LABEL, RUN_SOLO_LABEL } from '#scripts/issue/issue-labels'
4
4
  import { lane_await } from '#scripts/lane/lane-await'
5
5
  import { lane_registry } from '#scripts/lane/lane-registry'
@@ -20,12 +20,16 @@ type TriageVerdict = 'triage'
20
20
 
21
21
  const TRIAGE_VERDICT: TriageVerdict = 'triage'
22
22
 
23
- function is_triaged(child: EpicChild): boolean {
24
- const { labels } = child
25
-
23
+ // Read off a bare label list too, so `josh issue:file` refuses an untriaged `auto-ok` filing by the
24
+ // same answer the offer paths withhold it by (joshuafolkken/kit#3313).
25
+ function has_triage_label(labels: ReadonlyArray<string>): boolean {
26
26
  return has_label_name(labels, RUN_SOLO_LABEL) || has_label_name(labels, RUN_LANE_LABEL)
27
27
  }
28
28
 
29
+ function is_triaged(child: EpicChild): boolean {
30
+ return has_triage_label(child.labels)
31
+ }
32
+
29
33
  // The candidates nobody has judged, in the order they were given.
30
34
  function untriaged(children: ReadonlyArray<EpicChild>): ReadonlyArray<EpicChild> {
31
35
  return children.filter((child) => !is_triaged(child))
@@ -41,7 +45,7 @@ function message(children: ReadonlyArray<EpicChild>, repo: string): string {
41
45
  )
42
46
  }
43
47
 
44
- const epic_triage = { TRIAGE_VERDICT, is_triaged, message, untriaged }
48
+ const epic_triage = { TRIAGE_VERDICT, has_triage_label, is_triaged, message, untriaged }
45
49
 
46
50
  export type { TriageVerdict }
47
51
  export { epic_triage }
@@ -1,5 +1,5 @@
1
+ import { epic_subject_schema } from '#scripts/git/git-schemas'
1
2
  import { parse_json_object_safe } from '#scripts/git/parse-json-array'
2
- import { epic_subject_schema } from '#scripts/git/schemas'
3
3
  import { EPIC_LABEL, has_label_name } from '#scripts/issue/issue-labels'
4
4
  import { epic_parse, type DeclarationState } from './epic-parse'
5
5
 
@@ -1,8 +1,8 @@
1
1
  import { git_next_issues } from '#scripts/issue/git-next-issues'
2
2
  import { review_attest } from '#scripts/review/review-attest'
3
3
  import { review_stamps } from '#scripts/review/review-stamps'
4
- import { run_hold } from '#scripts/run/run-hold'
5
- import { run_progress_clock } from '#scripts/run/run-progress-clock'
4
+ import { run_hold } from '#scripts/run/hold/run-hold'
5
+ import { run_progress_clock } from '#scripts/run/progress/run-progress-clock'
6
6
  import { parse_completed_issue_number } from './followup-issue-number'
7
7
  import { git_followup_cleanup, type CleanupStep } from './git-followup-cleanup'
8
8
  import { git_followup_pending } from './git-followup-pending'
@@ -1,6 +1,6 @@
1
1
  import { git_gh_command } from '#scripts/gh/git-gh-command'
2
+ import { issue_label_schema } from '#scripts/git/git-schemas'
2
3
  import { parse_json_object_safe } from '#scripts/git/parse-json-array'
3
- import { issue_label_schema } from '#scripts/git/schemas'
4
4
  import { IN_PROGRESS_LABEL, label_name_of } from '#scripts/issue/issue-labels'
5
5
  import { z } from 'zod'
6
6
 
@@ -19,7 +19,7 @@ import { z } from 'zod'
19
19
  // No procedure covered the ordinary ending: merged, closed, and still marked as running.
20
20
 
21
21
  // The `number,labels,body` read answers labels as objects, and this is the one field of it this
22
- // step needs. The element shape is `schemas.ts`'s rather than a second spelling of it; the wrapper
22
+ // step needs. The element shape is `git-schemas.ts`'s rather than a second spelling of it; the wrapper
23
23
  // is local because a read's *field list* is what multiplies helpers when it is shared
24
24
  // (`git-gh-issue-read.ts`).
25
25
  const labels_read_schema = z.object({ labels: z.array(issue_label_schema).optional() })
@@ -66,8 +66,8 @@ function is_posted_since(comment: IssueComment, merged_at: string): boolean {
66
66
  return Date.parse(comment.created_at) >= Date.parse(merged_at)
67
67
  }
68
68
 
69
- function parse_comments(comments_json: string | undefined): Array<IssueComment> {
70
- const parsed = COMMENTS_SCHEMA.safeParse(json_value.parse_or_undefined(comments_json ?? ''))
69
+ function parse_comments(comments_json: string | undefined = ''): Array<IssueComment> {
70
+ const parsed = COMMENTS_SCHEMA.safeParse(json_value.parse_or_undefined(comments_json))
71
71
 
72
72
  return parsed.success ? parsed.data : []
73
73
  }
@@ -0,0 +1,142 @@
1
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
2
+ import { git_gh_helpers } from '#scripts/gh/git-gh-helpers'
3
+ import {
4
+ DEFAULT_STABLE_READS,
5
+ git_pr_checks,
6
+ WATCH_CONFIRMED_STABLE_READS,
7
+ } from '#scripts/gh/git-pr-checks'
8
+ import { is_coderabbit_check } from '#scripts/gh/git-pr-checks-eval'
9
+ import { CHECK_STATUS_PASS, type PrStateSnapshot } from '#scripts/gh/git-pr-checks-parse'
10
+ import { json_value } from '#scripts/lib/json-value'
11
+
12
+ // The check wait and the notes read off its snapshot — split out of `git-pr-followup.ts` when it
13
+ // reached its line limit (joshuafolkken/kit#3264). `git-pr-followup` re-exports each name it always
14
+ // exported, so the move changed no call site and no suite that imports from it.
15
+
16
+ // Named so a test can pin the note without restating it (joshuafolkken/kit#999).
17
+ const WATCH_FAILED_NOTE = 'pr checks --watch failed; falling through to polling'
18
+
19
+ // The watch is a look ahead, not a gate. It fails when **any** check has failed — CodeRabbit
20
+ // included — and letting that escape ended the run before `evaluate_pr_state` could apply kit#753's
21
+ // CodeRabbit exemption at all. It only ever bit where every check finished inside the two-minute
22
+ // window, so a repository with a slow E2E never saw it and a fast one always would
23
+ // (joshuafolkken/kit#999). What the watch is has since changed — `gh pr checks --watch` went through
24
+ // GraphQL, so joshuafolkken/kit#1028 replaced it with the same poll loop bounded to two minutes —
25
+ // but its place here has not.
26
+ //
27
+ // Falling through costs nothing: whether the merge may proceed is decided in one place below, and a
28
+ // genuinely failing non-CodeRabbit check still ends the wait on the first poll by kit#990's
29
+ // fast-fail rather than by that failure.
30
+ // The watch fails for two different reasons — "a check failed" and "no checks reported on this
31
+ // branch" — and its own error does not say which. The pull request itself can: a failed check leaves
32
+ // a rollup, and a branch with no checks leaves it empty. Falling through on the empty case would
33
+ // trade a failure reported in seconds for the whole budget spent waiting on a required check that is
34
+ // missing rather than pending, so that one is rethrown and the old behavior kept.
35
+ //
36
+ // **Read from the raw payload, not from `parse_pr_state_snapshot`.** That parser never throws: a
37
+ // malformed answer or a schema mismatch degrades to `rollup: []`, which is indistinguishable there
38
+ // from a branch that genuinely has no checks. Rethrowing on it would put an unreadable answer back
39
+ // on the path this whole change exists to remove. So the question asked here is the narrow one —
40
+ // *is this definitely an empty rollup* — and every other outcome falls through.
41
+ function reads_as_empty_rollup(raw_json: string): boolean {
42
+ const parsed = json_value.parse_or_undefined(raw_json)
43
+
44
+ if (typeof parsed !== 'object' || parsed === null || !('statusCheckRollup' in parsed)) {
45
+ return false
46
+ }
47
+
48
+ const { statusCheckRollup: rollup } = parsed
49
+
50
+ return Array.isArray(rollup) && rollup.length === 0
51
+ }
52
+
53
+ // The read throws rather than answering `undefined`, so the `catch` is its only failure path.
54
+ //
55
+ // The **checks** half, not the whole snapshot: the question here is about `statusCheckRollup` alone,
56
+ // and reading the review listing to answer it paged a whole conversation for nothing
57
+ // (joshuafolkken/kit#1043).
58
+ async function has_no_checks(branch_name: string): Promise<boolean> {
59
+ try {
60
+ const checks = await git_gh_command.pr_get_checks_snapshot(branch_name)
61
+
62
+ return reads_as_empty_rollup(checks.snapshot_json)
63
+ } catch {
64
+ // The read refines the swallow; it is not a gate of its own. An answer it cannot get is not
65
+ // evidence of anything, so prefer falling through — the poll below reads the same state.
66
+ return false
67
+ }
68
+ }
69
+
70
+ async function handle_watch_failure(branch_name: string, error: unknown): Promise<void> {
71
+ if (await has_no_checks(branch_name)) throw error
72
+
73
+ // Swallowed, but never silently: the reason a run stopped early used to be this line, so it
74
+ // stays visible even though it no longer decides anything.
75
+ console.info(`⚠️ ${WATCH_FAILED_NOTE}: ${git_gh_helpers.get_error_message_with_stderr(error)}`)
76
+ }
77
+
78
+ // **Returns whether the watch confirmed completion** (joshuafolkken/kit#2029): `true` only when
79
+ // `pr_checks_watch` saw every check finish, `false` when it timed out with checks still pending or
80
+ // fell through to polling on a swallowed failure. `run_checks` reads it to decide how many stable poll
81
+ // reads the wait still needs. A timed-out watch (`timed_out: true`) never spanned the pending→settled
82
+ // window, so it must keep the full stable-read window rather than claim the confirmation. A rethrown
83
+ // failure — the empty-rollup case — never reaches a caller, so it needs no answer here.
84
+ async function watch_before_polling(branch_name: string): Promise<boolean> {
85
+ console.info('')
86
+ console.info('📊 Watching PR checks...')
87
+
88
+ try {
89
+ const result = await git_gh_command.pr_checks_watch(branch_name)
90
+
91
+ return !result.timed_out
92
+ } catch (error) {
93
+ await handle_watch_failure(branch_name, error)
94
+
95
+ return false
96
+ }
97
+ }
98
+
99
+ // **A confirmed watch lowers the poll's stable-read requirement to one** (joshuafolkken/kit#2029). The
100
+ // watch is the first confirmation that every check settled, so the poll that follows need only agree
101
+ // once rather than twice — which removes the extra interval `followup` spent re-proving a settled run.
102
+ // A skipped or fallen-through watch keeps `DEFAULT_STABLE_READS`, and a check that turned red is still
103
+ // caught: the poll evaluates the full merge gate and throws on failure before any stable read counts.
104
+ async function run_checks(input: {
105
+ branch_name: string
106
+ is_skip_watch: boolean
107
+ }): Promise<PrStateSnapshot> {
108
+ const is_watch_confirmed = !input.is_skip_watch && (await watch_before_polling(input.branch_name))
109
+ const stable_reads = is_watch_confirmed ? WATCH_CONFIRMED_STABLE_READS : DEFAULT_STABLE_READS
110
+
111
+ return await git_pr_checks.wait_for_pr_success(input.branch_name, stable_reads)
112
+ }
113
+
114
+ // Temporary (kit#753): record every CodeRabbit check that was not passing when the merge gate
115
+ // opened, so a merge shipped without CodeRabbit review stays auditable.
116
+ function read_coderabbit_skip_notes(snapshot: PrStateSnapshot): Array<string> {
117
+ return snapshot.rollup
118
+ .filter((check) => is_coderabbit_check(check.name))
119
+ .filter((check) => check.status !== CHECK_STATUS_PASS)
120
+ .map(
121
+ (check) =>
122
+ `CodeRabbit check skipped (kit#753): ${check.name} was ${check.status} at merge time`,
123
+ )
124
+ }
125
+
126
+ // The skip is printed where the run is being watched, not only carried into the Telegram body it
127
+ // ends up in. A merge that went ahead while CodeRabbit had not come back is the one thing kit#753's
128
+ // exemption trades away, and it used to be invisible in `followup`'s own output
129
+ // (joshuafolkken/kit#1217).
130
+ function log_skip_notes(notes: ReadonlyArray<string>): void {
131
+ for (const note of notes) {
132
+ console.info(`⏭ ${note}`)
133
+ }
134
+ }
135
+
136
+ const git_pr_followup_checks = {
137
+ run_checks,
138
+ read_coderabbit_skip_notes,
139
+ log_skip_notes,
140
+ }
141
+
142
+ export { git_pr_followup_checks, WATCH_FAILED_NOTE }
@@ -1,18 +1,9 @@
1
1
  import { git_gh_command } from '#scripts/gh/git-gh-command'
2
- import { git_gh_helpers } from '#scripts/gh/git-gh-helpers'
3
2
  import { git_pr_ai_review, type TelegramContext } from '#scripts/gh/git-pr-ai-review'
4
- import {
5
- DEFAULT_STABLE_READS,
6
- git_pr_checks,
7
- WATCH_CONFIRMED_STABLE_READS,
8
- } from '#scripts/gh/git-pr-checks'
9
- import { is_coderabbit_check } from '#scripts/gh/git-pr-checks-eval'
10
- import { CHECK_STATUS_PASS, type PrStateSnapshot } from '#scripts/gh/git-pr-checks-parse'
11
3
  import { git_pr_coderabbit } from '#scripts/gh/git-pr-coderabbit'
12
4
  import { git_pr_managed_config } from '#scripts/gh/git-pr-managed-config'
13
5
  import { github_issue_url } from '#scripts/gh/github-issue-url'
14
6
  import { git_closes_keyword } from '#scripts/git/git-closes-keyword'
15
- import { json_value } from '#scripts/lib/json-value'
16
7
  import type { GitNotifyConfig } from '#scripts/notify/git-notify'
17
8
  import {
18
9
  telegram_notify,
@@ -22,12 +13,12 @@ import {
22
13
  import { git_followup_merged } from './git-followup-merged'
23
14
  import { git_followup_pending } from './git-followup-pending'
24
15
  import { git_followup_stages, type StageLog } from './git-followup-stages'
16
+ import { git_pr_followup_checks } from './git-pr-followup-checks'
25
17
  import { git_pr_followup_wrapup } from './git-pr-followup-wrapup'
26
18
 
27
19
  const { STAGE, lap } = git_followup_stages
20
+ const { run_checks, read_coderabbit_skip_notes, log_skip_notes } = git_pr_followup_checks
28
21
 
29
- // Named so a test can pin the note without restating it (joshuafolkken/kit#999).
30
- const WATCH_FAILED_NOTE = 'pr checks --watch failed; falling through to polling'
31
22
  const REPO_NAME_SEPARATOR = '/'
32
23
 
33
24
  function parse_repo_name(name_with_owner: string | undefined): string | undefined {
@@ -107,123 +98,6 @@ async function warn_if_missing_closes(branch_name: string): Promise<string | und
107
98
  return undefined
108
99
  }
109
100
 
110
- // The watch is a look ahead, not a gate. It fails when **any** check has failed — CodeRabbit
111
- // included — and letting that escape ended the run before `evaluate_pr_state` could apply kit#753's
112
- // CodeRabbit exemption at all. It only ever bit where every check finished inside the two-minute
113
- // window, so a repository with a slow E2E never saw it and a fast one always would
114
- // (joshuafolkken/kit#999). What the watch is has since changed — `gh pr checks --watch` went through
115
- // GraphQL, so joshuafolkken/kit#1028 replaced it with the same poll loop bounded to two minutes —
116
- // but its place here has not.
117
- //
118
- // Falling through costs nothing: whether the merge may proceed is decided in one place below, and a
119
- // genuinely failing non-CodeRabbit check still ends the wait on the first poll by kit#990's
120
- // fast-fail rather than by that failure.
121
- // The watch fails for two different reasons — "a check failed" and "no checks reported on this
122
- // branch" — and its own error does not say which. The pull request itself can: a failed check leaves
123
- // a rollup, and a branch with no checks leaves it empty. Falling through on the empty case would
124
- // trade a failure reported in seconds for the whole budget spent waiting on a required check that is
125
- // missing rather than pending, so that one is rethrown and the old behavior kept.
126
- //
127
- // **Read from the raw payload, not from `parse_pr_state_snapshot`.** That parser never throws: a
128
- // malformed answer or a schema mismatch degrades to `rollup: []`, which is indistinguishable there
129
- // from a branch that genuinely has no checks. Rethrowing on it would put an unreadable answer back
130
- // on the path this whole change exists to remove. So the question asked here is the narrow one —
131
- // *is this definitely an empty rollup* — and every other outcome falls through.
132
- function reads_as_empty_rollup(raw_json: string): boolean {
133
- const parsed = json_value.parse_or_undefined(raw_json)
134
-
135
- if (typeof parsed !== 'object' || parsed === null || !('statusCheckRollup' in parsed)) {
136
- return false
137
- }
138
-
139
- const { statusCheckRollup: rollup } = parsed
140
-
141
- return Array.isArray(rollup) && rollup.length === 0
142
- }
143
-
144
- // The read throws rather than answering `undefined`, so the `catch` is its only failure path.
145
- //
146
- // The **checks** half, not the whole snapshot: the question here is about `statusCheckRollup` alone,
147
- // and reading the review listing to answer it paged a whole conversation for nothing
148
- // (joshuafolkken/kit#1043).
149
- async function has_no_checks(branch_name: string): Promise<boolean> {
150
- try {
151
- const checks = await git_gh_command.pr_get_checks_snapshot(branch_name)
152
-
153
- return reads_as_empty_rollup(checks.snapshot_json)
154
- } catch {
155
- // The read refines the swallow; it is not a gate of its own. An answer it cannot get is not
156
- // evidence of anything, so prefer falling through — the poll below reads the same state.
157
- return false
158
- }
159
- }
160
-
161
- async function handle_watch_failure(branch_name: string, error: unknown): Promise<void> {
162
- if (await has_no_checks(branch_name)) throw error
163
-
164
- // Swallowed, but never silently: the reason a run stopped early used to be this line, so it
165
- // stays visible even though it no longer decides anything.
166
- console.info(`⚠️ ${WATCH_FAILED_NOTE}: ${git_gh_helpers.get_error_message_with_stderr(error)}`)
167
- }
168
-
169
- // **Returns whether the watch confirmed completion** (joshuafolkken/kit#2029): `true` only when
170
- // `pr_checks_watch` saw every check finish, `false` when it timed out with checks still pending or
171
- // fell through to polling on a swallowed failure. `run_checks` reads it to decide how many stable poll
172
- // reads the wait still needs. A timed-out watch (`timed_out: true`) never spanned the pending→settled
173
- // window, so it must keep the full stable-read window rather than claim the confirmation. A rethrown
174
- // failure — the empty-rollup case — never reaches a caller, so it needs no answer here.
175
- async function watch_before_polling(branch_name: string): Promise<boolean> {
176
- console.info('')
177
- console.info('📊 Watching PR checks...')
178
-
179
- try {
180
- const result = await git_gh_command.pr_checks_watch(branch_name)
181
-
182
- return !result.timed_out
183
- } catch (error) {
184
- await handle_watch_failure(branch_name, error)
185
-
186
- return false
187
- }
188
- }
189
-
190
- // **A confirmed watch lowers the poll's stable-read requirement to one** (joshuafolkken/kit#2029). The
191
- // watch is the first confirmation that every check settled, so the poll that follows need only agree
192
- // once rather than twice — which removes the extra interval `followup` spent re-proving a settled run.
193
- // A skipped or fallen-through watch keeps `DEFAULT_STABLE_READS`, and a check that turned red is still
194
- // caught: the poll evaluates the full merge gate and throws on failure before any stable read counts.
195
- async function run_checks(input: {
196
- branch_name: string
197
- is_skip_watch: boolean
198
- }): Promise<PrStateSnapshot> {
199
- const is_watch_confirmed = !input.is_skip_watch && (await watch_before_polling(input.branch_name))
200
- const stable_reads = is_watch_confirmed ? WATCH_CONFIRMED_STABLE_READS : DEFAULT_STABLE_READS
201
-
202
- return await git_pr_checks.wait_for_pr_success(input.branch_name, stable_reads)
203
- }
204
-
205
- // Temporary (kit#753): record every CodeRabbit check that was not passing when the merge gate
206
- // opened, so a merge shipped without CodeRabbit review stays auditable.
207
- function read_coderabbit_skip_notes(snapshot: PrStateSnapshot): Array<string> {
208
- return snapshot.rollup
209
- .filter((check) => is_coderabbit_check(check.name))
210
- .filter((check) => check.status !== CHECK_STATUS_PASS)
211
- .map(
212
- (check) =>
213
- `CodeRabbit check skipped (kit#753): ${check.name} was ${check.status} at merge time`,
214
- )
215
- }
216
-
217
- // The skip is printed where the run is being watched, not only carried into the Telegram body it
218
- // ends up in. A merge that went ahead while CodeRabbit had not come back is the one thing kit#753's
219
- // exemption trades away, and it used to be invisible in `followup`'s own output
220
- // (joshuafolkken/kit#1217).
221
- function log_skip_notes(notes: ReadonlyArray<string>): void {
222
- for (const note of notes) {
223
- console.info(`⏭ ${note}`)
224
- }
225
- }
226
-
227
101
  // **The one read of the four that can depend on another.** Without an issue number on the command
228
102
  // line it is the pull request body that names it (joshuafolkken/kit#1539), so the title read waits
229
103
  // for exactly that one and for nothing else. With a number in hand the `??` short-circuits and the
@@ -451,7 +325,6 @@ const git_pr_followup = {
451
325
 
452
326
  export {
453
327
  git_pr_followup,
454
- WATCH_FAILED_NOTE,
455
328
  run_checks,
456
329
  build_issue_url,
457
330
  parse_repo_name,
@@ -464,3 +337,5 @@ export {
464
337
  }
465
338
  export type { FollowupInput }
466
339
  export type { TelegramContext } from '#scripts/gh/git-pr-ai-review'
340
+
341
+ export { WATCH_FAILED_NOTE } from './git-pr-followup-checks'