@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
@@ -28,7 +28,7 @@ import { run_progress_read, type ObservationRead } from './run-progress-read'
28
28
  // `backlogrun` parent — `ready #N · free lanes N` — which is itself something the parent acts on.
29
29
  //
30
30
  // **It cannot send a Telegram.** That is structural rather than a promise — nothing here imports
31
- // `scripts/git/telegram-notify`, which is the only egress there is. A heartbeat every twenty minutes on
31
+ // `scripts/notify/telegram-notify`, which is the only egress there is. A heartbeat every twenty minutes on
32
32
  // a phone is notification fatigue, and it would cheapen the `confirmation` and `completion` messages
33
33
  // that do need to interrupt someone.
34
34
 
@@ -51,7 +51,7 @@ const TICK_SECONDS = 30
51
51
  const DECLINE_RETRY_SECONDS = 120
52
52
  // The clock a fresh loop starts with: nothing has declined yet, so nothing is being waited out.
53
53
  const NO_RETRY = 0
54
- const { DEFAULT_MAX_HOURS } = run_progress_args
54
+ const { DEFAULT_WAIT_MAX_HOURS } = run_progress_args
55
55
  const ENVIRONMENT_KEY = 'JOSH_PROGRESS'
56
56
  const DISABLED_VALUE = '0'
57
57
 
@@ -74,7 +74,7 @@ const UNREADABLE_NOTICE =
74
74
  'The `in-progress` listing could not be read, so nothing is reported. That is not "nothing is running" — check `gh auth status` and ask again.'
75
75
  const FAILED_TICK_PREFIX =
76
76
  'A progress reading failed, so nothing is reported for it. The watcher is still running, and will read again after the cooldown:'
77
- const WAIT_EXPIRED_NOTICE = `The watch bound (\`--hours\`, ${String(DEFAULT_MAX_HOURS)} by default) ran out before the run had been quiet for a whole interval, so there is nothing to report. Starting another \`--wait\` resumes the same clock.`
77
+ const WAIT_EXPIRED_NOTICE = `The watch bound (\`--hours\`, ${String(DEFAULT_WAIT_MAX_HOURS)} by default for \`--wait\`) ran out with no newly runnable work arriving. Every report it made is already on the run's event stream (\`pnpm josh run:event --watch\`), so there is nothing to relay. Starting another \`--wait\` resumes the same clock.`
78
78
  // The repository name is only ever printed, never written against, so the bounded lookup is the right
79
79
  // one: a `gh` call that hangs would otherwise block the synchronous read at startup and leave the
80
80
  // watcher neither running nor saying so.
@@ -104,6 +104,10 @@ interface WatchOptions {
104
104
  output_paths: ReadonlyArray<string>
105
105
  repo: string
106
106
  tick_ms: number
107
+ // `--wait` alone: the line goes to the run's event stream and the ambient log, never to standard
108
+ // output, because a background command's output reaches the session only as a wake
109
+ // (joshuafolkken/kit#3102).
110
+ is_stream_only?: boolean
107
111
  }
108
112
 
109
113
  interface EmitContext {
@@ -202,7 +206,7 @@ async function emit(options: WatchOptions, context: EmitContext): Promise<EmitRe
202
206
  unchanged_since_ms: state.unchanged_since_ms,
203
207
  })
204
208
 
205
- console.info(line)
209
+ if (options.is_stream_only !== true) console.info(line)
206
210
 
207
211
  return { kind: 'observed', state, line }
208
212
  }
@@ -287,8 +291,8 @@ async function step(options: WatchOptions, target: string, loop: WatchLoop): Pro
287
291
  /**
288
292
  * The loop both reporting forms are, with one difference between them.
289
293
  *
290
- * `watch` runs until `--hours` expires; `--wait` stops at the first line it prints
291
- * (joshuafolkken/kit#1576). Everything else — where the clock is seeded from, the tick, the decline
294
+ * `watch` runs until `--hours` expires; `--wait` stops at the first arrival and keeps its lines off
295
+ * standard output (joshuafolkken/kit#3102). Everything else — where the clock is seeded from, the tick, the decline
292
296
  * cooldown `step` keeps — is one implementation on purpose: two copies would let a fix to the bound or
293
297
  * to the initial silence reach one form and not the other, and `--wait` is the form a run starts.
294
298
  */
@@ -301,17 +305,14 @@ function seed_loop(target: string, started_ms: number): WatchLoop {
301
305
  // Why the loop stopped, so `drive` prints the bound-expired notice only when the bound is what
302
306
  // expired — not when `josh followup` ended the watcher, which is a clean stop with nothing to report.
303
307
  // `arrived` is a `--wait` ended early by newly runnable work (joshuafolkken/kit#2503).
304
- type WatchExit = 'reported' | 'arrived' | 'ended' | 'bound'
308
+ type WatchExit = 'arrived' | 'ended' | 'bound'
305
309
 
306
- // `wait` is present for `--wait` alone, which ends at the first line it prints or at the first arrival
307
- // its probe sees. `state` is set by the one branch of `step` that printed a line and by no other, so
308
- // this asks "was anything reported" without keeping a second copy of the loop's own bookkeeping.
309
- async function wait_exit(
310
- loop: WatchLoop,
311
- wait: ArrivalProbe | undefined,
312
- ): Promise<WatchExit | undefined> {
310
+ // `wait` is present for `--wait` alone, which ends at the first arrival its probe sees. **A report is
311
+ // not an exit** (joshuafolkken/kit#3102): every exit wakes the session that started it, and a wake
312
+ // spent relaying a line the event stream already carries is the most expensive turn a run takes, so
313
+ // only what the parent has to act on ends the wait.
314
+ async function wait_exit(wait: ArrivalProbe | undefined): Promise<WatchExit | undefined> {
313
315
  if (wait === undefined) return undefined
314
- if (loop.state !== undefined) return 'reported'
315
316
 
316
317
  return (await wait.has_arrived(Date.now())) ? 'arrived' : undefined
317
318
  }
@@ -335,8 +336,8 @@ function final_exit(life: string): WatchExit {
335
336
  // The loop both reporting forms share, reading its own liveness record at the top of each pass — the
336
337
  // `run-wake-loop.ts` `run_loop` shape (joshuafolkken/kit#1727). A record gone means `josh followup`
337
338
  // removed it at the merge, so the watcher stops at once instead of waiting out its bound
338
- // (joshuafolkken/kit#1821); `--wait` also stops at the first line it prints, and otherwise the loop
339
- // runs until `--hours` expires.
339
+ // (joshuafolkken/kit#1821); `--wait` also stops at the first arrival, and otherwise the loop runs
340
+ // until `--hours` expires.
340
341
  async function run_ticks(
341
342
  options: WatchOptions,
342
343
  target: string,
@@ -347,11 +348,14 @@ async function run_ticks(
347
348
  let loop = seed_loop(target, started_ms)
348
349
 
349
350
  while (Date.now() - started_ms < options.max_ms && !run_progress_clock.is_life_ended(life)) {
351
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
350
352
  await sleep(options.tick_ms)
351
353
  run_progress_clock.ping_life(life)
354
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
352
355
  loop = await step(options, target, loop)
353
356
 
354
- const exit = await wait_exit(loop, wait)
357
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
358
+ const exit = await wait_exit(wait)
355
359
 
356
360
  if (exit !== undefined) return exit
357
361
  }
@@ -394,14 +398,16 @@ async function once(options: WatchOptions): Promise<number> {
394
398
  }
395
399
 
396
400
  /**
397
- * One interval of silence, one line, and then exit — the form that reports where a long-running
398
- * command cannot (joshuafolkken/kit#1576).
401
+ * The form a run starts in the background: it reports on its own and wakes the caller only for work
402
+ * (joshuafolkken/kit#3102).
399
403
  *
400
- * **The exit is the whole point.** A harness that only delivers a background command's standard
401
- * output *when that command exits* — Claude Code is one — relays nothing at all from `watch`, which
402
- * by design never exits: `epicrun #1474` was measured at 2h36m without a single progress line while
403
- * children were in flight. This form waits the same clock out and then ends, so the line it printed
404
- * is delivered; the caller relays it and starts the next one.
404
+ * **A report is not a wake.** It once ended at its first line so a harness that delivers a background
405
+ * command's output only on exit would relay it (joshuafolkken/kit#1576) — which cost the orchestrating
406
+ * session one turn per heartbeat, re-reading a hundred thousand tokens of context to pass on a line it
407
+ * was forbidden to change. Every line already reaches the run's event stream as a `heartbeat` and the
408
+ * ambient log, which a person's `run:event --watch` pane reads with no model in between, so this form
409
+ * keeps its lines off standard output and exits only on an arrival, on `josh followup` ending its life
410
+ * record, or on its bound. The caller starts it once and restarts it only after one of those.
405
411
  *
406
412
  * **The clock is still this command's, not the caller's.** It is the watch loop itself, a tick at a
407
413
  * time, rather than a second reading of the same record — so a real report elsewhere pushes the next
@@ -416,10 +422,10 @@ async function once(options: WatchOptions): Promise<number> {
416
422
  * carried budget, no lane, and no `in-progress` child — so a genuinely idle repository keeps the loop
417
423
  * waiting instead of ending it; returning there would hand the caller an instant answer to restart,
418
424
  * and the documented restart makes that a poll rather than a heartbeat. Once a run has started the
419
- * loop reports and exits even before the first child carries `in-progress` (joshuafolkken/kit#1900).
425
+ * loop reports even before the first child carries `in-progress` (joshuafolkken/kit#1900).
420
426
  */
421
427
  async function wait_once(options: WatchOptions): Promise<number> {
422
- return await drive(options, true)
428
+ return await drive({ ...options, is_stream_only: true }, true)
423
429
  }
424
430
 
425
431
  async function mark_now(): Promise<number> {
@@ -445,7 +451,7 @@ function to_options(values: ParsedValues): WatchOptions | undefined {
445
451
 
446
452
  return {
447
453
  interval_ms: run_progress_args.to_interval_ms(values.interval),
448
- max_ms: run_progress_args.to_max_ms(values.hours),
454
+ max_ms: run_progress_args.to_max_ms(values.hours, values.wait === true),
449
455
  output_paths: values.output ?? [],
450
456
  repo,
451
457
  tick_ms: TICK_SECONDS * run_progress.MS_PER_SECOND,
@@ -491,27 +497,20 @@ async function main(argv: ReadonlyArray<string>): Promise<void> {
491
497
  }
492
498
 
493
499
  const run_progress_cli = {
494
- DECLINE_RETRY_SECONDS,
495
- DEFAULT_MAX_HOURS,
500
+ DEFAULT_WAIT_MAX_HOURS,
496
501
  DISABLED_NOTICE,
497
502
  FAILED_TICK_PREFIX,
498
503
  FRESH_LOOP,
499
504
  IDLE_NOTICE,
500
505
  LANE_CHILD_NOTICE,
501
- MARKED_NOTICE,
502
506
  REPO_UNRESOLVED_NOTICE,
503
- TICK_SECONDS,
504
507
  UNREADABLE_NOTICE,
505
508
  USAGE,
506
509
  WAIT_EXPIRED_NOTICE,
507
- main,
508
510
  read_arguments: run_progress_args.read_arguments,
509
- report_decline,
510
511
  run,
511
512
  step,
512
513
  to_interval_ms: run_progress_args.to_interval_ms,
513
- to_max_ms: run_progress_args.to_max_ms,
514
- to_options,
515
514
  wait_once,
516
515
  }
517
516
 
@@ -1,6 +1,6 @@
1
- import { git_utilities } from '#scripts/git/constants'
1
+ import { git_common_directory } from '#scripts/git/git-common-directory'
2
2
  import { stamp_file } from '#scripts/josh/stamp-file'
3
- import { execaSync } from 'execa'
3
+ import { error_text } from '#scripts/lib/error-message'
4
4
  import { z } from 'zod'
5
5
 
6
6
  // The report clock `josh run:progress` keeps, in the one form both of its readers can use
@@ -16,9 +16,10 @@ import { z } from 'zod'
16
16
  //
17
17
  // **The key is the work tree's own git directory**, the way `run_hold` keys its record: two lanes of
18
18
  // one repository are two runs, and one lane's report must not silence the other's clock. The
19
- // synchronous twin asks `git rev-parse --absolute-git-dir`, which is the first of the two lines
20
- // `git_command.git_directories` reads and the one the asynchronous path already takes — so the two
21
- // spellings cannot name different files.
19
+ // synchronous twin is `git_common_directory.own()`, which asks the same two lines
20
+ // `git_command.git_directories` reads and takes the first — so the two spellings cannot name different
21
+ // files. A git read that fails or times out answers `undefined`, which reads as "no clock here" and
22
+ // lets the call through.
22
23
 
23
24
  const PROGRESS_PREFIX = 'josh-run-progress-'
24
25
  // The watcher's own liveness record, kept apart from the report clock above: that one says *when* the
@@ -55,14 +56,6 @@ const MS_PER_MINUTE = 60_000
55
56
  const LOG_STALE_MINUTES = 90
56
57
  const LOG_STALE_MS = LOG_STALE_MINUTES * MS_PER_MINUTE
57
58
  const BLOCK_SEPARATOR = '\n\n'
58
- // The lookup only ever decides whether a rule speaks, so a git call that hangs must not hold the hook
59
- // that is holding the user's call. A timeout answers `undefined`, which reads as "no clock here" and
60
- // lets the call through — the direction every other failure in this path already takes.
61
- const GIT_TIMEOUT_MS = 5000
62
- // The first of the two lines `git_command.git_directories` reads, asked on its own because that is the
63
- // one the record is keyed on. Asking git rather than assuming a directory named `.git` is what makes a
64
- // linked work tree, a bare repository and a `--separate-git-dir` clone all answer correctly.
65
- const GIT_DIRECTORY_ARGUMENTS: ReadonlyArray<string> = ['rev-parse', '--absolute-git-dir']
66
59
 
67
60
  // `line` is the last heartbeat the watcher printed, kept verbatim beside the clock so a reader that
68
61
  // cannot see the watcher's transcript — a person checking a woken headless `backlogrun` through
@@ -240,7 +233,9 @@ function is_life_fresh(target: string, threshold_ms: number): boolean {
240
233
  const pinged_ms = parse_pinged_at(raw)
241
234
 
242
235
  return pinged_ms !== undefined && Date.now() - pinged_ms < threshold_ms
243
- } catch {
236
+ } catch (error) {
237
+ error_text.trace_swallowed('run_progress_clock.is_life_fresh', error)
238
+
244
239
  return false
245
240
  }
246
241
  }
@@ -259,25 +254,8 @@ function end_life(target: string): void {
259
254
  stamp_file.remove_stamp(target)
260
255
  }
261
256
 
262
- // execa runs the binary directly with an argument array and no `shell` option, so CLI args cannot
263
- // break out of a shell sandbox; the git command and args are internally controlled, never untrusted
264
- // input. tssecurity:S8705 is a false positive here.
265
- function git_directory_sync(): string | undefined {
266
- const git_binary = git_utilities.get_git_command_for_spawn()
267
- const result = execaSync(git_binary, GIT_DIRECTORY_ARGUMENTS, {
268
- reject: false,
269
- timeout: GIT_TIMEOUT_MS,
270
- }) // NOSONAR
271
-
272
- if (result.exitCode !== 0) return undefined
273
-
274
- const output = result.stdout.trim()
275
-
276
- return output === '' ? undefined : output
277
- }
278
-
279
257
  function stamp_target_sync(): string | undefined {
280
- const directory = git_directory_sync()
258
+ const directory = git_common_directory.own()
281
259
 
282
260
  return directory === undefined ? undefined : stamp_target_of(directory)
283
261
  }
@@ -299,8 +277,6 @@ const run_progress_clock = {
299
277
  LIFE_PREFIX,
300
278
  LOG_KEEP,
301
279
  LOG_STALE_MS,
302
- PROGRESS_PREFIX,
303
- append_line,
304
280
  begin_life,
305
281
  end_life,
306
282
  is_life_ended,
@@ -314,7 +290,6 @@ const run_progress_clock = {
314
290
  read_last_report,
315
291
  read_last_report_sync,
316
292
  stamp_target_of,
317
- stamp_target_sync,
318
293
  }
319
294
 
320
295
  export { run_progress_clock }
@@ -27,8 +27,6 @@ import { run_progress } from './run-progress'
27
27
  // watcher it guards kept the default, which is the one state this module exists to prevent.
28
28
 
29
29
  const PACKAGE_FILE = 'package.json'
30
- const CONFIG_FIELD = 'josh'
31
- const INTERVAL_FIELD = 'progress_interval_minutes'
32
30
 
33
31
  // Deep enough to cross a workspace package and its root, and bounded so a path that never reaches a
34
32
  // root — a broken mount, a mocked `dirname` — cannot spin.
@@ -108,8 +106,6 @@ function configured_interval_ms(directory: string = process.cwd()): number {
108
106
  }
109
107
 
110
108
  const run_progress_config = {
111
- CONFIG_FIELD,
112
- INTERVAL_FIELD,
113
109
  PACKAGE_FILE,
114
110
  configured_interval_ms,
115
111
  read_package_minutes,
@@ -28,7 +28,7 @@ import { run_progress_clock } from './run-progress-clock'
28
28
  // `run-progress-clock.ts`, because the trigger-delivered rule that refuses an early heartbeat reads
29
29
  // the same record from inside a `PreToolUse` hook and cannot await the git call this file makes
30
30
  // (joshuafolkken/kit#1570). What stays here is the asynchronous way of naming the work tree.
31
- const { PROGRESS_PREFIX, mark, parse_stamp, read_last_report } = run_progress_clock
31
+ const { mark, parse_stamp, read_last_report } = run_progress_clock
32
32
 
33
33
  const FIRST_LOAD_AVERAGE = 0
34
34
 
@@ -182,7 +182,6 @@ async function read_observations(request: ObservationRequest): Promise<Observati
182
182
  }
183
183
 
184
184
  const run_progress_read = {
185
- PROGRESS_PREFIX,
186
185
  has_run_started,
187
186
  live_target,
188
187
  log_target,
@@ -257,7 +257,7 @@ interface LineTiming {
257
257
  * this is that same move for one value.
258
258
  *
259
259
  * **It is a schedule rather than an observation, and the two conditions on it are said in prose**
260
- * (`docs/josh-commands.md` → `josh run:progress`): it holds only while the silence continues, and a
260
+ * (`docs/josh-commands-automation.md` → `josh run:progress`): it holds only while the silence continues, and a
261
261
  * real report arriving first restarts the clock through `--mark` and supersedes it. The line printing
262
262
  * it is itself a report, so the clock starts at this observation.
263
263
  */
@@ -367,7 +367,6 @@ const run_progress = {
367
367
  format_line,
368
368
  format_next_line,
369
369
  format_next_report,
370
- format_record,
371
370
  format_report,
372
371
  is_due,
373
372
  minutes_from,
@@ -0,0 +1,117 @@
1
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
2
+ import type { PullMergeState } from '#scripts/gh/git-gh-pr-read'
3
+ import { git_branch } from '#scripts/git/git-branch'
4
+ import { git_command } from '#scripts/git/git-command'
5
+ import { run_halfrun_resume, type StopMark } from './run-halfrun-resume'
6
+ import { run_hold, type HoldRead } from './run-hold'
7
+
8
+ // joshuafolkken/kit#3023: a `prrun` stops at a green, mergeable pull request and **keeps** its hold, so
9
+ // a person can look before anything merges. What comes next is one of three, and `run:entry` answers
10
+ // which as the resume token `fullrun #N` acts on — read, never inferred:
11
+ //
12
+ // - `prrun-merged` — the person merged it by hand. Only `followup`'s tail is left.
13
+ // - `prrun-merge` — the branch is where the stop left it, both here and on the pull request, and the
14
+ // tree is clean. What was verified is what would merge, so `followup` merges without the gate and
15
+ // the review again.
16
+ // - `prrun-gate` — the branch moved since the stop, or the tree is dirty: a person changed something,
17
+ // so the gate and the review run again before `followup`. **The pull request's own head is asked
18
+ // too**: a commit pushed on GitHub (a suggestion applied, "Update branch") moves only the remote,
19
+ // and `followup` merges the remote — an unread or moved one is never taken as untouched.
20
+ //
21
+ // **Only a marked record is adopted**, as for the `halfrun` stop (`run-halfrun-resume.ts`): the stop
22
+ // writes the commit it stopped on (`run:hold <N> --prrun-stop`), and a hold without it is a run still
23
+ // working, never one waiting for a person.
24
+
25
+ const PRRUN_MERGED = 'prrun-merged'
26
+ const PRRUN_MERGE = 'prrun-merge'
27
+ const PRRUN_GATE = 'prrun-gate'
28
+
29
+ type PrrunToken = typeof PRRUN_GATE | typeof PRRUN_MERGE | typeof PRRUN_MERGED
30
+
31
+ interface ResumeFacts {
32
+ is_merged: boolean
33
+ stop_head: string
34
+ head: string
35
+ // The commit the pull request's head is on, or `undefined` when it could not be read.
36
+ pr_head: string | undefined
37
+ is_dirty: boolean
38
+ }
39
+
40
+ function is_untouched(facts: ResumeFacts): boolean {
41
+ return facts.head === facts.stop_head && facts.pr_head === facts.stop_head && !facts.is_dirty
42
+ }
43
+
44
+ function decide(facts: ResumeFacts): PrrunToken {
45
+ if (facts.is_merged) return PRRUN_MERGED
46
+
47
+ return is_untouched(facts) ? PRRUN_MERGE : PRRUN_GATE
48
+ }
49
+
50
+ function stop_head_of(read: HoldRead, issue: string): string | undefined {
51
+ const hold = run_halfrun_resume.hold_of(read)
52
+
53
+ return hold?.issue === issue ? hold.prrun_stop_head : undefined
54
+ }
55
+
56
+ async function read_merge_state(): Promise<PullMergeState | undefined> {
57
+ return await git_gh_command.pr_get_merge_state(await git_branch.current())
58
+ }
59
+
60
+ async function read_facts(stop_head: string): Promise<ResumeFacts> {
61
+ const [state, head, is_dirty] = await Promise.all([
62
+ read_merge_state(),
63
+ git_command.head_commit(),
64
+ run_hold.is_tree_dirty(),
65
+ ])
66
+
67
+ return {
68
+ is_merged: state?.is_merged === true,
69
+ stop_head,
70
+ head,
71
+ pr_head: state?.head_sha,
72
+ is_dirty,
73
+ }
74
+ }
75
+
76
+ // Read-only, so `run:entry` can ask the session budget before it takes anything over. `undefined` is
77
+ // "no `prrun` stop for this issue here", and the ordinary claim decides the tree as before.
78
+ async function resume_token(issue: string): Promise<PrrunToken | undefined> {
79
+ const target = await run_halfrun_resume.hold_target()
80
+
81
+ if (target === undefined) return undefined
82
+
83
+ const stop_head = stop_head_of(run_hold.read_hold(target), issue)
84
+
85
+ return stop_head === undefined ? undefined : decide(await read_facts(stop_head))
86
+ }
87
+
88
+ async function adopt(issue: string): Promise<boolean> {
89
+ const target = await run_halfrun_resume.hold_target()
90
+
91
+ if (target === undefined || stop_head_of(run_hold.read_hold(target), issue) === undefined) {
92
+ return false
93
+ }
94
+
95
+ return run_halfrun_resume.take_over(target, issue)
96
+ }
97
+
98
+ // The `--prrun-stop` mark: the run's own record re-written with the commit the pull request is on.
99
+ async function mark_stop_at(target: string, issue: string): Promise<StopMark> {
100
+ const head = await git_command.head_commit()
101
+
102
+ return run_halfrun_resume.mark_stop_at(target, issue, { prrun_stop_head: head })
103
+ }
104
+
105
+ const run_prrun_resume = {
106
+ PRRUN_GATE,
107
+ PRRUN_MERGE,
108
+ PRRUN_MERGED,
109
+ adopt,
110
+ decide,
111
+ mark_stop_at,
112
+ resume_token,
113
+ stop_head_of,
114
+ }
115
+
116
+ export type { PrrunToken, ResumeFacts }
117
+ export { run_prrun_resume }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { fileURLToPath } from 'node:url'
3
- import { git_followup_pending } from '#scripts/git/git-followup-pending'
3
+ import { git_followup_pending } from '#scripts/followup/git-followup-pending'
4
4
  import { release_scope_cli } from '#scripts/release/release-scope-cli'
5
5
  import { run_carry } from './run-carry'
6
6
  import { run_event_scope, type EventScope } from './run-event-scope'
@@ -62,7 +62,6 @@ async function main(): Promise<void> {
62
62
  }
63
63
 
64
64
  const run_report_cli = {
65
- main,
66
65
  run,
67
66
  }
68
67
 
@@ -71,7 +71,6 @@ function build_report(input: ReportInput): string {
71
71
  const run_report = {
72
72
  UNKNOWN_SCOPE_NOTICE,
73
73
  build_report,
74
- release_tail,
75
74
  }
76
75
 
77
76
  export type { ReportInput }
@@ -28,7 +28,7 @@ function is_owed(facts: RetrospectiveFacts): boolean {
28
28
  return !facts.is_lane_child && !facts.is_retrospective_done && !facts.is_consumer
29
29
  }
30
30
 
31
- const run_retrospective = { RETROSPECTIVE_ENV_KEY, is_enabled, is_owed }
31
+ const run_retrospective = { is_enabled, is_owed }
32
32
 
33
33
  export type { RetrospectiveFacts }
34
34
  export { run_retrospective }
@@ -118,10 +118,7 @@ async function main(argv: ReadonlyArray<string>): Promise<void> {
118
118
  }
119
119
 
120
120
  const run_review_cli = {
121
- JOIN_FLAG,
122
- USAGE,
123
121
  join_exit_code,
124
- main,
125
122
  run,
126
123
  }
127
124
 
@@ -94,7 +94,9 @@ async function wait_for_gate_finish(now: Clock = () => Date.now()): Promise<Gate
94
94
  let state = await read_gate_state()
95
95
 
96
96
  while (!run_review.is_gate_settled(state) && now() - started < MAX_JOIN_WAIT_MS) {
97
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
97
98
  await sleep(POLL_INTERVAL_MS)
99
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
98
100
  state = await read_gate_state()
99
101
  }
100
102
 
@@ -105,12 +105,10 @@ const run_review = {
105
105
  GATE_RED,
106
106
  GATE_RUNNING,
107
107
  adopt_verdict,
108
- format_seconds,
109
108
  format_timing,
110
109
  gate_state,
111
110
  is_gate_settled,
112
111
  overlap_seconds,
113
- span_seconds,
114
112
  }
115
113
 
116
114
  export type { AdoptVerdict, GateState, ReviewTiming, TimingStart }
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { fileURLToPath } from 'node:url'
3
- import { parseArgs } from 'node:util'
4
3
  import { lane_child_marker } from '#scripts/lane/lane-child-marker'
4
+ import { cli_flags } from '#scripts/lib/cli-flags'
5
5
  import { run_event_stream } from './run-event-stream'
6
6
  import { run_event_stream_emit } from './run-event-stream-emit'
7
7
  import { run_ship, type ShipSection } from './run-ship'
8
8
  import { run_ship_detach } from './run-ship-detach'
9
+ import { run_ship_next } from './run-ship-next'
9
10
  import { run_ship_probe } from './run-ship-probe'
10
11
  import { run_ship_return } from './run-ship-return'
11
12
  import { run_ship_stage, type Phase, type ShipState, type Stage } from './run-ship-stage'
@@ -120,13 +121,6 @@ interface ParsedValues extends NotifyValues {
120
121
  cite?: Array<string>
121
122
  }
122
123
 
123
- function parse_values(argv: ReadonlyArray<string>): {
124
- values: ParsedValues
125
- positionals: Array<string>
126
- } {
127
- return parseArgs({ args: [...argv], options: OPTIONS, allowPositionals: true, strict: true })
128
- }
129
-
130
124
  function ship_args(
131
125
  title: string,
132
126
  rest: ReadonlyArray<string>,
@@ -154,9 +148,10 @@ function log_command(number: string, positionals: ReadonlyArray<string>): ShipCo
154
148
  return CITE_PATTERN.test(number) && positionals.length === 0 ? { kind: 'log', number } : undefined
155
149
  }
156
150
 
157
- function read_args(argv: ReadonlyArray<string>): ShipCommand | undefined {
158
- const { values, positionals } = parse_values(argv)
159
-
151
+ function command_of(
152
+ values: ParsedValues,
153
+ positionals: ReadonlyArray<string>,
154
+ ): ShipCommand | undefined {
160
155
  if (values.log !== undefined) return log_command(values.log, positionals)
161
156
 
162
157
  const title = positionals[FIRST]
@@ -169,11 +164,9 @@ function read_args(argv: ReadonlyArray<string>): ShipCommand | undefined {
169
164
  // Numbers-only tail and a strict parse, so a title with no `#<N>` or a stray flag is refused rather
170
165
  // than shipped past the wrong step.
171
166
  function parse(argv: ReadonlyArray<string>): ShipCommand | undefined {
172
- try {
173
- return read_args(argv)
174
- } catch {
175
- return undefined
176
- }
167
+ const parsed = cli_flags.arguments_of(argv, OPTIONS)
168
+
169
+ return parsed === undefined ? undefined : command_of(parsed.values, parsed.positionals)
177
170
  }
178
171
 
179
172
  async function run_step(step: Step, args: ShipArguments, state: ShipState): Promise<ShipSection> {
@@ -255,7 +248,9 @@ async function stopped(
255
248
  args: ShipArguments,
256
249
  stage: Stage,
257
250
  ): Promise<ReadonlyArray<ShipSection>> {
258
- if (run_ship_detach.is_supervised()) await run_ship_return.return_control(args.number, stage)
251
+ if (run_ship_detach.is_supervised()) {
252
+ await run_ship_return.return_control(args.number, stage, run_ship_next.resume_of(args))
253
+ }
259
254
 
260
255
  return sections
261
256
  }
@@ -268,10 +263,12 @@ async function ship(args: ShipArguments): Promise<ReadonlyArray<ShipSection>> {
268
263
  const sections: Array<ShipSection> = []
269
264
 
270
265
  for (const step of run_ship_steps.steps(args)) {
266
+ // eslint-disable-next-line no-await-in-loop -- stages run in order and the first failure stops the ship
271
267
  const section = await run_stage(step, args, context)
272
268
 
273
269
  sections.push(section)
274
270
 
271
+ // eslint-disable-next-line no-await-in-loop -- stages run in order and the first failure stops the ship
275
272
  if (section.code !== SUCCESS_EXIT_CODE) return await stopped(sections, args, step.stage)
276
273
  }
277
274
 
@@ -324,6 +321,19 @@ function should_detach(args: ShipArguments): boolean {
324
321
  return !run_ship_detach.is_supervised() && lane_child_marker.is_child_of(process.cwd())
325
322
  }
326
323
 
324
+ // joshuafolkken/kit#2966: the preflight is deterministic and fast, so it runs here, in the agent's own
325
+ // turn, before the hand-off — a stop it finds is fixed by the same session instead of relaunching one
326
+ // from the supervisor. Only the slow stages go to the supervisor, which re-asks the preflight itself.
327
+ async function detach_after_preflight(args: ShipArguments): Promise<number> {
328
+ const section = await run_stage(run_ship_steps.PREFLIGHT_STEP, args, await open_context(args))
329
+
330
+ if (section.code === SUCCESS_EXIT_CODE) return await detach(args)
331
+
332
+ console.info(run_ship.format_report([section]))
333
+
334
+ return run_ship.exit_code([section])
335
+ }
336
+
327
337
  async function supervised_repository(): Promise<string | undefined> {
328
338
  return run_ship_detach.is_supervised() ? await run_ship_probe.repository_directory() : undefined
329
339
  }
@@ -346,7 +356,7 @@ async function run_ship_command(args: ShipArguments): Promise<number> {
346
356
  if (should_detach(args)) {
347
357
  if (!args.is_detach) console.error(LANE_CHILD_DETACH_NOTE)
348
358
 
349
- return await detach(args)
359
+ return await detach_after_preflight(args)
350
360
  }
351
361
 
352
362
  await claim_supervised_identity(args)
@@ -376,7 +386,7 @@ async function main(argv: ReadonlyArray<string>): Promise<void> {
376
386
  process.exitCode = await run(argv)
377
387
  }
378
388
 
379
- const run_ship_cli = { SUCCESS_EXIT_CODE, USAGE, main, parse, run, ship }
389
+ const run_ship_cli = { run }
380
390
 
381
391
  if (process.argv[1] === fileURLToPath(import.meta.url)) await main(process.argv.slice(ARGV_OFFSET))
382
392
 
@@ -195,6 +195,7 @@ async function ready_record(repository: string, number: string): Promise<ShipRec
195
195
  for (let attempt = 0; attempt < RECORD_WAIT_ATTEMPTS; attempt += 1) {
196
196
  if (!is_pending(record)) return record
197
197
 
198
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
198
199
  await sleep(RECORD_POLL_MS)
199
200
  record = current_record(repository, number)
200
201
  }