@joshuafolkken/kit 1.1050.0 → 1.1128.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 (630) hide show
  1. package/.claude/settings.json +9 -8
  2. package/.claude/skills/epic-commands/SKILL.md +5 -7
  3. package/.claude/skills/workflow-commands/SKILL.md +20 -12
  4. package/.claude/skills/workflow-commands/background-commands.md +36 -84
  5. package/.claude/skills/workflow-commands/backlogrun-child.md +1 -1
  6. package/.claude/skills/workflow-commands/backlogrun-lanes.md +8 -6
  7. package/.claude/skills/workflow-commands/backlogrun-progress.md +30 -33
  8. package/.claude/skills/workflow-commands/backlogrun-steps.md +12 -12
  9. package/.claude/skills/workflow-commands/backlogrun.md +2 -2
  10. package/.claude/skills/workflow-commands/chain-rule.md +4 -2
  11. package/.claude/skills/workflow-commands/delegation.md +3 -3
  12. package/.claude/skills/workflow-commands/followup-reference.md +1 -1
  13. package/.claude/skills/workflow-commands/followup.md +7 -3
  14. package/.claude/skills/workflow-commands/fullrun-steps.md +3 -2
  15. package/.claude/skills/workflow-commands/fullrun.md +8 -6
  16. package/.claude/skills/workflow-commands/halfrun.md +19 -21
  17. package/.claude/skills/workflow-commands/issue-comments.md +1 -1
  18. package/.claude/skills/workflow-commands/issue-fold-existing.md +10 -10
  19. package/.claude/skills/workflow-commands/issue-scout.md +1 -1
  20. package/.claude/skills/workflow-commands/kickoff.md +9 -4
  21. package/.claude/skills/workflow-commands/latest-gate.md +8 -16
  22. package/.claude/skills/workflow-commands/needs-human-review.md +1 -1
  23. package/.claude/skills/workflow-commands/observation-filing.md +3 -3
  24. package/.claude/skills/workflow-commands/pre-gate-cut.md +4 -8
  25. package/.claude/skills/workflow-commands/prerequisite.md +1 -1
  26. package/.claude/skills/workflow-commands/prrun.md +36 -0
  27. package/.claude/skills/workflow-commands/retrospective.md +1 -1
  28. package/.claude/skills/workflow-commands/split-assessment.md +4 -18
  29. package/.claude/skills/workflow-commands/working-tree-hold.md +22 -2
  30. package/.codex/hooks.json +16 -20
  31. package/.github/actions/setup-node/action.yml +33 -0
  32. package/.github/actions/setup-pnpm/action.yml +93 -0
  33. package/.github/dependabot.yml +5 -1
  34. package/.github/workflows/auto-tag.yml +2 -3
  35. package/.github/workflows/ci.yml +31 -134
  36. package/.github/workflows/dependabot-auto-merge.yml +1 -0
  37. package/.github/workflows/pr-classification.yml +6 -44
  38. package/.github/workflows/production.yml +1 -0
  39. package/.github/workflows/publish.yml +43 -47
  40. package/.github/workflows/sonar-qube.yml +41 -4
  41. package/.pnpmfile.mjs +29 -0
  42. package/AGENTS.md +5 -2
  43. package/CLAUDE.md +33 -27
  44. package/GEMINI.md +5 -2
  45. package/README.md +66 -20
  46. package/SECURITY.md +37 -6
  47. package/cspell/index.yaml +6 -1
  48. package/dist/CLAUDE.basic.md +6 -6
  49. package/dist/CLAUDE.md +33 -27
  50. package/dist/CLAUDE.static.md +6 -6
  51. package/dist/config-merge/index.js +48 -12
  52. package/dist/hooks/batch-guard.js +4 -4
  53. package/dist/hooks/{chunk-7VPDUBRN.js → chunk-4JZDP5X5.js} +152 -155
  54. package/dist/hooks/{chunk-HJIISEAC.js → chunk-5SD2YM7Z.js} +5 -13
  55. package/dist/hooks/{chunk-NAS4C7GP.js → chunk-77J7IEQM.js} +2 -2
  56. package/dist/hooks/{chunk-DMQN45FN.js → chunk-7HVB7SZG.js} +3 -3
  57. package/dist/hooks/{chunk-RLDX5SOC.js → chunk-AIOIOMPX.js} +4413 -1970
  58. package/dist/hooks/{chunk-5BK3K7V4.js → chunk-M7QKEKWO.js} +873 -2049
  59. package/dist/hooks/{chunk-WQ4OSBSS.js → chunk-MKVGBQPA.js} +2 -2
  60. package/dist/hooks/{chunk-EJDX2TOB.js → chunk-NXYMNPUQ.js} +7 -88
  61. package/dist/hooks/{chunk-NJQOBE6L.js → chunk-QVLT52OV.js} +2 -7
  62. package/dist/hooks/{chunk-GQ7KP45M.js → chunk-XYUIQOW3.js} +2 -2
  63. package/dist/hooks/codex-hook-adapter.js +12 -48
  64. package/dist/hooks/duplicate-read-guard.js +5 -5
  65. package/dist/hooks/format-edited.js +2 -2
  66. package/dist/hooks/inputs.json +280 -0
  67. package/dist/hooks/investigation-guard.js +5 -5
  68. package/dist/hooks/pretool-guard.js +9 -9
  69. package/dist/hooks/session-lang.js +0 -1
  70. package/dist/hooks/stop-guard.js +192 -333
  71. package/dist/josh.js +240 -148
  72. package/dist/managed-marker/index.js +2 -1
  73. package/dist/version/index.js +70 -78
  74. package/eslint/rules/promise.js +5 -0
  75. package/eslint/rules/unicorn.js +3 -2
  76. package/package.json +26 -5
  77. package/pnpm-workspace.yaml +5 -7
  78. package/prompts/collaboration-workflow/glossary.md +17 -0
  79. package/prompts/collaboration-workflow/issue-citation.md +5 -5
  80. package/prompts/collaboration-workflow/issue-template.md +1 -1
  81. package/prompts/collaboration-workflow/operating-rules.md +5 -5
  82. package/prompts/collaboration-workflow/overview.md +13 -1
  83. package/prompts/collaboration-workflow/principles.md +9 -0
  84. package/prompts/collaboration-workflow/report-format.md +1 -1
  85. package/prompts/collaboration-workflow/residency.md +3 -3
  86. package/prompts/collaboration-workflow/rule-delivery.md +1 -1
  87. package/prompts/collaboration-workflow/turn-batching.md +3 -3
  88. package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
  89. package/prompts/collaboration-workflow/wip-cap.md +25 -22
  90. package/prompts/collaboration-workflow.md +6 -8
  91. package/prompts/review-rubric.md +1 -1
  92. package/prompts/review.md +3 -3
  93. package/prompts/sonar-hotspot-handling.md +11 -3
  94. package/prompts/testing-guide.md +3 -3
  95. package/scripts/adopt/adopt-toolkits.ts +0 -2
  96. package/scripts/adopt/adopt.ts +0 -2
  97. package/scripts/agent/agent-argv.ts +0 -2
  98. package/scripts/agent/agent-diagnostics.ts +1 -1
  99. package/scripts/agent/agent-role-profile.ts +0 -7
  100. package/scripts/agent/claude-agent-argv.ts +0 -2
  101. package/scripts/agent/codex-agent-argv.ts +1 -1
  102. package/scripts/agent/codex-hooks.ts +92 -0
  103. package/scripts/agent/interactive-ask.ts +1 -1
  104. package/scripts/auto-ok/auto-ok-cli.ts +6 -16
  105. package/scripts/backlog/backlog-arrival.ts +30 -10
  106. package/scripts/backlog/backlog-budget-cli.ts +15 -16
  107. package/scripts/backlog/backlog-budget.ts +0 -2
  108. package/scripts/backlog/backlog-defect-priority.ts +2 -2
  109. package/scripts/backlog/backlog-drive-cli.ts +10 -8
  110. package/scripts/backlog/backlog-drive-named-offer.ts +16 -3
  111. package/scripts/backlog/backlog-drive-named.ts +2 -1
  112. package/scripts/backlog/backlog-drive.ts +76 -16
  113. package/scripts/backlog/backlog-named.ts +0 -3
  114. package/scripts/backlog/backlog-next.ts +9 -15
  115. package/scripts/backlog/backlog-offer-cli.ts +6 -19
  116. package/scripts/backlog/backlog-plan-cli.ts +1 -7
  117. package/scripts/backlog/backlog-plan.ts +30 -19
  118. package/scripts/backlog/backlog-pool.ts +3 -6
  119. package/scripts/backlog/backlog-rank.ts +2 -2
  120. package/scripts/backlog/backlog-ready.ts +39 -10
  121. package/scripts/backlog/backlog-scope.ts +1 -3
  122. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  123. package/scripts/backlog/backlog-stalled.ts +0 -1
  124. package/scripts/backlog/backlog-waves.ts +5 -2
  125. package/scripts/behavior/behavior-cli.ts +1 -1
  126. package/scripts/build/build-codex-hooks.ts +26 -0
  127. package/scripts/build/build-hooks.ts +85 -9
  128. package/scripts/build/hook-bundle-stamp.ts +111 -0
  129. package/scripts/cases/cases-cli.ts +1 -1
  130. package/scripts/cases/cases-logic.ts +1 -1
  131. package/scripts/ci/composite-actions.ts +30 -0
  132. package/scripts/ci/pr-classification.ts +3 -3
  133. package/scripts/claude/workflow-destination.ts +10 -1
  134. package/scripts/clone/clone-fingerprint.ts +0 -1
  135. package/scripts/clone/clone-scan-cli.ts +1 -1
  136. package/scripts/clone/clone-scan.ts +1 -1
  137. package/scripts/config-merge/json-format.ts +0 -1
  138. package/scripts/config-merge/parse-jsonc.ts +33 -3
  139. package/scripts/config-merge/patch-json-key.ts +13 -6
  140. package/scripts/config-merge/patch-yaml-list.ts +1 -1
  141. package/scripts/cost-runtime/codex-usage.ts +2 -9
  142. package/scripts/cost-runtime/cost-attribute.ts +0 -1
  143. package/scripts/cost-runtime/cost-cli.ts +3 -10
  144. package/scripts/cost-runtime/cost-corpus.ts +0 -2
  145. package/scripts/cost-runtime/cost-format.ts +0 -3
  146. package/scripts/cost-runtime/cost-pricing.ts +0 -1
  147. package/scripts/cost-runtime/cost-tokens.ts +0 -1
  148. package/scripts/delegation/delegation-cli.ts +1 -1
  149. package/scripts/delegation/delegation-policy.ts +0 -1
  150. package/scripts/delegation/duplicate-read-guard.ts +0 -8
  151. package/scripts/delegation/fanout-cli.ts +1 -1
  152. package/scripts/delegation/fanout.ts +0 -2
  153. package/scripts/delegation/investigation-reads.ts +1 -164
  154. package/scripts/discovery/repo-map-logic.ts +0 -1
  155. package/scripts/discovery/repo-party-cli.ts +1 -1
  156. package/scripts/doctor/doctor-consumer.ts +6 -10
  157. package/scripts/doctor/doctor-io.ts +9 -13
  158. package/scripts/doctor/doctor-logic.ts +0 -4
  159. package/scripts/doctor/doctor.ts +14 -9
  160. package/scripts/document/backlogrun-parent-read-set.ts +12 -1
  161. package/scripts/document/document-byte-budget.ts +5 -5
  162. package/scripts/document/document-byte-check.ts +0 -1
  163. package/scripts/document/document-read-cli.ts +0 -2
  164. package/scripts/document/document-section-cli.ts +1 -1
  165. package/scripts/document/document-section.ts +0 -3
  166. package/scripts/document/edit-files-cli.ts +0 -1
  167. package/scripts/document/entry-read-budget.ts +21 -7
  168. package/scripts/document/entry-read-set.ts +32 -8
  169. package/scripts/document/lane-child-read-set.ts +8 -6
  170. package/scripts/document/read-files-cli.ts +0 -4
  171. package/scripts/document/read-set-cli.ts +0 -2
  172. package/scripts/document/read-set-point-of-use.ts +9 -3
  173. package/scripts/document/read-set-pricing.ts +0 -1
  174. package/scripts/document/read-set-trim.ts +4 -2
  175. package/scripts/epic/epic-add.ts +1 -1
  176. package/scripts/epic/epic-audit-cli.ts +1 -4
  177. package/scripts/epic/epic-audit-orphans.ts +1 -3
  178. package/scripts/epic/epic-audit-rationale.ts +0 -1
  179. package/scripts/epic/epic-audit-report.ts +0 -2
  180. package/scripts/epic/epic-audit.ts +8 -22
  181. package/scripts/epic/epic-bundle-cli.ts +1 -1
  182. package/scripts/epic/epic-bundle-evidence.ts +0 -1
  183. package/scripts/epic/epic-bundle-referenced.ts +1 -5
  184. package/scripts/epic/epic-bundle.ts +0 -4
  185. package/scripts/epic/epic-busy.ts +4 -5
  186. package/scripts/epic/epic-candidate-confirm.ts +0 -5
  187. package/scripts/epic/epic-classify.ts +2 -2
  188. package/scripts/epic/epic-cli-argv.ts +0 -1
  189. package/scripts/epic/epic-close-comment.ts +1 -1
  190. package/scripts/epic/epic-close.ts +5 -2
  191. package/scripts/epic/epic-cross-repo.ts +2 -4
  192. package/scripts/epic/epic-decision.ts +0 -1
  193. package/scripts/epic/epic-fetch.ts +2 -2
  194. package/scripts/epic/epic-index.ts +2 -3
  195. package/scripts/epic/epic-issue.ts +1 -15
  196. package/scripts/epic/epic-lane-offer.ts +9 -8
  197. package/scripts/epic/epic-nested.ts +1 -1
  198. package/scripts/epic/epic-next-read.ts +1 -3
  199. package/scripts/epic/epic-next-views.ts +0 -1
  200. package/scripts/epic/epic-next.ts +1 -10
  201. package/scripts/epic/epic-parse.ts +0 -1
  202. package/scripts/epic/epic-promote.ts +0 -9
  203. package/scripts/epic/epic-rank.ts +2 -2
  204. package/scripts/epic/epic-read.ts +1 -2
  205. package/scripts/epic/epic-reconcile.ts +1 -1
  206. package/scripts/epic/epic-reference.ts +1 -13
  207. package/scripts/epic/epic-relation-recheck.ts +1 -1
  208. package/scripts/epic/epic-relations.ts +1 -1
  209. package/scripts/epic/epic-remove.ts +1 -1
  210. package/scripts/epic/epic-report.ts +0 -1
  211. package/scripts/epic/epic-run.ts +2 -3
  212. package/scripts/epic/epic-shape.ts +1 -1
  213. package/scripts/epic/epic-solo-stale.ts +107 -0
  214. package/scripts/epic/epic-solo.ts +3 -2
  215. package/scripts/epic/epic-triage.ts +1 -1
  216. package/scripts/epic/epic-validate.ts +1 -1
  217. package/scripts/epic/epic.ts +1 -1
  218. package/scripts/exports/unused-members-cli.ts +53 -0
  219. package/scripts/{git → followup}/followup-issue-number.ts +1 -6
  220. package/scripts/{git → followup}/git-followup-cleanup.ts +2 -1
  221. package/scripts/{git → followup}/git-followup-finish.ts +1 -3
  222. package/scripts/{git → followup}/git-followup-flush.ts +2 -2
  223. package/scripts/{git → followup}/git-followup-issue-close.ts +2 -2
  224. package/scripts/{git → followup}/git-followup-label.ts +4 -4
  225. package/scripts/followup/git-followup-merged.ts +112 -0
  226. package/scripts/{git → followup}/git-followup-pending.ts +1 -1
  227. package/scripts/{git → followup}/git-followup-workflow.ts +19 -11
  228. package/scripts/{git → followup}/git-pr-followup-wrapup.ts +9 -3
  229. package/scripts/{git → followup}/git-pr-followup.ts +53 -16
  230. package/scripts/gate/core-budget.ts +3 -9
  231. package/scripts/gate/gate-plan.ts +7 -4
  232. package/scripts/gate/gate-report.ts +0 -6
  233. package/scripts/gate/gate-tree.ts +1 -1
  234. package/scripts/gate/pre-commit-type-check.ts +0 -6
  235. package/scripts/gate/project-checks.ts +7 -8
  236. package/scripts/gate/scoped-green.ts +0 -1
  237. package/scripts/gate/type-check-step.ts +1 -1
  238. package/scripts/gate/verification-gate.ts +0 -2
  239. package/scripts/gh/fix-gh-packages.ts +15 -12
  240. package/scripts/gh/gh-spawn.ts +2 -2
  241. package/scripts/{git → gh}/git-ai-review-scan.ts +1 -5
  242. package/scripts/{git → gh}/git-gh-check.ts +2 -1
  243. package/scripts/{git → gh}/git-gh-exec.ts +1 -1
  244. package/scripts/{git → gh}/git-gh-failure.ts +1 -1
  245. package/scripts/{git → gh}/git-gh-helpers.ts +0 -1
  246. package/scripts/{git → gh}/git-gh-issue-list.ts +13 -10
  247. package/scripts/{git → gh}/git-gh-issue-read.ts +1 -1
  248. package/scripts/{git → gh}/git-gh-issue-rest.ts +6 -3
  249. package/scripts/{git → gh}/git-gh-issue-write.ts +4 -1
  250. package/scripts/{git → gh}/git-gh-pr-auto-merge.ts +4 -1
  251. package/scripts/{git → gh}/git-gh-pr-read.ts +24 -0
  252. package/scripts/{git → gh}/git-gh-pr-rest.ts +5 -2
  253. package/scripts/{git → gh}/git-gh-pr-review.ts +2 -2
  254. package/scripts/{git → gh}/git-gh-pr-rollup.ts +1 -3
  255. package/scripts/{git → gh}/git-gh-pr.ts +3 -1
  256. package/scripts/{git → gh}/git-pr-ai-review.ts +2 -5
  257. package/scripts/{git → gh}/git-pr-checks-eval.ts +0 -10
  258. package/scripts/{git → gh}/git-pr-checks-parse.ts +4 -20
  259. package/scripts/{git → gh}/git-pr-checks.ts +2 -1
  260. package/scripts/{git → gh}/git-pr-coderabbit.ts +2 -5
  261. package/scripts/{git → gh}/git-pr-confirmation.ts +1 -3
  262. package/scripts/{git → gh}/git-pr-managed-config.ts +2 -3
  263. package/scripts/{git → gh}/git-pr.ts +3 -3
  264. package/scripts/git/changed-file-scope.ts +0 -1
  265. package/scripts/git/git-branch.ts +3 -6
  266. package/scripts/git/git-command.ts +6 -1
  267. package/scripts/git/git-commit.ts +1 -1
  268. package/scripts/git/git-common-directory.ts +80 -24
  269. package/scripts/git/git-error.ts +66 -9
  270. package/scripts/git/git-issue.ts +1 -1
  271. package/scripts/git/git-preflight.ts +2 -2
  272. package/scripts/git/git-prompt.ts +6 -31
  273. package/scripts/git/git-push.ts +1 -1
  274. package/scripts/git/git-spawn-sync.ts +65 -0
  275. package/scripts/git/git-spawn.ts +5 -4
  276. package/scripts/git/git-staging.ts +1 -0
  277. package/scripts/git/git-status.ts +1 -1
  278. package/scripts/git/git-workflow.ts +1 -1
  279. package/scripts/git/gone-branch.ts +1 -0
  280. package/scripts/git/listing-cutoff.ts +1 -1
  281. package/scripts/git/main-merge.ts +1 -1
  282. package/scripts/git/main-sync.ts +19 -1
  283. package/scripts/git/parse-json-array.ts +0 -3
  284. package/scripts/git/repository-lock.ts +16 -16
  285. package/scripts/git/{git-stash.ts → stash/git-stash.ts} +1 -1
  286. package/scripts/git/{stash-pop-args.ts → stash/stash-pop-args.ts} +2 -7
  287. package/scripts/git/{stash-pop-cli.ts → stash/stash-pop-cli.ts} +0 -1
  288. package/scripts/git/{stash-sweep-lock.ts → stash/stash-sweep-lock.ts} +3 -3
  289. package/scripts/hooks/codex-hook-adapter.ts +0 -1
  290. package/scripts/hooks/edited-cspell.ts +1 -1
  291. package/scripts/hooks/format-edited-file.ts +2 -0
  292. package/scripts/hooks/pre-push-unit.ts +0 -4
  293. package/scripts/hooks/pretool-guard.ts +22 -4
  294. package/scripts/hooks/reserved-run.ts +1 -1
  295. package/scripts/hooks/step-zero-notice.ts +76 -0
  296. package/scripts/hooks/stop-guard.ts +47 -21
  297. package/scripts/init/claude-plugin-config.ts +4 -5
  298. package/scripts/init/hook-command-rewrite.ts +16 -8
  299. package/scripts/init/hook-launch.ts +41 -15
  300. package/scripts/init/init-basic.ts +2 -5
  301. package/scripts/init/init-file-action.ts +54 -0
  302. package/scripts/init/init-logic-migrate.ts +35 -17
  303. package/scripts/init/init-logic-secretlint.ts +1 -1
  304. package/scripts/init/init-logic.ts +10 -19
  305. package/scripts/init/init.ts +11 -71
  306. package/scripts/init/kit-written-paths.ts +2 -0
  307. package/scripts/init/project-profile.ts +9 -17
  308. package/scripts/init/start-exec.ts +1 -1
  309. package/scripts/init/start-setup-pr.ts +6 -6
  310. package/scripts/init/start-steps.ts +3 -1
  311. package/scripts/issue/behavior-change-lint.ts +0 -1
  312. package/scripts/issue/defect-rate-cli.ts +64 -23
  313. package/scripts/issue/defect-rate.ts +1 -2
  314. package/scripts/{git → issue}/git-next-issues.ts +3 -3
  315. package/scripts/issue/issue-backlinks-cli.ts +1 -5
  316. package/scripts/issue/issue-backlinks.ts +7 -14
  317. package/scripts/issue/issue-cite-cli.ts +2 -6
  318. package/scripts/issue/issue-cite.ts +12 -8
  319. package/scripts/issue/issue-classification.ts +1 -9
  320. package/scripts/issue/issue-closing-pr.ts +4 -4
  321. package/scripts/issue/issue-comment-cli.ts +2 -2
  322. package/scripts/issue/issue-file-cli.ts +5 -5
  323. package/scripts/issue/issue-file.ts +7 -14
  324. package/scripts/issue/issue-fold-cli.ts +1 -1
  325. package/scripts/issue/issue-fold-existing-cli.ts +1 -1
  326. package/scripts/{git → issue}/issue-labels.ts +8 -0
  327. package/scripts/issue/issue-lint-cli.ts +1 -1
  328. package/scripts/issue/issue-merged.ts +2 -2
  329. package/scripts/{git → issue}/issue-rank.ts +1 -1
  330. package/scripts/issue/issue-read-cli.ts +8 -47
  331. package/scripts/issue/issue-read.ts +0 -2
  332. package/scripts/issue/issue-report-failures.ts +80 -0
  333. package/scripts/issue/issue-scout-cli.ts +1 -7
  334. package/scripts/issue/issue-scout-closed.ts +2 -2
  335. package/scripts/issue/issue-scout.ts +0 -2
  336. package/scripts/issue/issue-state-cli.ts +5 -46
  337. package/scripts/issue/issue-state.ts +1 -5
  338. package/scripts/issue/markdown-section.ts +21 -6
  339. package/scripts/issue/measure-rerun-cli.ts +74 -15
  340. package/scripts/issue/reproduction-measure.ts +1 -1
  341. package/scripts/josh/cli-body.ts +1 -1
  342. package/scripts/josh/file-map-stamp.ts +0 -3
  343. package/scripts/josh/josh-command-map.ts +2 -0
  344. package/scripts/josh/josh-commands-ai.ts +9 -102
  345. package/scripts/josh/josh-commands-backlog.ts +5 -1
  346. package/scripts/josh/josh-commands-development.ts +8 -1
  347. package/scripts/josh/josh-commands-issue.ts +2 -2
  348. package/scripts/josh/josh-commands-maintenance.ts +7 -0
  349. package/scripts/josh/josh-commands-project.ts +7 -1
  350. package/scripts/josh/josh-commands-run.ts +114 -0
  351. package/scripts/josh/josh-commands-versioning.ts +8 -0
  352. package/scripts/josh/josh-commands-workflow.ts +3 -3
  353. package/scripts/josh/path-decision.ts +0 -3
  354. package/scripts/josh/process-owner.ts +16 -0
  355. package/scripts/josh/run-github-prerequisite.ts +3 -6
  356. package/scripts/josh/session-language-cli.ts +0 -1
  357. package/scripts/lane/lane-await.ts +46 -19
  358. package/scripts/lane/lane-capacity.ts +6 -3
  359. package/scripts/lane/lane-child-invocation.ts +20 -7
  360. package/scripts/lane/lane-cli.ts +1 -4
  361. package/scripts/lane/lane-close.ts +5 -4
  362. package/scripts/lane/lane-dispatch.ts +3 -4
  363. package/scripts/lane/lane-environment.ts +0 -9
  364. package/scripts/lane/lane-handoff.ts +38 -0
  365. package/scripts/lane/lane-launch-cli.ts +31 -27
  366. package/scripts/lane/lane-open.ts +0 -3
  367. package/scripts/lane/lane-output.ts +0 -1
  368. package/scripts/lane/lane-registry.ts +9 -0
  369. package/scripts/lane/openai-lane-ship.ts +2 -0
  370. package/scripts/lane/openai-lane-supervisor-decision.ts +2 -1
  371. package/scripts/lane/openai-lane-supervisor.ts +6 -4
  372. package/scripts/lane/openai-review-broker.ts +7 -1
  373. package/scripts/lib/bounded-pool.ts +1 -0
  374. package/scripts/lib/cli-flags.ts +31 -2
  375. package/scripts/lib/error-message.ts +12 -1
  376. package/scripts/lib/json-value.ts +5 -4
  377. package/scripts/lib/poll.ts +3 -1
  378. package/scripts/lib/timed-fetch.ts +15 -0
  379. package/scripts/lib/timeouts.ts +13 -0
  380. package/scripts/{yaml → lib}/yaml-document.ts +3 -1
  381. package/scripts/{bytes → lines}/bytes-command.ts +0 -2
  382. package/scripts/lines/line-targets.ts +0 -11
  383. package/scripts/lines/lines-command.ts +5 -4
  384. package/scripts/lint/lint-related-scope.ts +0 -1
  385. package/scripts/lint/lint-related.ts +1 -1
  386. package/scripts/{git → notify}/telegram-notify.ts +2 -2
  387. package/scripts/{git → notify}/telegram-test.ts +4 -4
  388. package/scripts/observations/observation-ledger-home.ts +0 -1
  389. package/scripts/observations/observation-ledger-line.ts +0 -1
  390. package/scripts/observations/observation-ledger-migrate.ts +1 -1
  391. package/scripts/observations/observations-flush-landing.ts +3 -3
  392. package/scripts/observations/observations-flush.ts +2 -2
  393. package/scripts/overrides/lockfile-overrides.ts +1 -1
  394. package/scripts/overrides/overrides-logic.ts +1 -1
  395. package/scripts/package/package-scout-cli.ts +2 -7
  396. package/scripts/package/package-scout.ts +0 -2
  397. package/scripts/propagate/propagate-git-failure.ts +0 -1
  398. package/scripts/propagate/propagate-git.ts +3 -10
  399. package/scripts/propagate/propagate-publish.ts +2 -3
  400. package/scripts/propagate/propagate-run.ts +0 -2
  401. package/scripts/propagate/propagate-steps.ts +3 -5
  402. package/scripts/propagate/propagate-targets.ts +0 -4
  403. package/scripts/propagate/propagate.ts +0 -4
  404. package/scripts/refactor/import-graph.ts +0 -1
  405. package/scripts/refactor/refactor-lint.ts +0 -2
  406. package/scripts/refactor/refactor-scan.ts +2 -2
  407. package/scripts/refactor/refactor-targets.ts +0 -1
  408. package/scripts/registry-migration/migrate-logic.ts +28 -21
  409. package/scripts/registry-migration/migrate.ts +25 -14
  410. package/scripts/release/github-release-cli.ts +19 -6
  411. package/scripts/release/github-release-environment.ts +46 -0
  412. package/scripts/release/github-release.ts +151 -94
  413. package/scripts/release/release-history.ts +0 -1
  414. package/scripts/release/release-plan.ts +0 -1
  415. package/scripts/release/release-publish.ts +38 -5
  416. package/scripts/release/release-scope-cli.ts +1 -2
  417. package/scripts/release/release-tag.ts +2 -4
  418. package/scripts/release/release-worktree.ts +97 -2
  419. package/scripts/repo/auto-merge-setting-logic.ts +1 -4
  420. package/scripts/repo/repo-setting.ts +1 -1
  421. package/scripts/repo/repository-labels.ts +3 -3
  422. package/scripts/repo/required-checks-logic.ts +206 -0
  423. package/scripts/repo/required-checks-report.ts +71 -0
  424. package/scripts/repo/ruleset-check-cli.ts +53 -0
  425. package/scripts/repo/ruleset-check.ts +147 -0
  426. package/scripts/report/report-lint-cli.ts +0 -4
  427. package/scripts/review/disposition-cli.ts +1 -1
  428. package/scripts/review/live-evidence.ts +1 -1
  429. package/scripts/review/review-attest-cli.ts +0 -6
  430. package/scripts/review/review-attest.ts +4 -16
  431. package/scripts/review/review-brief-cli.ts +23 -6
  432. package/scripts/review/review-brief.ts +21 -8
  433. package/scripts/review/review-checkout.ts +3 -2
  434. package/scripts/review/review-diff-parts.ts +89 -0
  435. package/scripts/review/review-diff-write.ts +97 -0
  436. package/scripts/review/review-finding-ledger.ts +0 -1
  437. package/scripts/review/review-record-cli.ts +10 -17
  438. package/scripts/review/review-record.ts +0 -1
  439. package/scripts/review/review-round2-cli.ts +1 -1
  440. package/scripts/review/review-stamps.ts +0 -3
  441. package/scripts/review/review-tree.ts +14 -2
  442. package/scripts/rules/decision-oracle-stage.ts +18 -0
  443. package/scripts/rules/decision-oracle.ts +10 -7
  444. package/scripts/rules/delivered-rules.ts +9 -1
  445. package/scripts/rules/destructive-command.ts +119 -0
  446. package/scripts/rules/direct-filing.ts +1 -1
  447. package/scripts/rules/early-heartbeat.ts +4 -3
  448. package/scripts/rules/file-body.ts +0 -3
  449. package/scripts/rules/filing-cap.ts +0 -2
  450. package/scripts/rules/gh-api.ts +7 -2
  451. package/scripts/rules/git-argv.ts +18 -7
  452. package/scripts/rules/git-force.ts +0 -2
  453. package/scripts/rules/implementation-cut.ts +0 -1
  454. package/scripts/rules/index-guard.ts +71 -0
  455. package/scripts/rules/issue-citation.ts +50 -25
  456. package/scripts/rules/lane-background.ts +15 -7
  457. package/scripts/rules/lane-park.ts +22 -6
  458. package/scripts/rules/last-prompt.ts +1 -0
  459. package/scripts/rules/oracle-consulted.ts +1 -1
  460. package/scripts/rules/oracle-firing.ts +1 -0
  461. package/scripts/rules/oracle-list-cli.ts +0 -3
  462. package/scripts/rules/permission-guards.ts +14 -0
  463. package/scripts/rules/protected-files.ts +74 -0
  464. package/scripts/rules/reply-language.ts +1 -1
  465. package/scripts/rules/rule-body-guard.ts +0 -3
  466. package/scripts/rules/rule-value-cli.ts +0 -1
  467. package/scripts/rules/shell-body-trigger.ts +0 -1
  468. package/scripts/rules/shell-segments.ts +0 -1
  469. package/scripts/rules/stop-rules.ts +20 -9
  470. package/scripts/rules/tail-commands.ts +1 -1
  471. package/scripts/rules/test-declared-commit.ts +1 -1
  472. package/scripts/rules/third-party-write.ts +0 -1
  473. package/scripts/rules/worktree-guard.ts +26 -9
  474. package/scripts/run/run-carry-args.ts +8 -12
  475. package/scripts/run/run-carry-cli.ts +0 -2
  476. package/scripts/run/run-carry-stash.ts +2 -2
  477. package/scripts/run/run-carry-streak.ts +1 -1
  478. package/scripts/run/run-cut-args.ts +1 -6
  479. package/scripts/run/run-cut-cli.ts +0 -7
  480. package/scripts/run/run-cut.ts +8 -54
  481. package/scripts/run/run-ending-cli.ts +2 -6
  482. package/scripts/run/run-ending.ts +1 -4
  483. package/scripts/run/run-entry-cli.ts +133 -25
  484. package/scripts/run/run-entry-stop.ts +104 -0
  485. package/scripts/run/run-entry.ts +0 -1
  486. package/scripts/run/run-event-cli.ts +0 -2
  487. package/scripts/run/run-event-follow.ts +1 -0
  488. package/scripts/run/run-event-render.ts +1 -1
  489. package/scripts/run/run-event-scope.ts +53 -4
  490. package/scripts/run/run-event-stream-emit.ts +7 -2
  491. package/scripts/run/run-event-stream.ts +0 -3
  492. package/scripts/run/run-event-watch.ts +1 -0
  493. package/scripts/run/run-halfrun-resume.ts +20 -7
  494. package/scripts/run/run-headless.ts +2 -2
  495. package/scripts/run/run-hold-cli.ts +33 -10
  496. package/scripts/run/run-hold.ts +29 -6
  497. package/scripts/run/run-issue-number.ts +1 -1
  498. package/scripts/run/run-liveness-cli.ts +2 -6
  499. package/scripts/run/run-liveness.ts +2 -3
  500. package/scripts/run/run-merge-cli.ts +7 -23
  501. package/scripts/run/run-merge-steps.ts +5 -5
  502. package/scripts/run/run-merge.ts +1 -1
  503. package/scripts/run/run-next-cli.ts +1 -1
  504. package/scripts/run/run-parent-cut-hook.ts +4 -2
  505. package/scripts/run/run-preflight.ts +2 -9
  506. package/scripts/run/run-prep-cli.ts +6 -3
  507. package/scripts/run/run-prep-locate.ts +1 -1
  508. package/scripts/run/run-progress-args.ts +20 -4
  509. package/scripts/run/run-progress-cli.ts +35 -36
  510. package/scripts/run/run-progress-clock.ts +10 -35
  511. package/scripts/run/run-progress-config.ts +0 -4
  512. package/scripts/run/run-progress-read.ts +1 -2
  513. package/scripts/run/run-progress.ts +1 -2
  514. package/scripts/run/run-prrun-resume.ts +117 -0
  515. package/scripts/run/run-report-cli.ts +1 -2
  516. package/scripts/run/run-report.ts +0 -1
  517. package/scripts/run/run-retrospective.ts +1 -1
  518. package/scripts/run/run-review-cli.ts +0 -3
  519. package/scripts/run/run-review-steps.ts +2 -0
  520. package/scripts/run/run-review.ts +0 -2
  521. package/scripts/run/run-ship-cli.ts +29 -19
  522. package/scripts/run/run-ship-detach.ts +1 -0
  523. package/scripts/run/run-ship-next.ts +106 -0
  524. package/scripts/run/run-ship-preflight.ts +1 -1
  525. package/scripts/run/run-ship-probe.ts +4 -1
  526. package/scripts/run/run-ship-return.ts +8 -3
  527. package/scripts/run/run-ship-review-steps.ts +18 -4
  528. package/scripts/run/run-ship-review.ts +11 -0
  529. package/scripts/run/run-ship-scoped.ts +1 -0
  530. package/scripts/run/run-ship-steps.ts +1 -1
  531. package/scripts/run/run-ship.ts +0 -1
  532. package/scripts/run/run-stage-read.ts +62 -0
  533. package/scripts/run/run-stage.ts +160 -0
  534. package/scripts/run/run-status-cli.ts +3 -16
  535. package/scripts/run/run-step-cli.ts +21 -14
  536. package/scripts/run/run-step.ts +1 -1
  537. package/scripts/run/run-stop-notify.ts +1 -3
  538. package/scripts/run/run-stranded-cli.ts +0 -4
  539. package/scripts/run/run-stranded-detect.ts +2 -2
  540. package/scripts/run/run-tail-cli.ts +17 -11
  541. package/scripts/run/run-tail.ts +2 -0
  542. package/scripts/run/run-tidy-lanes.ts +6 -2
  543. package/scripts/run/run-tidy-stashes.ts +12 -12
  544. package/scripts/run/run-wake-cli.ts +4 -13
  545. package/scripts/run/run-wake-loop.ts +2 -2
  546. package/scripts/run/run-wake.ts +2 -4
  547. package/scripts/run/run-watcher-guard-cli.ts +0 -4
  548. package/scripts/run/run-watcher-guard.ts +18 -12
  549. package/scripts/run/run-watcher-hook.ts +1 -1
  550. package/scripts/safe-chain/ci-installer-pin.ts +11 -5
  551. package/scripts/safe-chain/pack-hook.ts +92 -0
  552. package/scripts/safe-chain/project-config.ts +1 -1
  553. package/scripts/security/security-audit-provision-logic.ts +0 -1
  554. package/scripts/security/security-audit-provision.ts +3 -2
  555. package/scripts/security/security-audit.ts +0 -1
  556. package/scripts/security/security-updates-logic.ts +0 -1
  557. package/scripts/sonar/sonar-hotspots-cli.ts +10 -52
  558. package/scripts/sonar/sonar-hotspots.ts +0 -1
  559. package/scripts/sonar/sonar-new-code-cli.ts +150 -0
  560. package/scripts/sonar/sonar-new-code.ts +79 -0
  561. package/scripts/sonar/sonar-project.ts +97 -0
  562. package/scripts/split/split-assess-cli.ts +1 -1
  563. package/scripts/split/split-assess.ts +2 -5
  564. package/scripts/sync/managed-config-scope-cli.ts +1 -1
  565. package/scripts/sync/skill-migration.ts +0 -1
  566. package/scripts/sync/sync-ai-files.ts +4 -0
  567. package/scripts/sync/sync-hook-safety.ts +2 -3
  568. package/scripts/sync/sync.ts +1 -8
  569. package/scripts/sync/template-source-logic.ts +0 -1
  570. package/scripts/sync/workflow-pin-logic.ts +16 -28
  571. package/scripts/test/test-declared-changed.ts +13 -16
  572. package/scripts/test/test-declared-logic.ts +0 -1
  573. package/scripts/test/test-declared.ts +3 -9
  574. package/scripts/test/test-red-commit.ts +2 -2
  575. package/scripts/test/test-red.ts +14 -7
  576. package/scripts/test/test-type-logic.ts +1 -1
  577. package/scripts/test/unit-worker-share.ts +7 -16
  578. package/scripts/time-runtime/time-batch-guard.ts +1 -2
  579. package/scripts/time-runtime/time-bundle-call.ts +0 -1
  580. package/scripts/time-runtime/time-bundles.ts +0 -4
  581. package/scripts/time-runtime/time-density-hook.ts +0 -2
  582. package/scripts/time-runtime/time-followup-stage.ts +1 -1
  583. package/scripts/time-runtime/time-format.ts +0 -73
  584. package/scripts/time-runtime/time-round-trips.ts +0 -4
  585. package/scripts/time-runtime/time-single-check.ts +1 -1
  586. package/scripts/time-runtime/time-spans.ts +1 -1
  587. package/scripts/time-runtime/time-transcript-line.ts +1 -2
  588. package/scripts/ui/ui-routes-cli.ts +0 -5
  589. package/scripts/ui/ui-routes.ts +0 -5
  590. package/scripts/version/effective-upstream.ts +2 -2
  591. package/scripts/version/latest-corepack.ts +7 -9
  592. package/scripts/version/latest-scope-cli.ts +0 -2
  593. package/scripts/version/latest-update.ts +0 -2
  594. package/scripts/version/npm-registry.ts +2 -2
  595. package/scripts/version/publishable-range-check.ts +1 -1
  596. package/scripts/version/release-hold.ts +0 -4
  597. package/scripts/version/running-binary.ts +2 -2
  598. package/scripts/version/version-check-logic.ts +0 -2
  599. package/scripts/version/version-commands.ts +11 -1
  600. package/scripts/version/version-remote.ts +3 -3
  601. package/scripts/version/version-targets.ts +4 -4
  602. package/templates/CLAUDE.basic.md +6 -6
  603. package/templates/sonar-project.properties +28 -1
  604. package/templates/workflows/ci.yml +16 -82
  605. package/templates/workflows/dependabot-auto-merge.yml +6 -3
  606. package/templates/workflows/github-release.yml +79 -0
  607. package/tsconfig.sonar.json +4 -22
  608. package/prompts/collaboration-workflow/cross-repo-epic.md +0 -5
  609. package/prompts/collaboration-workflow/epic-audit.md +0 -5
  610. package/prompts/collaboration-workflow/epic-bundle.md +0 -5
  611. package/scripts/init/plugin-install-hint.ts +0 -47
  612. package/scripts/version/parse-json.ts +0 -13
  613. /package/scripts/{git → followup}/git-followup-stages.ts +0 -0
  614. /package/scripts/{git → gh}/git-gh-api-path.ts +0 -0
  615. /package/scripts/{git → gh}/git-gh-command.ts +0 -0
  616. /package/scripts/{git → gh}/git-gh-issue.ts +0 -0
  617. /package/scripts/{git → gh}/git-gh-pr-snapshot.ts +0 -0
  618. /package/scripts/{git → gh}/git-gh-reachability.ts +0 -0
  619. /package/scripts/{git → gh}/git-gh-repo.ts +0 -0
  620. /package/scripts/{git → gh}/git-gh-rest-state.ts +0 -0
  621. /package/scripts/{git → gh}/git-pr-checks-watch.ts +0 -0
  622. /package/scripts/{git → gh}/git-pr-error.ts +0 -0
  623. /package/scripts/{git → gh}/git-pr-messages.ts +0 -0
  624. /package/scripts/{git → gh}/github-issue-url.ts +0 -0
  625. /package/scripts/git/{stash-orphans.ts → stash/stash-orphans.ts} +0 -0
  626. /package/scripts/{git → issue}/issue-reference.ts +0 -0
  627. /package/scripts/{git → lib}/animation-helpers.ts +0 -0
  628. /package/scripts/{git → lib}/git-animation.ts +0 -0
  629. /package/scripts/{git → notify}/git-notify.ts +0 -0
  630. /package/scripts/{git → notify}/telegram-test-logic.ts +0 -0
@@ -1,4 +1,4 @@
1
- import { git_gh_command } from '#scripts/git/git-gh-command'
1
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
2
2
  import { epic_fetch } from './epic-fetch'
3
3
  import { epic_graph, type EpicChild } from './epic-graph'
4
4
  import { epic_parse } from './epic-parse'
@@ -65,7 +65,6 @@ async function read_epic(epic_number: number): Promise<EpicReading | { error: st
65
65
  }
66
66
 
67
67
  const epic_read = {
68
- UNKNOWN_REPO,
69
68
  read_epic,
70
69
  }
71
70
 
@@ -1,4 +1,4 @@
1
- import { git_gh_command } from '#scripts/git/git-gh-command'
1
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
2
2
  import { epic_add_body } from './epic-add-body'
3
3
  import { epic_chains } from './epic-chains'
4
4
  import { epic_graph, type EpicChild, type IssueReference } from './epic-graph'
@@ -54,20 +54,8 @@ function format_replaced_relations(links: ReadonlyArray<DependencyLink>): string
54
54
  return `Replaced blocked-by: ${join_references(quoted)}.`
55
55
  }
56
56
 
57
- const epic_reference = {
58
- DEPENDENCY_ARROW,
59
- REFERENCE_SEPARATOR,
60
- to_issue_reference,
61
- join_references,
62
- format_dependency_link,
63
- format_dependency_links,
64
- format_issue_references,
65
- format_replaced_relations,
66
- }
67
-
68
- export type { IssueReference } from '#scripts/git/issue-reference'
57
+ export type { IssueReference } from '#scripts/issue/issue-reference'
69
58
  export {
70
- epic_reference,
71
59
  DEPENDENCY_ARROW,
72
60
  REFERENCE_SEPARATOR,
73
61
  to_issue_reference,
@@ -103,7 +103,7 @@ async function recheck_missing_relations(
103
103
  )
104
104
  }
105
105
 
106
- const epic_relation_recheck = { recheck_missing_relations, suspect_children, RECHECK_LIMIT }
106
+ const epic_relation_recheck = { recheck_missing_relations, RECHECK_LIMIT }
107
107
 
108
108
  export type { BlockersReader }
109
109
  export { epic_relation_recheck }
@@ -1,4 +1,4 @@
1
- import { git_gh_command } from '#scripts/git/git-gh-command'
1
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
2
2
  import type { DependencyLink } from './epic-parse'
3
3
  import { format_dependency_links } from './epic-reference'
4
4
 
@@ -1,4 +1,4 @@
1
- import { git_gh_command } from '#scripts/git/git-gh-command'
1
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
2
2
  import { epic_decision } from './epic-decision'
3
3
  import { epic_read } from './epic-read'
4
4
  import { format_dependency_links } from './epic-reference'
@@ -193,7 +193,6 @@ const epic_report = {
193
193
  build_result,
194
194
  candidates_for_repo,
195
195
  format_result,
196
- VERDICT_LINES,
197
196
  }
198
197
 
199
198
  export type { EpicNextResult, EpicVerdict, RepoCandidates }
@@ -1,5 +1,5 @@
1
- import { git_gh_command } from '#scripts/git/git-gh-command'
2
- import { EPIC_LABEL } from '#scripts/git/issue-labels'
1
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
2
+ import { EPIC_LABEL } from '#scripts/issue/issue-labels'
3
3
  import { epic_body } from './epic-body'
4
4
  import { epic_promote } from './epic-promote'
5
5
  import { epic_relations } from './epic-relations'
@@ -213,7 +213,6 @@ async function check_epic(epic_number: number): Promise<number> {
213
213
  }
214
214
 
215
215
  const epic_run = {
216
- parse_created_number,
217
216
  create_epic,
218
217
  promote_epic,
219
218
  check_epic,
@@ -1,4 +1,4 @@
1
- import { EPIC_LABEL } from '#scripts/git/issue-labels'
1
+ import { EPIC_LABEL } from '#scripts/issue/issue-labels'
2
2
  import { epic_parse } from './epic-parse'
3
3
  import { to_issue_reference } from './epic-reference'
4
4
 
@@ -0,0 +1,107 @@
1
+ import { setTimeout as sleep } from 'node:timers/promises'
2
+ import type { OpenIssueData } from '#scripts/git/schemas'
3
+ import { has_any_label, IN_PROGRESS_LABEL, RUN_SOLO_LABEL } from '#scripts/issue/issue-labels'
4
+ import { lane_await } from '#scripts/lane/lane-await'
5
+ import { lane_registry } from '#scripts/lane/lane-registry'
6
+ import { issue_citation } from '#scripts/rules/issue-citation'
7
+ import type { BusyRead } from './epic-busy'
8
+
9
+ // A `run:solo` holder nothing is running for (joshuafolkken/kit#3017).
10
+ //
11
+ // A running `run:solo` issue lets nothing new start (`epic-solo.ts`), and "running" is read off the
12
+ // `in-progress` label alone. A label nothing removed — a child that parked itself, then had its
13
+ // `needs-decision` lifted by a person — therefore held the whole backlog: every candidate went to
14
+ // `waiting`, no lane was running to release it, and `backlog:offer` booked the run `exhausted`.
15
+ //
16
+ // **What decides it is the child's process, not its lane.** The lane of a child that parked itself
17
+ // stays open after the child has ended, so an open lane is no evidence of a run; the process pattern
18
+ // `lane:await` and `run:liveness` read is. A matching process keeps the holder, whatever its lane.
19
+ //
20
+ // **A missing process releases only what this machine dispatched, and only once re-confirmed.** The
21
+ // process read sees this machine alone, and a session a person started by hand matches no lane
22
+ // pattern, so an absent process is evidence of an ended run only for a holder with a lane here. A
23
+ // boundary cut drops the process for a moment before its resume takes over, so the absence is read
24
+ // again after `lane_await.RECONFIRM_MS` — the window `lane:await` measured for that handoff. Every
25
+ // doubt keeps the holder: a wrongly kept label waits, a wrongly released one runs beside a solo run.
26
+ //
27
+ // Only `run:solo` holders are released: theirs is the one label that blocks every candidate. A stale
28
+ // ordinary holder spends one lane, which `epic_busy.lanes_full_message` already names.
29
+
30
+ const SOLO_LABELS: ReadonlySet<string> = new Set([RUN_SOLO_LABEL])
31
+
32
+ interface StaleRelease {
33
+ read: BusyRead
34
+ notice?: string
35
+ }
36
+
37
+ function is_absent_solo(issue: OpenIssueData): boolean {
38
+ if (!has_any_label(issue.labels, SOLO_LABELS)) return false
39
+
40
+ return !lane_await.is_process_running_default(String(issue.number))
41
+ }
42
+
43
+ // The issue numbers of this checkout's lanes. A listing that cannot be read names none, which keeps
44
+ // every holder.
45
+ async function local_lanes(): Promise<ReadonlySet<string>> {
46
+ try {
47
+ const lanes = await lane_registry.list_lanes()
48
+
49
+ return new Set(lanes.map((lane) => lane.issue))
50
+ } catch {
51
+ return new Set()
52
+ }
53
+ }
54
+
55
+ async function settle(): Promise<void> {
56
+ await sleep(lane_await.RECONFIRM_MS)
57
+ }
58
+
59
+ // Read through this object so a test can stand in for the git listing and the re-confirm wait.
60
+ const io = { local_lanes, settle }
61
+
62
+ async function find_stale(
63
+ issues: ReadonlyArray<OpenIssueData>,
64
+ ): Promise<ReadonlyArray<OpenIssueData>> {
65
+ const absent = issues.filter((issue) => is_absent_solo(issue))
66
+
67
+ if (absent.length === 0) return []
68
+
69
+ const lanes = await io.local_lanes()
70
+ const dispatched = absent.filter((issue) => lanes.has(String(issue.number)))
71
+
72
+ if (dispatched.length === 0) return []
73
+
74
+ await io.settle()
75
+
76
+ return dispatched.filter((issue) => is_absent_solo(issue))
77
+ }
78
+
79
+ function stale_message(stale: ReadonlyArray<OpenIssueData>, repo: string): string {
80
+ const named = stale.map((issue) => `#${String(issue.number)}`).join(', ')
81
+
82
+ return issue_citation.linkify(
83
+ `${named} carries \`${RUN_SOLO_LABEL}\` and \`${IN_PROGRESS_LABEL}\`, but no process of it is running in ${repo} — the \`${IN_PROGRESS_LABEL}\` label is stale, so it does not hold the backlog. Remove the label once you have confirmed nothing is running it.`,
84
+ repo,
85
+ )
86
+ }
87
+
88
+ // The read with every stale `run:solo` holder taken out, and the sentence naming them. A read with no
89
+ // holder left is `idle`, exactly as `epic_busy.parse_listing` answers an empty listing.
90
+ async function release(read: BusyRead, repo: string): Promise<StaleRelease> {
91
+ if (read.kind !== 'busy') return { read }
92
+
93
+ const stale = await find_stale(read.issues)
94
+
95
+ if (stale.length === 0) return { read }
96
+
97
+ const holders = read.issues.filter((issue) => !stale.includes(issue))
98
+ const released: BusyRead =
99
+ holders.length > 0 ? { kind: 'busy', issues: holders } : { kind: 'idle' }
100
+
101
+ return { read: released, notice: stale_message(stale, repo) }
102
+ }
103
+
104
+ const epic_solo_stale = { io, release }
105
+
106
+ export type { StaleRelease }
107
+ export { epic_solo_stale }
@@ -1,4 +1,4 @@
1
- import { has_any_label, has_label_name, RUN_SOLO_LABEL } from '#scripts/git/issue-labels'
1
+ import { has_any_label, has_label_name, RUN_SOLO_LABEL } from '#scripts/issue/issue-labels'
2
2
  import { issue_citation } from '#scripts/rules/issue-citation'
3
3
  import { epic_busy, type BusyRead } from './epic-busy'
4
4
  import type { EpicChild } from './epic-graph'
@@ -15,7 +15,8 @@ import { epic_report, type EpicNextResult } from './epic-report'
15
15
  // `epic:next --lanes` (a named epic's children, `epic-lane-offer.ts`). One rule, so the two cannot
16
16
  // disagree about what may start beside what.
17
17
  //
18
- // Three rules, all against the repository's `in-progress` holders (`epic_busy.read_repository`):
18
+ // Three rules, all against the repository's `in-progress` holders (`epic_busy.read_repository`, with
19
+ // a stale `run:solo` holder taken out by `epic_solo_stale.release` — joshuafolkken/kit#3017):
19
20
  // a running `run:solo` issue lets nothing new start; a `run:solo` candidate starts only into an idle
20
21
  // repository and then alone; and nothing ranked below a waiting `run:solo` candidate is offered past
21
22
  // it, so it is not starved by the lanes it is waiting on. A listing that could not be read is not an
@@ -1,4 +1,4 @@
1
- import { has_label_name, RUN_LANE_LABEL, RUN_SOLO_LABEL } from '#scripts/git/issue-labels'
1
+ import { has_label_name, RUN_LANE_LABEL, RUN_SOLO_LABEL } from '#scripts/issue/issue-labels'
2
2
  import { issue_citation } from '#scripts/rules/issue-citation'
3
3
  import type { EpicChild } from './epic-graph'
4
4
 
@@ -1,6 +1,6 @@
1
- import { EPIC_LABEL, has_label_name } from '#scripts/git/issue-labels'
2
1
  import { parse_json_object_safe } from '#scripts/git/parse-json-array'
3
2
  import { epic_subject_schema } from '#scripts/git/schemas'
3
+ import { EPIC_LABEL, has_label_name } from '#scripts/issue/issue-labels'
4
4
  import { epic_parse, type DeclarationState } from './epic-parse'
5
5
 
6
6
  // The four requirements an epic has to satisfy are otherwise enforced only by an agent reading the
@@ -9,7 +9,7 @@
9
9
  * [--decision-file <path|->]
10
10
  * tsx scripts/epic/epic.ts --remove <E> <M> <N> ... [--decision-file <path|->]
11
11
  */
12
- import { git_gh_command } from '#scripts/git/git-gh-command'
12
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
13
13
  import { error_text } from '#scripts/lib/error-message'
14
14
  import { epic_add, type AddChildrenInput } from './epic-add'
15
15
  import { epic_cli, type AddArguments, type CrossRepoAddTarget } from './epic-cli'
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env tsx
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { doctor_consumer } from '#scripts/doctor/doctor-consumer'
5
+ import { project_checks } from '#scripts/gate/project-checks'
6
+ import type { NamespaceMember } from './namespace-members'
7
+
8
+ const CHECK = 'exports'
9
+ const FAIL_EXIT_CODE = 1
10
+ // A consumer's namespaces are read from `.svelte` files and routes a TypeScript program over its
11
+ // `tsconfig.json` does not see, so every member they use would read as unused. The check is kit's own.
12
+ const CONSUMER_SKIP_REASON = 'checks only the kit repository itself, not a consumer project'
13
+
14
+ function format_member(root: string, member: NamespaceMember): string {
15
+ return `${path.relative(root, member.file)}:${String(member.line)} ${member.namespace}.${member.member}`
16
+ }
17
+
18
+ function report(root: string, members: ReadonlyArray<NamespaceMember>): string {
19
+ if (members.length === 0) return `josh ${CHECK}: no unused namespace member`
20
+
21
+ return [
22
+ `josh ${CHECK}: ${String(members.length)} namespace member(s) nothing reads — remove each from its namespace object (and the declaration, if nothing in its file uses it either):`,
23
+ ...members.map((member) => ` ${format_member(root, member)}`),
24
+ ].join('\n')
25
+ }
26
+
27
+ async function run_unused_members(root: string): Promise<number> {
28
+ if (doctor_consumer.is_kit_consumer(root)) {
29
+ console.info(project_checks.skip_notice(CHECK, CONSUMER_SKIP_REASON))
30
+
31
+ return 0
32
+ }
33
+
34
+ // Loaded only past the skip: the scanner pulls in `typescript`, an optional peer a basic-profile
35
+ // consumer may not have, and this check runs in every consumer's gate. For the same reason the
36
+ // scanner is left out of the published package (`package.json` `files`); only this entry ships.
37
+ const { unused_members } = await import('./unused-members')
38
+ const members = unused_members.find_unused_members(root)
39
+ const output = report(root, members)
40
+
41
+ if (members.length === 0) console.info(output)
42
+ else console.error(output)
43
+
44
+ return members.length === 0 ? 0 : FAIL_EXIT_CODE
45
+ }
46
+
47
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
48
+ process.exitCode = await run_unused_members(project_checks.project_root(process.cwd()))
49
+ }
50
+
51
+ const unused_members_cli = { run_unused_members }
52
+
53
+ export { unused_members_cli }
@@ -23,9 +23,4 @@ function parse_completed_issue_number(raw: string | undefined): number | undefin
23
23
  return digits === undefined ? undefined : Number(digits)
24
24
  }
25
25
 
26
- const followup_issue_number = {
27
- parse_issue_number_from_text,
28
- parse_completed_issue_number,
29
- }
30
-
31
- export { followup_issue_number, parse_issue_number_from_text, parse_completed_issue_number }
26
+ export { parse_issue_number_from_text, parse_completed_issue_number }
@@ -1,4 +1,4 @@
1
- import { git_gh_helpers } from './git-gh-helpers'
1
+ import { git_gh_helpers } from '#scripts/gh/git-gh-helpers'
2
2
 
3
3
  // joshuafolkken/kit#1539: everything a run does after its merge is cleanup, and a merge cannot be
4
4
  // taken back. Those steps used to be plain sequential statements, so whichever of them threw first
@@ -70,6 +70,7 @@ async function run_guarded_steps(
70
70
  let is_complete = true
71
71
 
72
72
  for (const step of steps) {
73
+ // eslint-disable-next-line no-await-in-loop -- cleanup steps run in their declared order
73
74
  const is_done = await run_guarded_step(is_guarded, step)
74
75
 
75
76
  if (!is_done) is_complete = false
@@ -1,3 +1,4 @@
1
+ import { git_next_issues } from '#scripts/issue/git-next-issues'
1
2
  import { review_attest } from '#scripts/review/review-attest'
2
3
  import { review_stamps } from '#scripts/review/review-stamps'
3
4
  import { run_hold } from '#scripts/run/run-hold'
@@ -5,7 +6,6 @@ import { run_progress_clock } from '#scripts/run/run-progress-clock'
5
6
  import { parse_completed_issue_number } from './followup-issue-number'
6
7
  import { git_followup_cleanup, type CleanupStep } from './git-followup-cleanup'
7
8
  import { git_followup_pending } from './git-followup-pending'
8
- import { git_next_issues } from './git-next-issues'
9
9
 
10
10
  // **The count of unreleased merges, not the project version** (joshuafolkken/kit#1486). This line
11
11
  // used to read the local `package.json` and was read as "the version this run just shipped"; children
@@ -208,8 +208,6 @@ const git_followup_finish = {
208
208
  print_next_issues,
209
209
  print_pending_release,
210
210
  clear_review_records,
211
- clear_round_one_snapshot,
212
- clear_review_target,
213
211
  release_worktree_hold,
214
212
  end_progress_watcher,
215
213
  }
@@ -1,8 +1,8 @@
1
+ import { git_command } from '#scripts/git/git-command'
2
+ import { git_spawn } from '#scripts/git/git-spawn'
1
3
  import { observation_ledger } from '#scripts/observations/observation-ledger'
2
4
  import { observation_ledger_prepare } from '#scripts/observations/observation-ledger-prepare'
3
5
  import { observations_flush } from '#scripts/observations/observations-flush'
4
- import { git_command } from './git-command'
5
- import { git_spawn } from './git-spawn'
6
6
 
7
7
  // The residual commit path for the observation ledger (joshuafolkken/kit#1810). A run's appended lines
8
8
  // ride its own commit (joshuafolkken/kit#2763), so the ledger is usually clean by now and this
@@ -1,8 +1,8 @@
1
1
  import { epic_parse } from '#scripts/epic/epic-parse'
2
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
3
+ import { parse_json_object_safe } from '#scripts/git/parse-json-array'
2
4
  import { poll, type PollOptions } from '#scripts/lib/poll'
3
5
  import { z } from 'zod'
4
- import { git_gh_command } from './git-gh-command'
5
- import { parse_json_object_safe } from './parse-json-array'
6
6
 
7
7
  // **A merged pull request whose `closes #N` GitHub did not apply** (joshuafolkken/kit#2770). From
8
8
  // 2026-09-30 06:34Z every merge `followup` made left its issue open — seven in a row, where every
@@ -1,8 +1,8 @@
1
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
2
+ import { parse_json_object_safe } from '#scripts/git/parse-json-array'
3
+ import { issue_label_schema } from '#scripts/git/schemas'
4
+ import { IN_PROGRESS_LABEL, label_name_of } from '#scripts/issue/issue-labels'
1
5
  import { z } from 'zod'
2
- import { git_gh_command } from './git-gh-command'
3
- import { IN_PROGRESS_LABEL, label_name_of } from './issue-labels'
4
- import { parse_json_object_safe } from './parse-json-array'
5
- import { issue_label_schema } from './schemas'
6
6
 
7
7
  // **`in-progress` means "a run is holding this issue right now", and a merged run is not holding
8
8
  // anything** (joshuafolkken/kit#1794). `pnpm josh followup` merged the pull request and closed the
@@ -0,0 +1,112 @@
1
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
2
+ import type { PullMergeState } from '#scripts/gh/git-gh-pr-read'
3
+ import { json_value } from '#scripts/lib/json-value'
4
+ import { z } from 'zod'
5
+
6
+ // joshuafolkken/kit#3023: `prrun` stops at a green, mergeable pull request and a person merges it by
7
+ // hand. Re-running `pnpm josh followup` then has to finish what a merged `fullrun` finishes — the
8
+ // completion report, the epic close, the merged issue's close, the `in-progress` removal, the hold
9
+ // release and the completion Telegram — **from the one tail `followup` already runs**, not a copy of
10
+ // it elsewhere: the hold release and `josh ms` act on the local work tree, which CI never sees.
11
+ //
12
+ // **A merged pull request is read, not declared.** No flag says "a person merged this"; `followup`
13
+ // asks the pull request, so the same command works whichever of the two merged it. The merge plan
14
+ // below is what the rest of the run branches on: merged skips every pre-merge step — the review and
15
+ // live-evidence gates, the CI wait, the AI-review scans and the merge call — and runs the tail as a
16
+ // merged run's, even under `--no-merge`, because there is nothing left for that flag to hold back.
17
+ //
18
+ // **A second run must not repeat the report.** Every other tail step is idempotent by nature (a
19
+ // closed epic is not closed twice, a released hold releases nothing), but the completion comment and
20
+ // the completion Telegram are writes that would land again. The comment is the record that the tail
21
+ // already ran: a `✅` report naming this pull request, **posted on the issue at or after the merge**.
22
+ // The time is what tells it apart from the report the `prrun` stop itself posted — a `--no-merge`
23
+ // run's tail posts the same `✅` report naming the same pull request before anything merged, and
24
+ // reading that one as the merged run's would skip the only report that says it merged. A report the
25
+ // tail wrote into a blank issue body carries no time, so it is not counted: a re-run then posts it
26
+ // once more as a comment, the cheaper failure than a merged run that reported nothing.
27
+
28
+ interface MergePlan {
29
+ is_merged: boolean
30
+ should_merge: boolean
31
+ merged_at: string | undefined
32
+ }
33
+
34
+ const COMPLETION_MARK = '✅ '
35
+ const PR_LINE_PREFIX = 'PR: '
36
+ const LINE_SEPARATOR = '\n'
37
+
38
+ const COMMENTS_SCHEMA = z.array(
39
+ z.object({ body: z.string().nullish(), created_at: z.string().nullish() }),
40
+ )
41
+
42
+ type IssueComment = z.infer<typeof COMMENTS_SCHEMA>[number]
43
+
44
+ function to_merge_plan(state: PullMergeState | undefined, is_merge_requested: boolean): MergePlan {
45
+ const is_merged = state?.is_merged === true
46
+
47
+ return { is_merged, should_merge: is_merged || is_merge_requested, merged_at: state?.merged_at }
48
+ }
49
+
50
+ async function read_merge_plan(
51
+ branch_name: string,
52
+ is_merge_requested: boolean,
53
+ ): Promise<MergePlan> {
54
+ return to_merge_plan(await git_gh_command.pr_get_merge_state(branch_name), is_merge_requested)
55
+ }
56
+
57
+ function is_completion_text(text: string | null | undefined, pr_url: string): boolean {
58
+ if (typeof text !== 'string' || !text.startsWith(COMPLETION_MARK)) return false
59
+
60
+ return text.split(LINE_SEPARATOR).includes(`${PR_LINE_PREFIX}${pr_url}`)
61
+ }
62
+
63
+ function is_posted_since(comment: IssueComment, merged_at: string): boolean {
64
+ if (typeof comment.created_at !== 'string') return false
65
+
66
+ return Date.parse(comment.created_at) >= Date.parse(merged_at)
67
+ }
68
+
69
+ function parse_comments(comments_json: string | undefined): Array<IssueComment> {
70
+ const parsed = COMMENTS_SCHEMA.safeParse(json_value.parse_or_undefined(comments_json ?? ''))
71
+
72
+ return parsed.success ? parsed.data : []
73
+ }
74
+
75
+ // The pure half: whether one of the issue's comments is this pull request's report, posted since it merged.
76
+ function has_completion_record(input: {
77
+ comments_json: string | undefined
78
+ pr_url: string
79
+ merged_at: string
80
+ }): boolean {
81
+ return parse_comments(input.comments_json).some(
82
+ (comment) =>
83
+ is_completion_text(comment.body, input.pr_url) && is_posted_since(comment, input.merged_at),
84
+ )
85
+ }
86
+
87
+ // Asked only on a merged pull request: an unmerged run has never reached the tail, so its report
88
+ // cannot be there yet. **An unreadable issue reads as "not recorded"** — the tail then posts, and a
89
+ // duplicate report is the cheaper failure than a merged run that reported nothing.
90
+ async function is_completion_recorded(input: {
91
+ merged_at: string | undefined
92
+ issue_number: string | undefined
93
+ pr_url: string | undefined
94
+ }): Promise<boolean> {
95
+ const { merged_at, issue_number, pr_url } = input
96
+
97
+ if (issue_number === undefined || pr_url === undefined || merged_at === undefined) return false
98
+
99
+ const comments_json = await git_gh_command.issue_list_comments(issue_number)
100
+
101
+ return has_completion_record({ comments_json, pr_url, merged_at })
102
+ }
103
+
104
+ const git_followup_merged = {
105
+ has_completion_record,
106
+ is_completion_recorded,
107
+ read_merge_plan,
108
+ to_merge_plan,
109
+ }
110
+
111
+ export type { MergePlan }
112
+ export { git_followup_merged }
@@ -1,7 +1,7 @@
1
+ import { git_command } from '#scripts/git/git-command'
1
2
  import { release_history, type HistoryReader } from '#scripts/release/release-history'
2
3
  import { release_plan } from '#scripts/release/release-plan'
3
4
  import { version_targets } from '#scripts/version/version-targets'
4
- import { git_command } from './git-command'
5
5
 
6
6
  // What the completion notification says about versions, now that children do not set one
7
7
  // (joshuafolkken/kit#1486).
@@ -1,16 +1,17 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { parseArgs } from 'node:util'
3
+ import { git_branch } from '#scripts/git/git-branch'
4
+ import { git_error } from '#scripts/git/git-error'
3
5
  import { cli_body } from '#scripts/josh/cli-body'
4
6
  import { josh_environment_file } from '#scripts/josh/josh-environment-file'
7
+ import { git_notify, type GitNotifyConfig } from '#scripts/notify/git-notify'
5
8
  import { live_evidence } from '#scripts/review/live-evidence'
6
9
  import { review_attest } from '#scripts/review/review-attest'
7
10
  import { review_record } from '#scripts/review/review-record'
8
11
  import { parse_issue_number_from_text } from './followup-issue-number'
9
- import { git_branch } from './git-branch'
10
- import { git_error } from './git-error'
11
12
  import { git_followup_finish } from './git-followup-finish'
12
13
  import { git_followup_flush } from './git-followup-flush'
13
- import { git_notify, type GitNotifyConfig } from './git-notify'
14
+ import { git_followup_merged, type MergePlan } from './git-followup-merged'
14
15
  import { git_pr_followup } from './git-pr-followup'
15
16
 
16
17
  josh_environment_file.load_environment_file()
@@ -204,14 +205,18 @@ async function assert_merge_gates(
204
205
  }
205
206
 
206
207
  // After the gates, before the CI wait: a line appended since the run's commit rides the pull request
207
- // itself, so it merges with it — a lane's included (joshuafolkken/kit#2919).
208
+ // itself, so it merges with it — a lane's included (joshuafolkken/kit#2919). **A pull request that has
209
+ // already merged skips both** (joshuafolkken/kit#3023): the gates guard a merge this run no longer
210
+ // makes, and a ledger commit would land on a branch that has already gone in.
208
211
  async function prepare_merge(
209
- should_merge: boolean,
212
+ plan: MergePlan,
210
213
  issue_number: string | undefined,
211
214
  branch_name: string,
212
215
  ): Promise<void> {
213
- await assert_merge_gates(should_merge, issue_number, branch_name)
214
- await git_followup_flush.commit_ledger_step(should_merge, issue_number)
216
+ if (plan.is_merged) return
217
+
218
+ await assert_merge_gates(plan.should_merge, issue_number, branch_name)
219
+ await git_followup_flush.commit_ledger_step(plan.should_merge, issue_number)
215
220
  }
216
221
 
217
222
  async function main(): Promise<void> {
@@ -225,10 +230,10 @@ async function main(): Promise<void> {
225
230
 
226
231
  const issue_number =
227
232
  cli.values['issue-number'] ?? parse_issue_number_from_text(cli.positionals[0] ?? undefined)
228
- const should_merge = is_merge_resolved(cli.values)
229
233
  const branch_name = await resolve_branch_name(cli.values.branch)
234
+ const plan = await git_followup_merged.read_merge_plan(branch_name, is_merge_resolved(cli.values))
230
235
 
231
- await prepare_merge(should_merge, issue_number, branch_name)
236
+ await prepare_merge(plan, issue_number, branch_name)
232
237
  // **The number the run reports on is the one it used**, which is the number the pull request
233
238
  // closes where the invocation named none (joshuafolkken/kit#1539). Recovered inside `run`, so the
234
239
  // tail records a run the command line could not identify rather than silently skipping it.
@@ -239,10 +244,12 @@ async function main(): Promise<void> {
239
244
  coderabbit_ignore_reason: cli.values['coderabbit-ignore-reason'],
240
245
  ai_review_ignore_reason: cli.values['ai-review-ignore-reason'],
241
246
  is_skip_watch: cli.values['skip-watch'] === true,
242
- should_merge,
247
+ should_merge: plan.should_merge,
248
+ is_merged: plan.is_merged,
249
+ merged_at: plan.merged_at,
243
250
  })
244
251
 
245
- await git_followup_finish.finish(used_issue_number ?? issue_number, should_merge)
252
+ await git_followup_finish.finish(used_issue_number ?? issue_number, plan.should_merge)
246
253
  }
247
254
 
248
255
  try {
@@ -257,6 +264,7 @@ const git_followup_workflow = {
257
264
  assert_review_attested,
258
265
  assert_review_recorded,
259
266
  parse_issue_number_from_text,
267
+ prepare_merge,
260
268
  resolve_branch_name,
261
269
  is_merge_resolved,
262
270
  }
@@ -1,10 +1,10 @@
1
1
  import { epic_close } from '#scripts/epic/epic-close'
2
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
3
+ import { git_notify, type GitNotifyConfig } from '#scripts/notify/git-notify'
2
4
  import { git_followup_cleanup } from './git-followup-cleanup'
3
5
  import { git_followup_issue_close } from './git-followup-issue-close'
4
6
  import { git_followup_label } from './git-followup-label'
5
7
  import { git_followup_stages, type StageLog } from './git-followup-stages'
6
- import { git_gh_command } from './git-gh-command'
7
- import { git_notify, type GitNotifyConfig } from './git-notify'
8
8
 
9
9
  const { STAGE, lap } = git_followup_stages
10
10
 
@@ -35,6 +35,10 @@ interface WrapupInput {
35
35
  // completion notification (joshuafolkken/kit#1592). Empty on a run that changed nothing `josh sync`
36
36
  // distributes, and an empty one adds no line.
37
37
  managed_notes: ReadonlyArray<string>
38
+ // A pull request merged before this run is not merged again, and a tail whose completion report is
39
+ // already on the issue does not post it twice (joshuafolkken/kit#3023, `git-followup-merged.ts`).
40
+ is_merged?: boolean | undefined
41
+ is_completion_recorded?: boolean | undefined
38
42
  }
39
43
 
40
44
  function build_notify_body(input: {
@@ -113,6 +117,8 @@ async function post_completion_notification(input: {
113
117
  // merge, a step that throws is reported and the ones after it still run, rather than taking the epic
114
118
  // close and the working-tree hold release down with it.
115
119
  async function notify_step(input: WrapupInput): Promise<void> {
120
+ if (input.is_completion_recorded === true) return
121
+
116
122
  await git_followup_cleanup.run_guarded_step(input.should_merge, {
117
123
  label: 'The completion comment',
118
124
  recovery: COMPLETION_COMMENT_RECOVERY,
@@ -227,7 +233,7 @@ async function run_tail_steps(input: WrapupInput): Promise<void> {
227
233
  }
228
234
 
229
235
  async function run_wrapup(input: WrapupInput, log: StageLog): Promise<void> {
230
- if (input.should_merge) {
236
+ if (input.should_merge && input.is_merged !== true) {
231
237
  await git_gh_command.pr_merge(input.branch_name)
232
238
  lap(log, STAGE.merge)
233
239
  }