@joshuafolkken/kit 1.1166.0 → 1.1229.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 (437) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/settings.json +2 -2
  3. package/.claude/skills/dependency-update/SKILL.md +4 -8
  4. package/.claude/skills/epic-commands/SKILL.md +2 -2
  5. package/.claude/skills/epic-commands/epic-bundle.md +1 -1
  6. package/.claude/skills/workflow-commands/SKILL.md +2 -4
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +9 -5
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +29 -9
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +7 -2
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +24 -15
  13. package/.claude/skills/workflow-commands/chain-rule.md +1 -1
  14. package/.claude/skills/workflow-commands/delegation.md +2 -2
  15. package/.claude/skills/workflow-commands/followup.md +5 -0
  16. package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
  17. package/.claude/skills/workflow-commands/halfrun.md +1 -1
  18. package/.claude/skills/workflow-commands/issue-scout.md +1 -1
  19. package/.claude/skills/workflow-commands/kickoff.md +1 -1
  20. package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
  21. package/.claude/skills/workflow-commands/pre-gate-cut.md +10 -5
  22. package/.claude/skills/workflow-commands/prerequisite.md +2 -2
  23. package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
  24. package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
  25. package/.codex/hooks.json +2 -2
  26. package/.cursorrules +6 -41
  27. package/.github/actions/setup-node/action.yml +1 -1
  28. package/.github/workflows/auto-tag.yml +3 -4
  29. package/.github/workflows/ci.yml +47 -33
  30. package/.github/workflows/dependabot-auto-merge.yml +13 -6
  31. package/.github/workflows/publish.yml +48 -4
  32. package/.github/workflows/sonar-qube.yml +3 -0
  33. package/.pnpmfile.mjs +4 -3
  34. package/.prettierignore +8 -2
  35. package/CLAUDE.md +18 -20
  36. package/README.md +26 -19
  37. package/SECURITY.md +15 -1
  38. package/dist/CLAUDE.md +18 -20
  39. package/dist/commands/document-section-cli.js +734 -0
  40. package/dist/hooks/audit-provision.js +346 -0
  41. package/dist/hooks/batch-guard.js +5 -4
  42. package/dist/hooks/{chunk-DYAVAM6S.js → chunk-55HHG5PX.js} +1405 -631
  43. package/dist/hooks/{chunk-A6P7PN7P.js → chunk-5VMKCCC4.js} +25 -10
  44. package/dist/hooks/chunk-5VNDSNG3.js +12 -0
  45. package/dist/hooks/{chunk-5KTCMCOT.js → chunk-6HJJXRBX.js} +4 -2
  46. package/dist/hooks/chunk-CDY4NFQG.js +15 -0
  47. package/dist/hooks/chunk-ELCN5LCH.js +135 -0
  48. package/dist/hooks/{chunk-ENA7P5FB.js → chunk-F56MFS4B.js} +3 -3
  49. package/dist/hooks/{chunk-ANJ2WGFD.js → chunk-FIVDHQGT.js} +7 -6
  50. package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
  51. package/dist/hooks/{chunk-GU5MATVE.js → chunk-JUDKDCDP.js} +499 -389
  52. package/dist/hooks/{chunk-ZDZ555YH.js → chunk-NV2JRZZO.js} +743 -909
  53. package/dist/hooks/{chunk-V2BTOZHT.js → chunk-RNWHN5BP.js} +3 -3
  54. package/dist/hooks/{chunk-URJA6XY4.js → chunk-TQNPDZNH.js} +4 -4
  55. package/dist/hooks/{chunk-GYNGP7HI.js → chunk-VC3PZPZO.js} +1 -1
  56. package/dist/hooks/codex-hook-adapter.js +14 -12
  57. package/dist/hooks/duplicate-read-guard.js +6 -5
  58. package/dist/hooks/format-edited.js +4 -3
  59. package/dist/hooks/inputs.json +232 -210
  60. package/dist/hooks/investigation-guard.js +6 -5
  61. package/dist/hooks/pretool-guard.js +12 -10
  62. package/dist/hooks/session-lang.js +2 -4
  63. package/dist/hooks/stop-guard.js +81 -59
  64. package/dist/josh.js +872 -75
  65. package/dist/version/index.d.ts +7 -2
  66. package/dist/version/index.js +152 -75
  67. package/eslint/base.js +23 -1
  68. package/eslint/rules/code-quality.js +3 -0
  69. package/eslint/rules/early-return-one-liner.js +98 -0
  70. package/eslint/rules/unicorn.js +16 -1
  71. package/package.json +7 -6
  72. package/pnpm-workspace.yaml +9 -4
  73. package/prettier/basic.d.ts +3 -1
  74. package/prettier/index.d.ts +3 -1
  75. package/prettier/static.d.ts +3 -1
  76. package/prompts/coding-standards.md +1 -1
  77. package/prompts/collaboration-workflow/file-edits.md +4 -4
  78. package/prompts/collaboration-workflow/glossary.md +1 -1
  79. package/prompts/collaboration-workflow/operating-rules.md +17 -9
  80. package/prompts/collaboration-workflow/overview.md +2 -2
  81. package/prompts/collaboration-workflow/principles.md +14 -12
  82. package/prompts/collaboration-workflow/report-format.md +1 -1
  83. package/prompts/collaboration-workflow/residency.md +1 -1
  84. package/prompts/collaboration-workflow/rule-delivery.md +6 -6
  85. package/prompts/collaboration-workflow/shell-body.md +6 -6
  86. package/prompts/collaboration-workflow/turn-batching.md +1 -1
  87. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
  88. package/prompts/collaboration-workflow/wip-cap.md +10 -10
  89. package/prompts/review-rubric.md +1 -2
  90. package/prompts/review.md +1 -1
  91. package/scripts/agent/agent-headless.ts +29 -0
  92. package/scripts/agent/agent-launch-environment.ts +6 -2
  93. package/scripts/agent/codex-agent-argv.ts +2 -1
  94. package/scripts/auto-ok/auto-ok-cli.ts +1 -1
  95. package/scripts/backlog/backlog-arrival.ts +3 -3
  96. package/scripts/backlog/backlog-budget.ts +1 -1
  97. package/scripts/backlog/backlog-drive-args.ts +135 -0
  98. package/scripts/backlog/backlog-drive-cli.ts +11 -137
  99. package/scripts/backlog/backlog-drive-launch.ts +39 -0
  100. package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
  101. package/scripts/backlog/backlog-drive-named.ts +3 -3
  102. package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
  103. package/scripts/backlog/backlog-drive-owner.ts +1 -1
  104. package/scripts/backlog/backlog-drive-restore.ts +1 -1
  105. package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
  106. package/scripts/backlog/backlog-drive.ts +1 -1
  107. package/scripts/backlog/backlog-next.ts +31 -9
  108. package/scripts/backlog/backlog-offer-cli.ts +6 -6
  109. package/scripts/backlog/backlog-offer.ts +1 -1
  110. package/scripts/backlog/backlog-plan-cli.ts +7 -6
  111. package/scripts/backlog/backlog-pool.ts +57 -11
  112. package/scripts/backlog/backlog-rank.ts +18 -9
  113. package/scripts/backlog/backlog-ready.ts +1 -1
  114. package/scripts/backlog/backlog-restructure.ts +152 -0
  115. package/scripts/backlog/backlog-scope.ts +1 -1
  116. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  117. package/scripts/backlog/backlog-stalled.ts +1 -1
  118. package/scripts/backlog/backlog-waves.ts +15 -17
  119. package/scripts/build/build-codex-hooks.ts +3 -1
  120. package/scripts/build/build-commands.ts +66 -0
  121. package/scripts/build/build-hooks.ts +2 -1
  122. package/scripts/build/build-library.ts +12 -3
  123. package/scripts/build/build.ts +70 -0
  124. package/scripts/build/local-bin.ts +3 -13
  125. package/scripts/cost-runtime/context-cut-threshold.ts +1 -9
  126. package/scripts/cost-runtime/cost-verdict.ts +15 -28
  127. package/scripts/delegation/investigation-reads.ts +3 -1
  128. package/scripts/discovery/repo-origin.ts +5 -5
  129. package/scripts/doctor/doctor-consumer.ts +2 -2
  130. package/scripts/doctor/doctor-io.ts +2 -3
  131. package/scripts/document/capped-print-part.ts +30 -0
  132. package/scripts/document/capped-print.ts +3 -4
  133. package/scripts/document/document-byte-budget.ts +11 -2
  134. package/scripts/document/document-scan.ts +117 -0
  135. package/scripts/document/document-section.ts +143 -16
  136. package/scripts/document/entry-read-budget.ts +5 -3
  137. package/scripts/document/entry-read-set.ts +10 -89
  138. package/scripts/document/read-set-cost.ts +98 -0
  139. package/scripts/epic/epic-add-body.ts +1 -0
  140. package/scripts/epic/epic-audit-checks.ts +1 -1
  141. package/scripts/epic/epic-audit-rationale.ts +4 -3
  142. package/scripts/epic/epic-bundle-cli.ts +14 -3
  143. package/scripts/epic/epic-bundle-poll.ts +41 -0
  144. package/scripts/epic/epic-busy.ts +1 -1
  145. package/scripts/epic/epic-candidate-confirm.ts +3 -2
  146. package/scripts/epic/epic-classify.ts +1 -1
  147. package/scripts/epic/epic-cli-argv.ts +78 -46
  148. package/scripts/epic/epic-cli-remove.ts +11 -19
  149. package/scripts/epic/epic-cli.ts +74 -73
  150. package/scripts/epic/epic-close-comment.ts +1 -1
  151. package/scripts/epic/epic-close.ts +1 -1
  152. package/scripts/epic/epic-cross-repo.ts +5 -5
  153. package/scripts/epic/epic-graph.ts +1 -1
  154. package/scripts/epic/epic-index.ts +1 -1
  155. package/scripts/epic/epic-issue.ts +2 -6
  156. package/scripts/epic/epic-lane-offer.ts +2 -2
  157. package/scripts/epic/epic-next.ts +29 -42
  158. package/scripts/epic/epic-solo-stale.ts +1 -1
  159. package/scripts/epic/epic-triage.ts +8 -4
  160. package/scripts/epic/epic-validate.ts +1 -1
  161. package/scripts/followup/git-followup-finish.ts +2 -2
  162. package/scripts/followup/git-followup-label.ts +2 -2
  163. package/scripts/followup/git-followup-merged.ts +2 -2
  164. package/scripts/followup/git-followup-stages.ts +9 -0
  165. package/scripts/followup/git-pr-followup-checks.ts +142 -0
  166. package/scripts/followup/git-pr-followup.ts +5 -130
  167. package/scripts/gate/core-budget.ts +69 -1
  168. package/scripts/gate/gate-plan.ts +47 -7
  169. package/scripts/gate/gate-skip.ts +38 -7
  170. package/scripts/gate/hook-gate-reuse.ts +12 -10
  171. package/scripts/gate/pre-commit-type-check.ts +4 -1
  172. package/scripts/gate/project-checks.ts +6 -9
  173. package/scripts/gate/type-check-step.ts +2 -3
  174. package/scripts/gate/verification-gate.ts +27 -8
  175. package/scripts/gh/git-gh-api-path.ts +2 -2
  176. package/scripts/gh/git-gh-issue-rest.ts +3 -3
  177. package/scripts/gh/git-gh-issue-write.ts +53 -7
  178. package/scripts/gh/git-gh-issue.ts +5 -3
  179. package/scripts/gh/git-gh-pr-read.ts +68 -19
  180. package/scripts/gh/git-gh-pr-rest.ts +1 -1
  181. package/scripts/gh/git-gh-pr.ts +5 -1
  182. package/scripts/gh/git-pr-ai-review.ts +1 -1
  183. package/scripts/gh/git-pr-checks-eval.ts +9 -3
  184. package/scripts/gh/git-pr-checks-parse.ts +1 -1
  185. package/scripts/gh/git-pr-checks-watch.ts +6 -5
  186. package/scripts/gh/git-pr-checks.ts +18 -6
  187. package/scripts/gh/git-pr-coderabbit.ts +1 -1
  188. package/scripts/gh/git-pr-error.ts +1 -3
  189. package/scripts/gh/git-pr.ts +9 -13
  190. package/scripts/gh/github-issue-url.ts +2 -2
  191. package/scripts/git/constants.ts +12 -6
  192. package/scripts/git/git-command.ts +20 -218
  193. package/scripts/git/git-diff-reads.ts +225 -0
  194. package/scripts/git/git-error.ts +4 -12
  195. package/scripts/git/git-pre-push-hook.ts +139 -0
  196. package/scripts/git/git-prompt.ts +2 -6
  197. package/scripts/git/{schemas.ts → git-schemas.ts} +2 -0
  198. package/scripts/git/git-spawn-sync.ts +3 -1
  199. package/scripts/git/git-spawn.ts +1 -1
  200. package/scripts/git/git-staging.ts +4 -6
  201. package/scripts/git/git-status.ts +3 -9
  202. package/scripts/git/main-merge.ts +57 -21
  203. package/scripts/hooks/codex-hook-adapter.ts +1 -1
  204. package/scripts/hooks/edited-cspell.ts +2 -4
  205. package/scripts/hooks/format-edited-file.ts +3 -12
  206. package/scripts/hooks/pre-push-unit.ts +31 -9
  207. package/scripts/hooks/reserved-run.ts +2 -2
  208. package/scripts/hooks/run-hook.sh +5 -4
  209. package/scripts/hooks/stop-guard.ts +5 -3
  210. package/scripts/init/init-actions.ts +0 -5
  211. package/scripts/init/init-ai-copy.ts +13 -2
  212. package/scripts/init/init-logic-json-merge.ts +19 -0
  213. package/scripts/init/init-logic-secretlint.ts +7 -7
  214. package/scripts/init/init-logic-workspace.ts +41 -2
  215. package/scripts/init/init-logic.ts +16 -26
  216. package/scripts/init/init.ts +5 -15
  217. package/scripts/init/initial-commit.ts +4 -4
  218. package/scripts/init/kit-development-versions.ts +40 -0
  219. package/scripts/init/kit-setup-state.ts +2 -2
  220. package/scripts/init/start-exec.ts +68 -19
  221. package/scripts/init/start-setup-pr.ts +9 -9
  222. package/scripts/init/start-steps.ts +6 -10
  223. package/scripts/issue/defect-rate-cli.ts +1 -1
  224. package/scripts/issue/git-next-issues.ts +1 -1
  225. package/scripts/issue/issue-auto-ok.ts +1 -1
  226. package/scripts/issue/issue-backlinks-cli.ts +2 -2
  227. package/scripts/issue/issue-backlinks.ts +9 -9
  228. package/scripts/issue/issue-comment-cli.ts +19 -13
  229. package/scripts/issue/issue-file-cli.ts +45 -18
  230. package/scripts/issue/issue-file.ts +24 -1
  231. package/scripts/issue/issue-scout-closed.ts +1 -1
  232. package/scripts/issue/issue-state-cli.ts +30 -74
  233. package/scripts/josh/josh-command-map.ts +2 -0
  234. package/scripts/josh/josh-command-types.ts +11 -0
  235. package/scripts/josh/josh-commands-ai.ts +4 -4
  236. package/scripts/josh/josh-commands-backlog.ts +4 -1
  237. package/scripts/josh/josh-commands-development.ts +11 -1
  238. package/scripts/josh/josh-commands-document.ts +1 -0
  239. package/scripts/josh/josh-commands-lane.ts +22 -0
  240. package/scripts/josh/josh-commands-maintenance.ts +4 -4
  241. package/scripts/josh/josh-commands-run.ts +12 -7
  242. package/scripts/josh/josh-in-process.ts +28 -9
  243. package/scripts/josh/josh-logic.ts +33 -15
  244. package/scripts/josh/process-identity.ts +1 -3
  245. package/scripts/josh/session-language-cli.ts +3 -4
  246. package/scripts/lane/lane-await.ts +5 -1
  247. package/scripts/lane/lane-child-invocation.ts +2 -2
  248. package/scripts/lane/lane-cli.ts +3 -3
  249. package/scripts/lane/lane-dispatch.ts +14 -10
  250. package/scripts/lane/lane-environment.ts +5 -1
  251. package/scripts/lane/lane-handoff.ts +1 -1
  252. package/scripts/lane/lane-install.ts +2 -13
  253. package/scripts/lane/lane-launch-cli.ts +35 -3
  254. package/scripts/lane/lane-ledger.ts +145 -0
  255. package/scripts/lane/lane-leftover.ts +1 -1
  256. package/scripts/lane/lane-load.ts +108 -0
  257. package/scripts/lane/lane-measure-cli.ts +149 -0
  258. package/scripts/lane/lane-occupancy.ts +3 -0
  259. package/scripts/lane/lane-reap.ts +1 -3
  260. package/scripts/lane/lane-relaunch.ts +1 -1
  261. package/scripts/lane/lane-stats.ts +224 -0
  262. package/scripts/lane/lane-vacant.ts +36 -0
  263. package/scripts/lane/openai-lane-ship.ts +2 -2
  264. package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
  265. package/scripts/lane/openai-lane-supervisor.ts +7 -165
  266. package/scripts/lane/openai-review-broker.ts +2 -4
  267. package/scripts/lib/ancestor-directories.ts +35 -0
  268. package/scripts/lib/animation-helpers.ts +2 -2
  269. package/scripts/lib/buffered-process.ts +3 -7
  270. package/scripts/lib/cli-flags.ts +71 -1
  271. package/scripts/lib/spawn-exit.ts +2 -2
  272. package/scripts/lib/timeouts.ts +35 -2
  273. package/scripts/lines/line-budget.ts +4 -6
  274. package/scripts/notify/telegram-notify.ts +26 -1
  275. package/scripts/notify/telegram-test.ts +6 -0
  276. package/scripts/observations/observation-ledger-line.ts +6 -6
  277. package/scripts/observations/observation-ledger-migrate.ts +55 -4
  278. package/scripts/observations/observation-ledger.ts +26 -7
  279. package/scripts/overrides/lockfile-overrides.ts +1 -1
  280. package/scripts/overrides/overrides-logic.ts +1 -1
  281. package/scripts/propagate/propagate-git.ts +1 -1
  282. package/scripts/propagate/propagate-publish.ts +2 -2
  283. package/scripts/propagate/propagate-steps.ts +20 -15
  284. package/scripts/propagate/propagate.ts +36 -27
  285. package/scripts/refactor/import-graph.ts +18 -1
  286. package/scripts/refactor/refactor-lint.ts +4 -4
  287. package/scripts/repo/repository-labels.ts +2 -2
  288. package/scripts/repo/required-checks-logic.ts +1 -3
  289. package/scripts/review/live-evidence.ts +2 -4
  290. package/scripts/review/review-brief-cli.ts +1 -1
  291. package/scripts/review/review-finding-ledger.ts +1 -1
  292. package/scripts/review/review-findings-cli.ts +1 -3
  293. package/scripts/rules/decision-oracle-batch.ts +128 -0
  294. package/scripts/rules/decision-oracle-tokens.ts +21 -0
  295. package/scripts/rules/decision-oracle.ts +8 -120
  296. package/scripts/rules/delivered-rules.ts +26 -182
  297. package/scripts/rules/direct-filing.ts +1 -1
  298. package/scripts/rules/early-heartbeat.ts +3 -3
  299. package/scripts/rules/git-argv.ts +1 -1
  300. package/scripts/rules/git-force.ts +1 -1
  301. package/scripts/rules/implementation-cut.ts +2 -2
  302. package/scripts/rules/index-guard.ts +1 -1
  303. package/scripts/rules/issue-comments.ts +118 -0
  304. package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
  305. package/scripts/rules/lane-background.ts +2 -2
  306. package/scripts/rules/lane-park.ts +18 -0
  307. package/scripts/rules/lane-split-park.ts +128 -0
  308. package/scripts/rules/oracle-firing.ts +1 -1
  309. package/scripts/rules/poll-loop.ts +1 -1
  310. package/scripts/rules/pre-gate-cut.ts +3 -3
  311. package/scripts/rules/protected-files.ts +59 -4
  312. package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
  313. package/scripts/rules/shell-body-trigger.ts +1 -1
  314. package/scripts/rules/stop-rules.ts +18 -7
  315. package/scripts/rules/test-declared-commit.ts +4 -4
  316. package/scripts/rules/wip-cap.ts +63 -0
  317. package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
  318. package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
  319. package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
  320. package/scripts/run/{run-carry.ts → carry/run-carry.ts} +12 -6
  321. package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
  322. package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +3 -3
  323. package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +2 -2
  324. package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
  325. package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
  326. package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
  327. package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
  328. package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
  329. package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +16 -2
  330. package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
  331. package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
  332. package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
  333. package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
  334. package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
  335. package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
  336. package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
  337. package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
  338. package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
  339. package/scripts/run/run-ending.ts +2 -2
  340. package/scripts/run/run-halfrun-resume.ts +6 -1
  341. package/scripts/run/run-headless.ts +1 -1
  342. package/scripts/run/run-invocation.ts +85 -84
  343. package/scripts/run/run-liveness.ts +1 -1
  344. package/scripts/run/run-parent-cut-hook.ts +1 -1
  345. package/scripts/run/run-preflight.ts +6 -6
  346. package/scripts/run/run-prep-cli.ts +1 -1
  347. package/scripts/run/run-prrun-resume.ts +1 -1
  348. package/scripts/run/run-report-cli.ts +4 -4
  349. package/scripts/run/run-report.ts +2 -2
  350. package/scripts/run/run-status-cli.ts +1 -1
  351. package/scripts/run/run-step-cli.ts +3 -3
  352. package/scripts/run/run-step.ts +2 -2
  353. package/scripts/run/run-stop-notify.ts +1 -1
  354. package/scripts/run/run-stranded-detect.ts +4 -4
  355. package/scripts/run/run-stranded.ts +2 -2
  356. package/scripts/run/run-watcher-guard-cli.ts +1 -1
  357. package/scripts/run/run-watcher-guard.ts +5 -3
  358. package/scripts/run/run-watcher-hook.ts +1 -1
  359. package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +29 -8
  360. package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
  361. package/scripts/run/ship/run-ship-lock.ts +50 -0
  362. package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +13 -0
  363. package/scripts/run/ship/run-ship-pre-detach.ts +66 -0
  364. package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
  365. package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
  366. package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +10 -6
  367. package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
  368. package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
  369. package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +13 -0
  370. package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +15 -8
  371. package/scripts/run/ship/run-ship-stop-text.ts +13 -0
  372. package/scripts/run/ship/run-ship-sync.ts +128 -0
  373. package/scripts/run/{run-ship.ts → ship/run-ship.ts} +6 -0
  374. package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
  375. package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +31 -9
  376. package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +3 -3
  377. package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +99 -17
  378. package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
  379. package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +8 -2
  380. package/scripts/run/wake/run-wake-restart.ts +58 -0
  381. package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
  382. package/scripts/run/{run-wake.ts → wake/run-wake.ts} +1 -1
  383. package/scripts/safe-chain/preinstall-command.ts +20 -21
  384. package/scripts/safe-chain/project-config.ts +19 -15
  385. package/scripts/sync/sync-ai-files.ts +1 -1
  386. package/scripts/sync/sync-configs.ts +2 -1
  387. package/scripts/sync/sync.ts +15 -2
  388. package/scripts/test/test-red-logic.ts +17 -2
  389. package/scripts/test/test-red.ts +8 -7
  390. package/scripts/test/unit-worker-share.ts +1 -0
  391. package/scripts/time-runtime/time-background.ts +5 -1
  392. package/scripts/time-runtime/time-instant.ts +3 -1
  393. package/scripts/time-runtime/time-spans.ts +6 -139
  394. package/scripts/time-runtime/time-tool-call.ts +160 -0
  395. package/scripts/time-runtime/time-transcript-line.ts +27 -1
  396. package/scripts/version/effective-upstream.ts +2 -20
  397. package/scripts/version/latest-corepack.ts +17 -17
  398. package/scripts/version/latest-regression.ts +1 -1
  399. package/scripts/version/latest-update.ts +4 -4
  400. package/scripts/version/npm-registry.ts +20 -5
  401. package/scripts/version/publishable-range-check.ts +2 -2
  402. package/scripts/version/publishable-range.ts +1 -1
  403. package/scripts/version/release-age.ts +48 -38
  404. package/scripts/version/release-hold.ts +1 -1
  405. package/scripts/version/safe-chain-version-update.ts +40 -0
  406. package/scripts/version/upgrade-shell-command.ts +48 -8
  407. package/scripts/version/version-check-logic.ts +23 -14
  408. package/scripts/version/version-commands.ts +23 -11
  409. package/scripts/version/version-remote.ts +28 -13
  410. package/templates/pnpm-workspace.basic.yaml +4 -0
  411. package/templates/workflows/dependabot-auto-merge.yml +5 -5
  412. package/templates/workflows/github-release.yml +2 -0
  413. package/scripts/build/build-config-merge.ts +0 -27
  414. package/scripts/build/build-managed-marker.ts +0 -27
  415. package/scripts/build/build-self-sync-guard.ts +0 -27
  416. package/scripts/build/build-version.ts +0 -26
  417. package/scripts/version/preinstall-version-update.ts +0 -86
  418. /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
  419. /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
  420. /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
  421. /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
  422. /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
  423. /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
  424. /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
  425. /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
  426. /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
  427. /package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +0 -0
  428. /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
  429. /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
  430. /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
  431. /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
  432. /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
  433. /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
  434. /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
  435. /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
  436. /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
  437. /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
@@ -2,11 +2,12 @@
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { lane_child_marker } from '#scripts/lane/lane-child-marker'
4
4
  import { cli_flags } from '#scripts/lib/cli-flags'
5
- import { run_event_stream } from './run-event-stream'
6
- import { run_event_stream_emit } from './run-event-stream-emit'
5
+ import { run_event_stream } from '#scripts/run/event/run-event-stream'
6
+ import { run_event_stream_emit } from '#scripts/run/event/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
9
  import { run_ship_next } from './run-ship-next'
10
+ import { run_ship_pre_detach } from './run-ship-pre-detach'
10
11
  import { run_ship_probe } from './run-ship-probe'
11
12
  import { run_ship_return } from './run-ship-return'
12
13
  import { run_ship_stage, type Phase, type ShipState, type Stage } from './run-ship-stage'
@@ -171,8 +172,9 @@ function parse(argv: ReadonlyArray<string>): ShipCommand | undefined {
171
172
 
172
173
  async function run_step(step: Step, args: ShipArguments, state: ShipState): Promise<ShipSection> {
173
174
  const result = await step.run(args, state)
175
+ const section = { header: step.header, body: result.out, code: result.code }
174
176
 
175
- return { header: step.header, body: result.out, code: result.code }
177
+ return result.conflicts === undefined ? section : { ...section, conflicts: result.conflicts }
176
178
  }
177
179
 
178
180
  async function emit_phase(args: ShipArguments, stage: Stage, phase: Phase): Promise<void> {
@@ -249,7 +251,14 @@ async function stopped(
249
251
  stage: Stage,
250
252
  ): Promise<ReadonlyArray<ShipSection>> {
251
253
  if (run_ship_detach.is_supervised()) {
252
- await run_ship_return.return_control(args.number, stage, run_ship_next.resume_of(args))
254
+ const conflicts = sections.at(-1)?.conflicts
255
+ const resume = run_ship_next.resume_of(args)
256
+
257
+ await run_ship_return.return_control(
258
+ args.number,
259
+ stage,
260
+ conflicts === undefined ? resume : { ...resume, conflicts },
261
+ )
253
262
  }
254
263
 
255
264
  return sections
@@ -324,14 +333,26 @@ function should_detach(args: ShipArguments): boolean {
324
333
  // joshuafolkken/kit#2966: the preflight is deterministic and fast, so it runs here, in the agent's own
325
334
  // turn, before the hand-off — a stop it finds is fixed by the same session instead of relaunching one
326
335
  // from the supervisor. Only the slow stages go to the supervisor, which re-asks the preflight itself.
336
+ // joshuafolkken/kit#3222: the type check and the document tests follow it here for the same reason; they
337
+ // are not a stage, so the supervisor's gate runs them again rather than passing over them.
338
+ async function pre_detach_sections(args: ShipArguments): Promise<ReadonlyArray<ShipSection>> {
339
+ const preflight = await run_stage(run_ship_steps.PREFLIGHT_STEP, args, await open_context(args))
340
+ if (preflight.code !== SUCCESS_EXIT_CODE) return [preflight]
341
+
342
+ const result = await run_ship_pre_detach.checks()
343
+
344
+ return [preflight, { header: run_ship.PRE_DETACH_HEADER, body: result.out, code: result.code }]
345
+ }
346
+
327
347
  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))
348
+ const sections = await pre_detach_sections(args)
349
+ const code = run_ship.exit_code(sections)
329
350
 
330
- if (section.code === SUCCESS_EXIT_CODE) return await detach(args)
351
+ if (code === SUCCESS_EXIT_CODE) return await detach(args)
331
352
 
332
- console.info(run_ship.format_report([section]))
353
+ console.info(run_ship.format_report(sections))
333
354
 
334
- return run_ship.exit_code([section])
355
+ return code
335
356
  }
336
357
 
337
358
  async function supervised_repository(): Promise<string | undefined> {
@@ -3,10 +3,10 @@ import { setTimeout as sleep } from 'node:timers/promises'
3
3
  import { agent_role_profile } from '#scripts/agent/agent-role-profile'
4
4
  import { process_identity } from '#scripts/josh/process-identity'
5
5
  import { stamp_file } from '#scripts/josh/stamp-file'
6
+ import { detached_launch, type LaunchArgv } from '#scripts/run/detached-launch'
7
+ import { run_event_stream } from '#scripts/run/event/run-event-stream'
8
+ import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
6
9
  import { z } from 'zod'
7
- import { detached_launch, type LaunchArgv } from './detached-launch'
8
- import { run_event_stream } from './run-event-stream'
9
- import { run_event_stream_emit } from './run-event-stream-emit'
10
10
 
11
11
  // `josh ship --detach` — hand the post-implementation region to a supervisor that outlives the agent
12
12
  // (joshuafolkken/kit#2428). A lane child used to take the pre-gate cut and relaunch a fresh agent session
@@ -0,0 +1,50 @@
1
+ import { existsSync } from 'node:fs'
2
+ import path from 'node:path'
3
+ import { stripVTControlCharacters } from 'node:util'
4
+ import { buffered_process } from '#scripts/lib/buffered-process'
5
+ import { INSTALL_TIMEOUT_MS } from '#scripts/lib/timeouts'
6
+
7
+ // A lock file that no longer matches its inputs, asked before the gate (joshuafolkken/kit#3307).
8
+ //
9
+ // The pre-push hook's setup runs `pnpm install`, which rewrites a lock that has drifted — a
10
+ // `.pnpmfile.mjs` edit moves `pnpmfileChecksum` — and so leaves the working tree dirty under the push.
11
+ // The hook then refuses the gate's record and re-runs the whole unit suite, long enough under a crowded
12
+ // machine to time the push out. The frozen, lock-only install answers the same question without
13
+ // writing anything or touching `node_modules`, in about a third of a second.
14
+ //
15
+ // A project with no lock file has nothing to drift, so nothing is asked there.
16
+ //
17
+ // **pnpm's own error line rides along**: the install refuses for reasons other than drift too — an
18
+ // engine mismatch, a timeout on a crowded machine — and there "run `pnpm install`" fixes nothing, so
19
+ // the reader needs pnpm's error code to tell the two apart. The line naming an `ERR_PNPM_` code wins
20
+ // over the first line, which in a workspace is pnpm's "Scope: all N workspace projects" banner.
21
+
22
+ const LOCK_FILE = 'pnpm-lock.yaml'
23
+ const PNPM_ERROR_CODE = 'ERR_PNPM_'
24
+ const LOCK_CHECK_ARGUMENTS = ['install', '--frozen-lockfile', '--lockfile-only', '--ignore-scripts']
25
+ const LOCK_DRIFT = `${LOCK_FILE} does not match its inputs (\`pnpm install --frozen-lockfile\` refuses it), so the pre-push hook's install would rewrite it under the push: run \`pnpm install\` and commit ${LOCK_FILE}.`
26
+
27
+ function drift_problem(output: string): string {
28
+ const lines = stripVTControlCharacters(output)
29
+ .split('\n')
30
+ .map((line) => line.trim())
31
+ .filter((line) => line !== '')
32
+ const reason = lines.find((line) => line.includes(PNPM_ERROR_CODE)) ?? lines[0]
33
+
34
+ return reason === undefined ? LOCK_DRIFT : `${LOCK_DRIFT} pnpm said: ${reason}`
35
+ }
36
+
37
+ async function lock_problems(directory: string = process.cwd()): Promise<Array<string>> {
38
+ if (!existsSync(path.join(directory, LOCK_FILE))) return []
39
+
40
+ const result = await buffered_process.run_buffered_process(LOCK_CHECK_ARGUMENTS, {
41
+ cwd: directory,
42
+ timeout_ms: INSTALL_TIMEOUT_MS,
43
+ })
44
+
45
+ return buffered_process.is_process_failed(result) ? [drift_problem(result.output)] : []
46
+ }
47
+
48
+ const run_ship_lock = { LOCK_CHECK_ARGUMENTS, LOCK_DRIFT, lock_problems }
49
+
50
+ export { run_ship_lock }
@@ -42,6 +42,8 @@ interface ShipResume {
42
42
  title: string
43
43
  flags: ReadonlyArray<string>
44
44
  is_review: boolean
45
+ // The paths a merge of the default branch left unmerged (joshuafolkken/kit#3221).
46
+ conflicts?: ReadonlyArray<string>
45
47
  }
46
48
 
47
49
  // Single quotes, so a `$` or a backtick in a title is never expanded by the shell that runs it.
@@ -90,14 +92,25 @@ const AFTER_FIX: Record<Stage, (resume: ShipResume) => string> = {
90
92
  [STAGE.PREFLIGHT]: detach_as_started,
91
93
  [STAGE.REVIEW]: detach_as_started,
92
94
  [STAGE.GATE]: detach_as_started,
95
+ [STAGE.SYNC]: detach_as_started,
93
96
  [STAGE.COMMIT]: detach_as_started,
94
97
  [STAGE.ROUND_TWO]: detach_again,
95
98
  [STAGE.FOLLOWUP]: detach_again,
96
99
  [STAGE.REPORT]: detach_again,
97
100
  }
98
101
 
102
+ // A conflict names its paths (joshuafolkken/kit#3221), so the resumed session resolves them without
103
+ // reading the report or asking git which files are unmerged.
104
+ function conflict_step(conflicts: ReadonlyArray<string>): string {
105
+ return `Resolve the conflict with origin/main in ${conflicts.join(', ')} (the merge is left in progress: remove the conflict markers in those files), then`
106
+ }
107
+
99
108
  // Two lines' worth: read the stopped report, then the one command that resumes after the fix.
100
109
  function next_step(issue: string, stage: Stage, resume: ShipResume): string {
110
+ const conflicts = resume.conflicts ?? []
111
+
112
+ if (conflicts.length > 0) return `${conflict_step(conflicts)} ${AFTER_FIX[stage](resume)}`
113
+
101
114
  return `Read the stopped report with \`${COMMAND.LOG} ${issue}\`, fix the ${stage} failure, then ${AFTER_FIX[stage](resume)}`
102
115
  }
103
116
 
@@ -0,0 +1,66 @@
1
+ import { existsSync } from 'node:fs'
2
+ import path from 'node:path'
3
+ import { type_check_step } from '#scripts/gate/type-check-step'
4
+ import type { JoshResult } from '#scripts/josh/josh-run'
5
+ import { buffered_process, type BufferedProcessResult } from '#scripts/lib/buffered-process'
6
+
7
+ // The fast checks a detached `josh ship` runs in the agent's own turn, after the preflight and before
8
+ // the hand-off (joshuafolkken/kit#3222). In one measured backlogrun about 7 of 31 lane resumes were a
9
+ // gate failure first seen after the detach — a type error in a file the related-only checks never
10
+ // touched, or a document test (a SKILL.md pointer, a byte budget, the command list) that only the full
11
+ // suite runs — each paid for with a relaunched session. These two answer in seconds, so the same
12
+ // session fixes what they find and ships again. **Nothing moves out of the gate**: the supervised gate
13
+ // stage still runs every check, these included.
14
+ //
15
+ // The test targets are the directories those resumes failed in. A consumer has none of them, so only
16
+ // the ones present run, and a consumer meets the type check alone.
17
+
18
+ const SUCCESS_EXIT_CODE = 0
19
+ const FAILURE_EXIT_CODE = 1
20
+ const SECTION_SEPARATOR = '\n\n'
21
+ const PASSED = 'type check and document tests passed'
22
+ const DOCUMENT_TEST_TARGETS: ReadonlyArray<string> = [
23
+ 'scripts/document',
24
+ 'scripts/claude',
25
+ 'scripts/rules',
26
+ 'scripts/josh/josh-command-map.test.ts',
27
+ ]
28
+ // A present directory need not hold a test the project's Vitest includes — a consumer's own
29
+ // `scripts/claude` of shell helpers, say — and a filter that matches nothing would stop every ship.
30
+ const DOCUMENT_TEST_COMMAND: ReadonlyArray<string> = ['josh', 'test:unit', '--passWithNoTests']
31
+
32
+ function present_targets(directory: string): ReadonlyArray<string> {
33
+ return DOCUMENT_TEST_TARGETS.filter((target) => existsSync(path.join(directory, target)))
34
+ }
35
+
36
+ async function command_lists(directory: string): Promise<Array<ReadonlyArray<string>>> {
37
+ const type_check = await type_check_step.resolve_type_check_args(directory)
38
+ const targets = present_targets(directory)
39
+
40
+ return targets.length === 0 ? [type_check] : [type_check, [...DOCUMENT_TEST_COMMAND, ...targets]]
41
+ }
42
+
43
+ async function run_in(
44
+ directory: string,
45
+ list: ReadonlyArray<string>,
46
+ ): Promise<BufferedProcessResult> {
47
+ return await buffered_process.run_buffered_process(list, { cwd: directory })
48
+ }
49
+
50
+ // Both run at once, buffered, and every failure is reported, so one ship shows all there is to fix.
51
+ async function checks(directory: string = process.cwd()): Promise<JoshResult> {
52
+ const lists = await command_lists(directory)
53
+ const results = await Promise.all(lists.map(async (list) => await run_in(directory, list)))
54
+ const failed = results.filter((result) => buffered_process.is_process_failed(result))
55
+
56
+ if (failed.length === 0) return { code: SUCCESS_EXIT_CODE, out: PASSED }
57
+
58
+ return {
59
+ code: FAILURE_EXIT_CODE,
60
+ out: failed.map((result) => result.output.trim()).join(SECTION_SEPARATOR),
61
+ }
62
+ }
63
+
64
+ const run_ship_pre_detach = { DOCUMENT_TEST_TARGETS, PASSED, checks }
65
+
66
+ export { run_ship_pre_detach }
@@ -7,6 +7,7 @@ import { git_preflight } from '#scripts/git/git-preflight'
7
7
  import type { JoshResult } from '#scripts/josh/josh-run'
8
8
  import { error_text } from '#scripts/lib/error-message'
9
9
  import { live_evidence } from '#scripts/review/live-evidence'
10
+ import { run_ship_lock } from './run-ship-lock'
10
11
  import { run_ship_scoped } from './run-ship-scoped'
11
12
 
12
13
  // `josh ship`'s first stage (joshuafolkken/kit#2946). A detached ship used to meet the pull request's
@@ -71,13 +72,16 @@ async function caught(problems: Promise<Array<string>>): Promise<Array<string>>
71
72
  }
72
73
  }
73
74
 
75
+ // The lock file is asked here too (joshuafolkken/kit#3307), so a drift stops the ship before the gate
76
+ // rather than being rewritten by the pre-push hook's install under the push.
74
77
  async function asked_problems(request: PreflightRequest): Promise<Array<string>> {
75
- const [preflight, evidence] = await Promise.all([
78
+ const [preflight, evidence, lock] = await Promise.all([
76
79
  caught(git_preflight.problems_of({ cli_input: request.title, will_open_pr: true })),
77
80
  caught(evidence_problems(request.body_path)),
81
+ caught(run_ship_lock.lock_problems()),
78
82
  ])
79
83
 
80
- return [...preflight, ...evidence]
84
+ return [...preflight, ...evidence, ...lock]
81
85
  }
82
86
 
83
87
  async function stage(request: PreflightRequest): Promise<JoshResult> {
@@ -1,8 +1,8 @@
1
1
  import { git_command } from '#scripts/git/git-command'
2
2
  import { git_worktree } from '#scripts/git/git-worktree'
3
3
  import { error_text } from '#scripts/lib/error-message'
4
- import { run_hold } from './run-hold'
5
- import { run_preflight } from './run-preflight'
4
+ import { run_hold } from '#scripts/run/hold/run-hold'
5
+ import { run_preflight } from '#scripts/run/run-preflight'
6
6
  import { run_ship_stage, type ShipState } from './run-ship-stage'
7
7
 
8
8
  // The read side of a resumed `josh ship` (joshuafolkken/kit#2426): the repository's actual state, asked
@@ -3,10 +3,11 @@ import { lane_child_invocation } from '#scripts/lane/lane-child-invocation'
3
3
  import { lane_child_marker } from '#scripts/lane/lane-child-marker'
4
4
  import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
5
5
  import { lane_relaunch } from '#scripts/lane/lane-relaunch'
6
- import { run_event_stream } from './run-event-stream'
7
- import { run_event_stream_emit } from './run-event-stream-emit'
6
+ import { run_event_stream } from '#scripts/run/event/run-event-stream'
7
+ import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
8
8
  import type { ShipResume } from './run-ship-next'
9
- import type { Stage } from './run-ship-stage'
9
+ import { run_ship_stage, type Stage } from './run-ship-stage'
10
+ import { run_ship_stop_text } from './run-ship-stop-text'
10
11
 
11
12
  // How a detached `josh ship` supervisor hands a stopped stage back (joshuafolkken/kit#2428). The agent
12
13
  // ended when it handed the region over, so a red gate, a High/Medium review, a failed push or red CI has
@@ -28,8 +29,8 @@ const OPENAI_PROVIDER = 'openai'
28
29
 
29
30
  type ReturnOutcome = 'relaunched' | 'recorded'
30
31
 
31
- function stop_text(issue: string, stage: Stage): string {
32
- return `#${issue} ${stage} failed — pnpm josh ship --log ${issue}`
32
+ function stop_text(issue: string, stage: Stage, resume: ShipResume): string {
33
+ return run_ship_stop_text.format(issue, run_ship_stage.stop_reason(stage, resume.conflicts))
33
34
  }
34
35
 
35
36
  function can_relaunch(lane: LaneInfo | undefined): lane is LaneInfo {
@@ -53,7 +54,10 @@ async function return_control(
53
54
  stage: Stage,
54
55
  resume: ShipResume,
55
56
  ): Promise<ReturnOutcome> {
56
- await run_event_stream_emit.emit(run_event_stream.EVENT_KIND.SHIP_STOP, stop_text(issue, stage))
57
+ await run_event_stream_emit.emit(
58
+ run_event_stream.EVENT_KIND.SHIP_STOP,
59
+ stop_text(issue, stage, resume),
60
+ )
57
61
 
58
62
  const lane = await lane_to_relaunch(issue)
59
63
 
@@ -6,7 +6,7 @@ import { stamp_file } from '#scripts/josh/stamp-file'
6
6
  import { lane_child_marker } from '#scripts/lane/lane-child-marker'
7
7
  import { openai_review_broker } from '#scripts/lane/openai-review-broker'
8
8
  import { review_record } from '#scripts/review/review-record'
9
- import { detached_launch } from './detached-launch'
9
+ import { detached_launch } from '#scripts/run/detached-launch'
10
10
  import { run_ship_review, type RoundOutcome, type ScoredVerdict } from './run-ship-review'
11
11
  import { run_ship_scoped, type Phase } from './run-ship-scoped'
12
12
 
@@ -59,8 +59,8 @@ function failure(out: string): JoshResult {
59
59
  return { code: FAILURE_EXIT_CODE, out }
60
60
  }
61
61
 
62
- function paths(root: string = PROJECT_ROOT): { brief: string; findings: string; log: string } {
63
- return openai_review_broker.review_paths(root)
62
+ function paths(): { brief: string; findings: string; log: string } {
63
+ return openai_review_broker.review_paths(PROJECT_ROOT)
64
64
  }
65
65
 
66
66
  async function josh(argv: ReadonlyArray<string>): Promise<JoshResult> {
@@ -39,7 +39,16 @@ async function scoped_pair(): Promise<JoshResult> {
39
39
  )
40
40
  }
41
41
 
42
- const run_ship_scoped = { run_phases, scoped_pair }
42
+ // The scoped pair and then `josh gate` — the gate stage, and the followup's gate over a merged tree
43
+ // before its push (joshuafolkken/kit#3307), so both leave the record the pre-push hook reuses.
44
+ async function scoped_gate(): Promise<JoshResult> {
45
+ return await run_phases([
46
+ scoped_pair,
47
+ async () => await josh_command.josh_run(['gate'], should_forward_stderr),
48
+ ])
49
+ }
50
+
51
+ const run_ship_scoped = { run_phases, scoped_gate, scoped_pair }
43
52
 
44
53
  export type { Phase }
45
54
  export { run_ship_scoped }
@@ -20,6 +20,7 @@ const STAGE = {
20
20
  PREFLIGHT: 'preflight',
21
21
  REVIEW: 'review',
22
22
  GATE: 'gate',
23
+ SYNC: 'sync',
23
24
  COMMIT: 'commit',
24
25
  ROUND_TWO: 'round-2',
25
26
  FOLLOWUP: 'followup',
@@ -99,6 +100,9 @@ const DONE_BY: Record<Stage, (done: ReadonlySet<string>, state: ShipState) => bo
99
100
  // the recorded round, and only a commit pins the tree that round read.
100
101
  [STAGE.REVIEW]: (_done, state) => state.is_merged || state.is_committed,
101
102
  [STAGE.GATE]: (_done, state) => state.is_merged || state.is_committed,
103
+ // The merge of the default branch before the commit (joshuafolkken/kit#3221): once committed, a
104
+ // conflicting pull request is the followup's to merge again.
105
+ [STAGE.SYNC]: (_done, state) => state.is_merged || state.is_committed,
102
106
  [STAGE.COMMIT]: (done, state) => state.is_merged || (done.has(STAGE.COMMIT) && is_shipped(state)),
103
107
  // The round-2 pass (joshuafolkken/kit#2489) reads the pushed fix delta, so its record is honored only
104
108
  // while that push still stands — new work pushed on top reruns it.
@@ -138,6 +142,14 @@ function event_text(issue: string, stage: Stage, phase: Phase): string {
138
142
  return `#${issue} ${stage} ${phase}`
139
143
  }
140
144
 
145
+ // A stop on a merge git could not finish is named `conflict` rather than by the stage it happened in
146
+ // (joshuafolkken/kit#3221), so the event stream reads the cause without anyone opening the report.
147
+ const CONFLICT = 'conflict'
148
+
149
+ function stop_reason(stage: Stage, conflicts: ReadonlyArray<string> = []): string {
150
+ return conflicts.length > 0 ? CONFLICT : stage
151
+ }
152
+
141
153
  const run_ship_stage = {
142
154
  PHASE,
143
155
  STAGE,
@@ -148,6 +160,7 @@ const run_ship_stage = {
148
160
  mark_done,
149
161
  read_done,
150
162
  record_path,
163
+ stop_reason,
151
164
  }
152
165
 
153
166
  export type { Phase, ShipState, Stage }
@@ -4,6 +4,7 @@ import { run_ship_preflight } from './run-ship-preflight'
4
4
  import { run_ship_review_steps } from './run-ship-review-steps'
5
5
  import { run_ship_scoped } from './run-ship-scoped'
6
6
  import { run_ship_stage, type ShipState, type Stage } from './run-ship-stage'
7
+ import { run_ship_sync, type StepResult } from './run-ship-sync'
7
8
 
8
9
  // The stages `josh ship` runs, in the order a change ships (joshuafolkken/kit#2398), kept apart from the
9
10
  // chaining CLI so the order is read in one place.
@@ -24,7 +25,7 @@ interface ShipArguments {
24
25
  interface Step {
25
26
  stage: Stage
26
27
  header: string
27
- run: (args: ShipArguments, state: ShipState) => Promise<JoshResult>
28
+ run: (args: ShipArguments, state: ShipState) => Promise<StepResult>
28
29
  }
29
30
 
30
31
  const { STAGE } = run_ship_stage
@@ -47,14 +48,19 @@ const PREFLIGHT_STEP: Step = {
47
48
  // with no green record (joshuafolkken/kit#2946). The commit step carries the `--skip-*` flags a resumed
48
49
  // ship needs, so an existing commit or push is never made twice.
49
50
  const COMMIT_STEPS: ReadonlyArray<Step> = [
51
+ // joshuafolkken/kit#3221: the default branch merged again before the commit, so the pull request
52
+ // opens on a branch that is current; a clean merge goes on without waking anyone. It runs before the
53
+ // gate (joshuafolkken/kit#3307): a merge after it moved the merge base the gate's record pins, so the
54
+ // pre-push hook could never reuse that record and re-ran the whole unit suite on every merged ship.
55
+ {
56
+ stage: STAGE.SYNC,
57
+ header: run_ship.SYNC_HEADER,
58
+ run: async () => await run_ship_sync.sync_stage(),
59
+ },
50
60
  {
51
61
  stage: STAGE.GATE,
52
62
  header: run_ship.GATE_HEADER,
53
- run: async () =>
54
- await run_ship_scoped.run_phases([
55
- run_ship_scoped.scoped_pair,
56
- async () => await josh(['gate']),
57
- ]),
63
+ run: async () => await run_ship_scoped.scoped_gate(),
58
64
  },
59
65
  {
60
66
  stage: STAGE.COMMIT,
@@ -69,7 +75,7 @@ const MERGE_STEPS: ReadonlyArray<Step> = [
69
75
  {
70
76
  stage: STAGE.FOLLOWUP,
71
77
  header: run_ship.FOLLOWUP_HEADER,
72
- run: async (args) => await josh(['followup', args.title, ...args.notify]),
78
+ run: async (args) => await run_ship_sync.followup_stage(args.title, args.notify),
73
79
  },
74
80
  {
75
81
  stage: STAGE.REPORT,
@@ -79,7 +85,8 @@ const MERGE_STEPS: ReadonlyArray<Step> = [
79
85
  ]
80
86
 
81
87
  // `--review` (joshuafolkken/kit#2427) puts the supervised round-1 review in front of the gate: it
82
- // launches the gate itself, so the gate stage that follows reuses that tree's green record. It also
88
+ // launches the gate itself, so the gate stage that follows reuses that tree's green record unless the
89
+ // sync stage between them merged the default branch and changed the tree. It also
83
90
  // puts the round-2 pass between the commit and the followup (joshuafolkken/kit#2489), so the PR opens
84
91
  // between the rounds and round 2 runs beside CI — a no-op when round 1 left no fix delta.
85
92
  const REVIEW_STEP: Step = {
@@ -0,0 +1,13 @@
1
+ // The text a `josh ship` supervisor records when a stage stops, written and read in one place so the
2
+ // retrospective's per-stage count never drifts from the writer (joshuafolkken/kit#3245).
3
+ const STOP_PATTERN = /^#\d+ (\S+) failed — /u
4
+
5
+ function format(issue: string, reason: string): string {
6
+ return `#${issue} ${reason} failed — pnpm josh ship --log ${issue}`
7
+ }
8
+
9
+ function reason_of(text: string): string | undefined {
10
+ return STOP_PATTERN.exec(text)?.[1]
11
+ }
12
+
13
+ export const run_ship_stop_text = { format, reason_of }
@@ -0,0 +1,128 @@
1
+ import { git_gh_pr_read } from '#scripts/gh/git-gh-pr-read'
2
+ import { is_merge_conflict } from '#scripts/gh/git-pr-checks-eval'
3
+ import { git_command } from '#scripts/git/git-command'
4
+ import { main_merge, type MergeOutcome } from '#scripts/git/main-merge'
5
+ import { josh_command, type JoshResult } from '#scripts/josh/josh-run'
6
+ import { error_text } from '#scripts/lib/error-message'
7
+ import { run_ship_scoped } from './run-ship-scoped'
8
+
9
+ // `josh ship` keeps the branch current with the default branch itself (joshuafolkken/kit#3221). The
10
+ // lane merged it once, before the hand-off, and nothing merged it again while the pull request opened
11
+ // and CI ran — so a default branch that moved meanwhile stopped the followup as `interrupted`, and a
12
+ // relaunched session spent its first turns finding out that the cause was a conflict. Most of those
13
+ // conflicts were not conflicts at all to git: merging the default branch again resolved them.
14
+ //
15
+ // So the merge happens twice more, mechanically: once before the commit (`sync_stage`), and once when
16
+ // the followup fails on a pull request GitHub reports `DIRTY` (`followup_stage`). Only a merge git
17
+ // itself cannot finish stops the ship, and it stops as `conflict`, naming the unmerged paths so the
18
+ // resumed session goes straight to them.
19
+
20
+ const should_forward_stderr = true
21
+ const SUCCESS_EXIT_CODE = 0
22
+ const FAILURE_EXIT_CODE = 1
23
+ // A default branch that keeps moving under a pull request is merged again at most this often per ship;
24
+ // past it the followup failure stands, so a busy default branch cannot hold a ship in a loop.
25
+ const MAX_MERGE_RETRIES = 2
26
+ const CONFLICT_NOTE = 'merging the default branch left unmerged paths:'
27
+ const DEFERRED_PREFIX = 'merge deferred to the followup: '
28
+ const CURRENT_SUFFIX = ' brings nothing in'
29
+ const MERGED_PREFIX = 'merged '
30
+
31
+ // A stage's result, plus the paths a stopped merge left unmerged — carried to the stop prompt.
32
+ interface StepResult extends JoshResult {
33
+ conflicts?: ReadonlyArray<string>
34
+ }
35
+
36
+ async function josh(argv: ReadonlyArray<string>): Promise<JoshResult> {
37
+ return await josh_command.josh_run(argv, should_forward_stderr)
38
+ }
39
+
40
+ // A merge that could not even be attempted — a fetch that failed — reads as a refusal: nothing was
41
+ // changed, and the followup still finds a real conflict on GitHub's side.
42
+ async function merge_outcome(): Promise<MergeOutcome> {
43
+ try {
44
+ return await main_merge.merge()
45
+ } catch (error) {
46
+ return { kind: 'refused', message: error_text.message_of(error) }
47
+ }
48
+ }
49
+
50
+ function conflict_result(files: ReadonlyArray<string>, lead: string): StepResult {
51
+ const out = [lead, CONFLICT_NOTE, ...files].filter((line) => line !== '').join('\n')
52
+
53
+ return { code: FAILURE_EXIT_CODE, out, conflicts: files }
54
+ }
55
+
56
+ function sync_note(outcome: Exclude<MergeOutcome, { kind: 'conflict' }>): string {
57
+ if (outcome.kind === 'refused') return `${DEFERRED_PREFIX}${outcome.message}`
58
+
59
+ return outcome.kind === 'current'
60
+ ? `${outcome.branch}${CURRENT_SUFFIX}`
61
+ : `${MERGED_PREFIX}${outcome.branch}`
62
+ }
63
+
64
+ // A refusal does not stop the commit: the guard refuses only uncommitted work the default branch also
65
+ // touches, and once that work is committed the followup merges again on a clean tree. A merge that changed
66
+ // the tree is checked by the gate stage that follows (joshuafolkken/kit#3307), so the commit never carries
67
+ // an unchecked merge and the gate's record names the merge base the push carries.
68
+ async function sync_stage(): Promise<StepResult> {
69
+ const outcome = await merge_outcome()
70
+
71
+ if (outcome.kind === 'conflict') return conflict_result(outcome.files, '')
72
+
73
+ return { code: SUCCESS_EXIT_CODE, out: sync_note(outcome) }
74
+ }
75
+
76
+ // Any read that fails answers "not conflicting": the followup failure then stands as it was.
77
+ async function is_conflicting(): Promise<boolean> {
78
+ try {
79
+ const state = await git_gh_pr_read.pr_get_merge_state(await git_command.branch())
80
+
81
+ return is_merge_conflict(state?.merge_state_status)
82
+ } catch {
83
+ return false
84
+ }
85
+ }
86
+
87
+ // The merged tree is gated before it is pushed (joshuafolkken/kit#3307): the push then carries the tree
88
+ // the gate's record describes, so the pre-push hook reuses it rather than re-running the unit suite.
89
+ async function push_merged(title: string): Promise<StepResult | undefined> {
90
+ const gate = await run_ship_scoped.scoped_gate()
91
+
92
+ if (gate.code !== SUCCESS_EXIT_CODE) return gate
93
+
94
+ const push = await josh(['git', '-y', '--skip-commit', '--skip-pr', title])
95
+
96
+ return push.code === SUCCESS_EXIT_CODE ? undefined : push
97
+ }
98
+
99
+ // The result to stop on, or `undefined` once a clean merge is pushed and the followup can wait again.
100
+ async function merge_again(title: string, failed: JoshResult): Promise<StepResult | undefined> {
101
+ if (!(await is_conflicting())) return failed
102
+
103
+ const outcome = await merge_outcome()
104
+
105
+ if (outcome.kind === 'conflict') return conflict_result(outcome.files, failed.out)
106
+ if (outcome.kind !== 'merged') return failed
107
+
108
+ return await push_merged(title)
109
+ }
110
+
111
+ async function followup_stage(
112
+ title: string,
113
+ notify: ReadonlyArray<string>,
114
+ retries = 0,
115
+ ): Promise<StepResult> {
116
+ const result = await josh(['followup', title, ...notify])
117
+
118
+ if (result.code === SUCCESS_EXIT_CODE || retries >= MAX_MERGE_RETRIES) return result
119
+
120
+ const stop = await merge_again(title, result)
121
+
122
+ return stop ?? (await followup_stage(title, notify, retries + 1))
123
+ }
124
+
125
+ const run_ship_sync = { CONFLICT_NOTE, MAX_MERGE_RETRIES, followup_stage, sync_stage }
126
+
127
+ export type { StepResult }
128
+ export { run_ship_sync }
@@ -10,8 +10,10 @@
10
10
  // composite ends at the failure, and the report names the step that failed so the run reads only it.
11
11
 
12
12
  const PREFLIGHT_HEADER = '=== preflight ==='
13
+ const PRE_DETACH_HEADER = '=== pre-detach checks ==='
13
14
  const REVIEW_HEADER = '=== review ==='
14
15
  const GATE_HEADER = '=== gate ==='
16
+ const SYNC_HEADER = '=== sync origin/main ==='
15
17
  const COMMIT_HEADER = '=== commit/push/PR ==='
16
18
  const ROUND_TWO_HEADER = '=== round-2 review ==='
17
19
  const FOLLOWUP_HEADER = '=== followup ==='
@@ -29,6 +31,8 @@ interface ShipSection {
29
31
  header: string
30
32
  body: string
31
33
  code: number
34
+ // The paths a stopped merge left unmerged (joshuafolkken/kit#3221), handed to the stop prompt.
35
+ conflicts?: ReadonlyArray<string>
32
36
  }
33
37
 
34
38
  function section_report(section: ShipSection): string {
@@ -65,10 +69,12 @@ const run_ship = {
65
69
  FOLLOWUP_HEADER,
66
70
  GATE_HEADER,
67
71
  PREFLIGHT_HEADER,
72
+ PRE_DETACH_HEADER,
68
73
  REPORT_HEADER,
69
74
  REVIEW_HEADER,
70
75
  ROUND_TWO_HEADER,
71
76
  SKIPPED_BODY,
77
+ SYNC_HEADER,
72
78
  STOPPED_PREFIX,
73
79
  exit_code,
74
80
  failed_section,
@@ -3,7 +3,7 @@ import { git_stash } from '#scripts/git/stash/git-stash'
3
3
  import { lane_close } from '#scripts/lane/lane-close'
4
4
  import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
5
5
  import { error_text } from '#scripts/lib/error-message'
6
- import { run_hold } from './run-hold'
6
+ import { run_hold } from '#scripts/run/hold/run-hold'
7
7
  import { run_tidy, type Outcome, type Verdict } from './run-tidy'
8
8
 
9
9
  // The lane half of `josh run:tidy` (joshuafolkken/kit#2701): each lane whose issue merged, read for