@joshuafolkken/kit 1.1178.0 → 1.1240.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/settings.json +2 -2
  3. package/.claude/skills/dependency-update/SKILL.md +4 -8
  4. package/.claude/skills/epic-commands/SKILL.md +2 -2
  5. package/.claude/skills/epic-commands/epic-bundle.md +1 -1
  6. package/.claude/skills/workflow-commands/SKILL.md +2 -4
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +7 -3
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +23 -2
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +9 -4
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +21 -13
  13. package/.claude/skills/workflow-commands/chain-rule.md +1 -1
  14. package/.claude/skills/workflow-commands/delegation.md +2 -2
  15. package/.claude/skills/workflow-commands/followup.md +10 -0
  16. package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
  17. package/.claude/skills/workflow-commands/halfrun.md +1 -1
  18. package/.claude/skills/workflow-commands/issue-scout.md +1 -1
  19. package/.claude/skills/workflow-commands/kickoff.md +2 -1
  20. package/.claude/skills/workflow-commands/latest-gate.md +1 -1
  21. package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
  22. package/.claude/skills/workflow-commands/pre-gate-cut.md +6 -1
  23. package/.claude/skills/workflow-commands/prerequisite.md +2 -2
  24. package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
  25. package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
  26. package/.codex/hooks.json +2 -2
  27. package/.cursorrules +6 -41
  28. package/.github/actions/setup-node/action.yml +1 -1
  29. package/.github/workflows/auto-tag.yml +3 -4
  30. package/.github/workflows/ci.yml +32 -31
  31. package/.github/workflows/dependabot-auto-merge.yml +13 -6
  32. package/.github/workflows/publish.yml +50 -4
  33. package/.github/workflows/sonar-qube.yml +3 -0
  34. package/.pnpmfile.mjs +4 -3
  35. package/.prettierignore +8 -2
  36. package/CLAUDE.md +18 -20
  37. package/README.md +44 -38
  38. package/SECURITY.md +15 -1
  39. package/dist/CLAUDE.md +18 -20
  40. package/dist/commands/document-section-cli.js +734 -0
  41. package/dist/hooks/audit-provision.js +346 -0
  42. package/dist/hooks/batch-guard.js +5 -4
  43. package/dist/hooks/{chunk-G6KRCKEC.js → chunk-4TZVB44A.js} +496 -378
  44. package/dist/hooks/chunk-5VNDSNG3.js +12 -0
  45. package/dist/hooks/chunk-CDY4NFQG.js +15 -0
  46. package/dist/hooks/chunk-ELCN5LCH.js +135 -0
  47. package/dist/hooks/{chunk-VQLLNFNT.js → chunk-F3ELQ2AF.js} +3 -3
  48. package/dist/hooks/{chunk-I4XSL4TT.js → chunk-GPTFMK5E.js} +1 -1
  49. package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
  50. package/dist/hooks/{chunk-UAGLLIGZ.js → chunk-KHGB5S3Y.js} +7 -6
  51. package/dist/hooks/{chunk-GDS5N2KB.js → chunk-KHX4U3BG.js} +735 -683
  52. package/dist/hooks/{chunk-VESKRQOF.js → chunk-KRBERBBI.js} +3 -3
  53. package/dist/hooks/{chunk-DO6NWWHU.js → chunk-QVSWU24O.js} +4 -4
  54. package/dist/hooks/{chunk-S6X6WYKR.js → chunk-WK2XSHSI.js} +1643 -923
  55. package/dist/hooks/{chunk-ZAL3NRGL.js → chunk-XZYKEM6J.js} +25 -10
  56. package/dist/hooks/{chunk-32GQTL4F.js → chunk-ZQGD4GBY.js} +4 -2
  57. package/dist/hooks/codex-hook-adapter.js +14 -12
  58. package/dist/hooks/duplicate-read-guard.js +6 -5
  59. package/dist/hooks/format-edited.js +4 -3
  60. package/dist/hooks/inputs.json +203 -179
  61. package/dist/hooks/investigation-guard.js +6 -5
  62. package/dist/hooks/pretool-guard.js +12 -10
  63. package/dist/hooks/session-lang.js +2 -4
  64. package/dist/hooks/stop-guard.js +66 -72
  65. package/dist/josh.js +1088 -78
  66. package/dist/version/index.d.ts +7 -2
  67. package/dist/version/index.js +152 -75
  68. package/eslint/base.js +23 -1
  69. package/eslint/rules/code-quality.js +3 -0
  70. package/eslint/rules/early-return-one-liner.js +98 -0
  71. package/eslint/rules/unicorn.js +16 -1
  72. package/package.json +7 -6
  73. package/pnpm-workspace.yaml +9 -4
  74. package/prettier/basic.d.ts +3 -1
  75. package/prettier/index.d.ts +3 -1
  76. package/prettier/static.d.ts +3 -1
  77. package/prompts/coding-standards.md +1 -1
  78. package/prompts/collaboration-workflow/file-edits.md +4 -4
  79. package/prompts/collaboration-workflow/glossary.md +1 -1
  80. package/prompts/collaboration-workflow/issue-template.md +1 -0
  81. package/prompts/collaboration-workflow/operating-rules.md +17 -9
  82. package/prompts/collaboration-workflow/overview.md +2 -2
  83. package/prompts/collaboration-workflow/principles.md +14 -12
  84. package/prompts/collaboration-workflow/report-format.md +1 -1
  85. package/prompts/collaboration-workflow/residency.md +1 -1
  86. package/prompts/collaboration-workflow/rule-delivery.md +6 -6
  87. package/prompts/collaboration-workflow/shell-body.md +6 -6
  88. package/prompts/collaboration-workflow/turn-batching.md +1 -1
  89. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
  90. package/prompts/collaboration-workflow/wip-cap.md +10 -10
  91. package/prompts/review-rubric.md +1 -2
  92. package/prompts/review.md +1 -1
  93. package/scripts/agent/agent-headless.ts +29 -0
  94. package/scripts/agent/agent-launch-environment.ts +6 -2
  95. package/scripts/agent/codex-agent-argv.ts +2 -1
  96. package/scripts/auto-ok/auto-ok-cli.ts +1 -1
  97. package/scripts/backlog/backlog-arrival.ts +3 -3
  98. package/scripts/backlog/backlog-budget.ts +1 -1
  99. package/scripts/backlog/backlog-drive-args.ts +135 -0
  100. package/scripts/backlog/backlog-drive-cli.ts +11 -137
  101. package/scripts/backlog/backlog-drive-launch.ts +39 -0
  102. package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
  103. package/scripts/backlog/backlog-drive-named.ts +3 -3
  104. package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
  105. package/scripts/backlog/backlog-drive-owner.ts +1 -1
  106. package/scripts/backlog/backlog-drive-restore.ts +1 -1
  107. package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
  108. package/scripts/backlog/backlog-drive.ts +1 -1
  109. package/scripts/backlog/backlog-next.ts +16 -5
  110. package/scripts/backlog/backlog-offer-cli.ts +6 -6
  111. package/scripts/backlog/backlog-offer.ts +1 -1
  112. package/scripts/backlog/backlog-plan-cli.ts +1 -1
  113. package/scripts/backlog/backlog-pool.ts +57 -11
  114. package/scripts/backlog/backlog-ready.ts +1 -1
  115. package/scripts/backlog/backlog-scope.ts +1 -1
  116. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  117. package/scripts/backlog/backlog-stalled.ts +1 -1
  118. package/scripts/build/build-codex-hooks.ts +3 -1
  119. package/scripts/build/build-commands.ts +66 -0
  120. package/scripts/build/build-hooks.ts +2 -1
  121. package/scripts/build/build-library.ts +12 -3
  122. package/scripts/build/build.ts +70 -0
  123. package/scripts/build/local-bin.ts +3 -13
  124. package/scripts/delegation/investigation-reads.ts +3 -1
  125. package/scripts/discovery/repo-origin.ts +5 -5
  126. package/scripts/doctor/doctor-consumer.ts +2 -2
  127. package/scripts/doctor/doctor-io.ts +2 -3
  128. package/scripts/document/capped-print-part.ts +30 -0
  129. package/scripts/document/capped-print.ts +3 -4
  130. package/scripts/document/document-byte-budget.ts +11 -2
  131. package/scripts/document/document-scan.ts +117 -0
  132. package/scripts/document/document-section.ts +143 -16
  133. package/scripts/document/entry-read-budget.ts +5 -3
  134. package/scripts/document/entry-read-set.ts +10 -89
  135. package/scripts/document/read-set-cost.ts +98 -0
  136. package/scripts/epic/epic-add-body.ts +1 -0
  137. package/scripts/epic/epic-audit-checks.ts +1 -1
  138. package/scripts/epic/epic-audit-rationale.ts +4 -3
  139. package/scripts/epic/epic-bundle-cli.ts +14 -3
  140. package/scripts/epic/epic-bundle-poll.ts +41 -0
  141. package/scripts/epic/epic-busy.ts +1 -1
  142. package/scripts/epic/epic-candidate-confirm.ts +3 -2
  143. package/scripts/epic/epic-classify.ts +1 -1
  144. package/scripts/epic/epic-cli-argv.ts +78 -46
  145. package/scripts/epic/epic-cli-remove.ts +11 -19
  146. package/scripts/epic/epic-cli.ts +74 -73
  147. package/scripts/epic/epic-close-comment.ts +1 -1
  148. package/scripts/epic/epic-close.ts +1 -1
  149. package/scripts/epic/epic-cross-repo.ts +5 -5
  150. package/scripts/epic/epic-graph.ts +1 -1
  151. package/scripts/epic/epic-index.ts +1 -1
  152. package/scripts/epic/epic-issue.ts +2 -6
  153. package/scripts/epic/epic-lane-offer.ts +2 -2
  154. package/scripts/epic/epic-next.ts +29 -42
  155. package/scripts/epic/epic-solo-stale.ts +1 -1
  156. package/scripts/epic/epic-triage.ts +8 -4
  157. package/scripts/epic/epic-validate.ts +1 -1
  158. package/scripts/followup/git-followup-finish.ts +2 -2
  159. package/scripts/followup/git-followup-label.ts +2 -2
  160. package/scripts/followup/git-followup-merged.ts +2 -2
  161. package/scripts/followup/git-pr-followup-checks.ts +142 -0
  162. package/scripts/followup/git-pr-followup.ts +4 -129
  163. package/scripts/gate/core-admission.ts +162 -0
  164. package/scripts/gate/core-budget.ts +190 -68
  165. package/scripts/gate/gate-plan.ts +80 -7
  166. package/scripts/gate/gate-report.ts +28 -9
  167. package/scripts/gate/gate-skip.ts +38 -7
  168. package/scripts/gate/hook-gate-reuse.ts +12 -10
  169. package/scripts/gate/machine-capacity.ts +175 -0
  170. package/scripts/gate/pre-commit-type-check.ts +4 -1
  171. package/scripts/gate/project-checks.ts +6 -9
  172. package/scripts/gate/type-check-step.ts +2 -3
  173. package/scripts/gate/verification-gate.ts +34 -10
  174. package/scripts/gh/git-gh-api-path.ts +2 -2
  175. package/scripts/gh/git-gh-issue-rest.ts +3 -3
  176. package/scripts/gh/git-gh-issue-write.ts +53 -7
  177. package/scripts/gh/git-gh-pr-read.ts +64 -19
  178. package/scripts/gh/git-gh-pr-rest.ts +1 -1
  179. package/scripts/gh/git-gh-pr.ts +5 -1
  180. package/scripts/gh/git-pr-ai-review.ts +1 -1
  181. package/scripts/gh/git-pr-checks-eval.ts +1 -3
  182. package/scripts/gh/git-pr-checks-parse.ts +1 -1
  183. package/scripts/gh/git-pr-checks-watch.ts +6 -5
  184. package/scripts/gh/git-pr-checks.ts +7 -5
  185. package/scripts/gh/git-pr-coderabbit.ts +1 -1
  186. package/scripts/gh/git-pr-error.ts +1 -3
  187. package/scripts/gh/git-pr.ts +9 -13
  188. package/scripts/gh/github-issue-url.ts +2 -2
  189. package/scripts/git/constants.ts +2 -6
  190. package/scripts/git/git-command.ts +20 -218
  191. package/scripts/git/git-diff-reads.ts +225 -0
  192. package/scripts/git/git-error.ts +4 -12
  193. package/scripts/git/git-pre-push-hook.ts +139 -0
  194. package/scripts/git/git-prompt.ts +2 -6
  195. package/scripts/git/git-spawn-sync.ts +3 -1
  196. package/scripts/git/git-spawn.ts +1 -1
  197. package/scripts/git/git-staging.ts +4 -6
  198. package/scripts/git/git-status.ts +3 -9
  199. package/scripts/hooks/codex-hook-adapter.ts +1 -1
  200. package/scripts/hooks/edited-cspell.ts +2 -4
  201. package/scripts/hooks/format-edited-file.ts +3 -12
  202. package/scripts/hooks/pre-push-unit.ts +31 -9
  203. package/scripts/hooks/reserved-run.ts +2 -2
  204. package/scripts/hooks/run-hook.sh +5 -4
  205. package/scripts/hooks/stop-guard.ts +5 -3
  206. package/scripts/init/init-actions.ts +0 -5
  207. package/scripts/init/init-ai-copy.ts +13 -2
  208. package/scripts/init/init-logic-json-merge.ts +19 -0
  209. package/scripts/init/init-logic-secretlint.ts +7 -7
  210. package/scripts/init/init-logic-workspace.ts +41 -2
  211. package/scripts/init/init-logic.ts +16 -26
  212. package/scripts/init/init.ts +10 -16
  213. package/scripts/init/initial-commit.ts +4 -4
  214. package/scripts/init/kit-development-versions.ts +40 -0
  215. package/scripts/init/kit-setup-state.ts +2 -2
  216. package/scripts/init/package-manager-pin.ts +35 -0
  217. package/scripts/init/start-exec.ts +68 -19
  218. package/scripts/init/start-setup-pr.ts +9 -9
  219. package/scripts/init/start-steps.ts +6 -10
  220. package/scripts/issue/defect-rate-cli.ts +1 -1
  221. package/scripts/issue/git-next-issues.ts +1 -1
  222. package/scripts/issue/issue-auto-ok.ts +32 -7
  223. package/scripts/issue/issue-backlinks-cli.ts +2 -2
  224. package/scripts/issue/issue-backlinks.ts +9 -9
  225. package/scripts/issue/issue-comment-cli.ts +19 -13
  226. package/scripts/issue/issue-file-cli.ts +72 -22
  227. package/scripts/issue/issue-file.ts +29 -1
  228. package/scripts/issue/issue-labels.ts +13 -0
  229. package/scripts/issue/issue-release-cli.ts +155 -0
  230. package/scripts/issue/issue-release.ts +58 -0
  231. package/scripts/issue/issue-scout-closed.ts +1 -1
  232. package/scripts/issue/issue-state-cli.ts +30 -74
  233. package/scripts/josh/josh-command-map.ts +2 -0
  234. package/scripts/josh/josh-command-types.ts +14 -0
  235. package/scripts/josh/josh-commands-ai.ts +4 -4
  236. package/scripts/josh/josh-commands-backlog.ts +4 -1
  237. package/scripts/josh/josh-commands-development.ts +18 -0
  238. package/scripts/josh/josh-commands-document.ts +1 -0
  239. package/scripts/josh/josh-commands-issue.ts +8 -1
  240. package/scripts/josh/josh-commands-lane.ts +24 -1
  241. package/scripts/josh/josh-commands-maintenance.ts +6 -6
  242. package/scripts/josh/josh-commands-run.ts +12 -7
  243. package/scripts/josh/josh-in-process.ts +28 -9
  244. package/scripts/josh/josh-logic.ts +34 -15
  245. package/scripts/josh/process-identity.ts +1 -3
  246. package/scripts/josh/session-language-cli.ts +3 -4
  247. package/scripts/lane/lane-await.ts +5 -1
  248. package/scripts/lane/lane-child-invocation.ts +2 -2
  249. package/scripts/lane/lane-cli.ts +12 -4
  250. package/scripts/lane/lane-dispatch.ts +14 -10
  251. package/scripts/lane/lane-environment.ts +5 -1
  252. package/scripts/lane/lane-handoff.ts +1 -1
  253. package/scripts/lane/lane-install.ts +2 -13
  254. package/scripts/lane/lane-launch-cli.ts +15 -1
  255. package/scripts/lane/lane-ledger.ts +145 -0
  256. package/scripts/lane/lane-leftover.ts +44 -30
  257. package/scripts/lane/lane-load.ts +108 -0
  258. package/scripts/lane/lane-measure-cli.ts +149 -0
  259. package/scripts/lane/lane-occupancy.ts +3 -0
  260. package/scripts/lane/lane-open.ts +5 -5
  261. package/scripts/lane/lane-paths.ts +13 -0
  262. package/scripts/lane/lane-reap.ts +1 -3
  263. package/scripts/lane/lane-registry.ts +11 -0
  264. package/scripts/lane/lane-relaunch.ts +1 -1
  265. package/scripts/lane/lane-stats.ts +224 -0
  266. package/scripts/lane/lane-stray.ts +140 -0
  267. package/scripts/lane/lane-vacant.ts +36 -0
  268. package/scripts/lane/openai-lane-ship.ts +2 -2
  269. package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
  270. package/scripts/lane/openai-lane-supervisor.ts +7 -165
  271. package/scripts/lane/openai-review-broker.ts +2 -4
  272. package/scripts/lib/ancestor-directories.ts +35 -0
  273. package/scripts/lib/animation-helpers.ts +2 -2
  274. package/scripts/lib/buffered-process.ts +3 -7
  275. package/scripts/lib/cli-flags.ts +71 -1
  276. package/scripts/lib/spawn-exit.ts +2 -2
  277. package/scripts/lib/timeouts.ts +35 -2
  278. package/scripts/lines/line-budget.ts +4 -6
  279. package/scripts/notify/telegram-test.ts +6 -0
  280. package/scripts/observations/observation-ledger-line.ts +6 -6
  281. package/scripts/observations/observation-ledger-migrate.ts +55 -4
  282. package/scripts/observations/observation-ledger.ts +26 -7
  283. package/scripts/overrides/lockfile-overrides.ts +1 -1
  284. package/scripts/overrides/overrides-logic.ts +1 -1
  285. package/scripts/propagate/propagate-git.ts +1 -1
  286. package/scripts/propagate/propagate-publish.ts +2 -2
  287. package/scripts/propagate/propagate-steps.ts +20 -15
  288. package/scripts/propagate/propagate.ts +36 -27
  289. package/scripts/refactor/import-graph.ts +18 -1
  290. package/scripts/refactor/refactor-lint.ts +4 -4
  291. package/scripts/release/github-release-environment.ts +1 -0
  292. package/scripts/release/github-release-runs.ts +115 -0
  293. package/scripts/release/github-release.ts +13 -67
  294. package/scripts/repo/repository-labels.ts +2 -2
  295. package/scripts/repo/required-checks-logic.ts +1 -3
  296. package/scripts/review/live-evidence.ts +2 -4
  297. package/scripts/review/review-brief-cli.ts +1 -1
  298. package/scripts/review/review-finding-ledger.ts +1 -1
  299. package/scripts/review/review-findings-cli.ts +1 -3
  300. package/scripts/rules/decision-oracle-batch.ts +128 -0
  301. package/scripts/rules/decision-oracle-tokens.ts +21 -0
  302. package/scripts/rules/decision-oracle.ts +8 -120
  303. package/scripts/rules/delivered-rules.ts +26 -182
  304. package/scripts/rules/direct-filing.ts +1 -1
  305. package/scripts/rules/early-heartbeat.ts +3 -3
  306. package/scripts/rules/git-argv.ts +1 -1
  307. package/scripts/rules/git-force.ts +1 -1
  308. package/scripts/rules/implementation-cut.ts +2 -2
  309. package/scripts/rules/index-guard.ts +1 -1
  310. package/scripts/rules/issue-comments.ts +118 -0
  311. package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
  312. package/scripts/rules/lane-background.ts +2 -2
  313. package/scripts/rules/lane-park.ts +18 -0
  314. package/scripts/rules/lane-split-park.ts +128 -0
  315. package/scripts/rules/oracle-firing.ts +1 -1
  316. package/scripts/rules/poll-loop.ts +1 -1
  317. package/scripts/rules/pre-gate-cut.ts +1 -1
  318. package/scripts/rules/protected-files.ts +59 -4
  319. package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
  320. package/scripts/rules/shell-body-trigger.ts +1 -1
  321. package/scripts/rules/stop-rules.ts +18 -7
  322. package/scripts/rules/test-declared-commit.ts +4 -4
  323. package/scripts/rules/wip-cap.ts +63 -0
  324. package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
  325. package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
  326. package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
  327. package/scripts/run/{run-carry.ts → carry/run-carry.ts} +1 -1
  328. package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
  329. package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +18 -5
  330. package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
  331. package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
  332. package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
  333. package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +1 -0
  334. package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
  335. package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
  336. package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +20 -2
  337. package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
  338. package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
  339. package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
  340. package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
  341. package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
  342. package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
  343. package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
  344. package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
  345. package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
  346. package/scripts/run/run-ending.ts +2 -2
  347. package/scripts/run/run-halfrun-resume.ts +6 -1
  348. package/scripts/run/run-headless.ts +1 -1
  349. package/scripts/run/run-invocation.ts +85 -84
  350. package/scripts/run/run-liveness.ts +1 -1
  351. package/scripts/run/run-parent-cut-hook.ts +1 -1
  352. package/scripts/run/run-preflight.ts +6 -6
  353. package/scripts/run/run-prep-cli.ts +1 -1
  354. package/scripts/run/run-prrun-resume.ts +1 -1
  355. package/scripts/run/run-report-cli.ts +4 -4
  356. package/scripts/run/run-report.ts +2 -2
  357. package/scripts/run/run-status-cli.ts +1 -1
  358. package/scripts/run/run-step-cli.ts +3 -3
  359. package/scripts/run/run-step.ts +7 -4
  360. package/scripts/run/run-stop-notify.ts +1 -1
  361. package/scripts/run/run-stranded-detect.ts +4 -4
  362. package/scripts/run/run-stranded.ts +2 -2
  363. package/scripts/run/run-watcher-guard-cli.ts +1 -1
  364. package/scripts/run/run-watcher-guard.ts +5 -3
  365. package/scripts/run/run-watcher-hook.ts +1 -1
  366. package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +2 -2
  367. package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
  368. package/scripts/run/ship/run-ship-lock.ts +50 -0
  369. package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
  370. package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
  371. package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +4 -3
  372. package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
  373. package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
  374. package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +11 -12
  375. package/scripts/run/ship/run-ship-stop-text.ts +13 -0
  376. package/scripts/run/{run-ship-sync.ts → ship/run-ship-sync.ts} +25 -11
  377. package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
  378. package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +25 -11
  379. package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +58 -7
  380. package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +59 -8
  381. package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
  382. package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +13 -15
  383. package/scripts/run/wake/run-wake-restart.ts +58 -0
  384. package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
  385. package/scripts/run/{run-wake.ts → wake/run-wake.ts} +16 -65
  386. package/scripts/safe-chain/preinstall-command.ts +20 -21
  387. package/scripts/safe-chain/project-config.ts +19 -15
  388. package/scripts/sync/sync-ai-files.ts +1 -1
  389. package/scripts/sync/sync-configs.ts +2 -1
  390. package/scripts/sync/sync.ts +15 -2
  391. package/scripts/test/unit-worker-share.ts +1 -0
  392. package/scripts/time-runtime/time-background.ts +5 -1
  393. package/scripts/time-runtime/time-instant.ts +3 -1
  394. package/scripts/time-runtime/time-spans.ts +6 -139
  395. package/scripts/time-runtime/time-tool-call.ts +160 -0
  396. package/scripts/version/effective-upstream.ts +2 -20
  397. package/scripts/version/latest-corepack.ts +124 -93
  398. package/scripts/version/latest-regression.ts +1 -1
  399. package/scripts/version/latest-update.ts +4 -4
  400. package/scripts/version/npm-registry.ts +20 -5
  401. package/scripts/version/publishable-range-check.ts +2 -2
  402. package/scripts/version/publishable-range.ts +1 -1
  403. package/scripts/version/release-age.ts +48 -38
  404. package/scripts/version/release-hold.ts +1 -1
  405. package/scripts/version/safe-chain-version-update.ts +40 -0
  406. package/scripts/version/upgrade-shell-command.ts +48 -8
  407. package/scripts/version/version-check-logic.ts +23 -14
  408. package/scripts/version/version-commands.ts +23 -11
  409. package/scripts/version/version-remote.ts +28 -13
  410. package/templates/pnpm-workspace.basic.yaml +4 -0
  411. package/templates/workflows/dependabot-auto-merge.yml +5 -5
  412. package/templates/workflows/github-release.yml +6 -0
  413. package/scripts/build/build-config-merge.ts +0 -27
  414. package/scripts/build/build-managed-marker.ts +0 -27
  415. package/scripts/build/build-self-sync-guard.ts +0 -27
  416. package/scripts/build/build-version.ts +0 -26
  417. package/scripts/version/preinstall-version-update.ts +0 -86
  418. /package/scripts/git/{schemas.ts → git-schemas.ts} +0 -0
  419. /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
  420. /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
  421. /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
  422. /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
  423. /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
  424. /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
  425. /package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +0 -0
  426. /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
  427. /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
  428. /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
  429. /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
  430. /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
  431. /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
  432. /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
  433. /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
  434. /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
  435. /package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +0 -0
  436. /package/scripts/run/{run-ship-pre-detach.ts → ship/run-ship-pre-detach.ts} +0 -0
  437. /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
  438. /package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +0 -0
  439. /package/scripts/run/{run-ship.ts → ship/run-ship.ts} +0 -0
  440. /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
  441. /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
  442. /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
@@ -23,7 +23,9 @@ import { init_file_action } from './init-file-action'
23
23
  import { init_install } from './init-install'
24
24
  import { init_logic } from './init-logic'
25
25
  import { PACKAGE_DIR, PROJECT_ROOT } from './init-paths'
26
+ import { kit_development_versions } from './kit-development-versions'
26
27
  import { kit_setup_state } from './kit-setup-state'
28
+ import { package_manager_pin } from './package-manager-pin'
27
29
  import { project_profile, type ProjectProfile, type ProjectShape } from './project-profile'
28
30
 
29
31
  const PACKAGE_JSON = 'package.json'
@@ -51,7 +53,10 @@ function resolve_kit_package_manager(manifest: unknown): string | undefined {
51
53
  }
52
54
 
53
55
  function get_kit_package_manager(): string | undefined {
54
- return resolve_kit_package_manager(init_actions.read_package_json(PACKAGE_JSON))
56
+ return package_manager_pin.choose(
57
+ resolve_kit_package_manager(init_actions.read_package_json(PACKAGE_JSON)),
58
+ process.env['npm_config_user_agent'],
59
+ )
55
60
  }
56
61
 
57
62
  function get_kit_development_engines(): Record<string, unknown> {
@@ -81,21 +86,10 @@ function tool_dependency_names(has_git: boolean): ReadonlyArray<string> {
81
86
 
82
87
  // Versions mirror kit's own devDependencies, so the consumer runs the toolchain kit is verified with.
83
88
  function get_toolchain_development_dependencies(has_git: boolean): Record<string, string> {
84
- const manifest = z
85
- .object({
86
- peerDependencies: z.record(z.string(), z.string()),
87
- devDependencies: z.record(z.string(), z.string()),
88
- })
89
- .parse(init_actions.read_package_json(PACKAGE_JSON))
90
- const names = [...Object.keys(manifest.peerDependencies), ...tool_dependency_names(has_git)]
91
- const entries = names.map((name): [string, string] => {
92
- const version = manifest.devDependencies[name]
93
- if (version === undefined) throw new Error(`Missing development version for ${name}`)
94
-
95
- return [name, version]
96
- })
97
-
98
- return Object.fromEntries(entries)
89
+ return kit_development_versions.versions_of([
90
+ ...kit_development_versions.peer_names(),
91
+ ...tool_dependency_names(has_git),
92
+ ])
99
93
  }
100
94
 
101
95
  function apply_dependency_merges(content: string, has_git = true): string {
@@ -6,17 +6,17 @@ import { start_exec } from './start-exec'
6
6
  const DEFAULT_BRANCH = 'main'
7
7
 
8
8
  function create_repository(root: string): void {
9
- start_exec.run('git', ['init', `--initial-branch=${DEFAULT_BRANCH}`], root)
9
+ start_exec.git_run(['init', `--initial-branch=${DEFAULT_BRANCH}`], root)
10
10
  }
11
11
 
12
12
  function commit_all(root: string): void {
13
- start_exec.run('git', ['add', '--all'], root)
13
+ start_exec.git_run(['add', '--all'], root)
14
14
  // The first commit of a new repository has no branch to come from, so the hook that keeps commits
15
15
  // off main — installed by the initialize step in a full project — would refuse the only commit
16
16
  // that has to land there.
17
- start_exec.run('git', ['commit', '--no-verify', '--message', 'Initial commit'], root)
17
+ start_exec.git_run(['commit', '--no-verify', '--message', 'Initial commit'], root)
18
18
  // A repository created by an older `git init` may name its unborn branch `master`.
19
- start_exec.run('git', ['branch', '--move', '--force', DEFAULT_BRANCH], root)
19
+ start_exec.git_run(['branch', '--move', '--force', DEFAULT_BRANCH], root)
20
20
  }
21
21
 
22
22
  const initial_commit = { create_repository, commit_all }
@@ -0,0 +1,40 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { z } from 'zod'
3
+ import { package_path } from './init-paths'
4
+
5
+ const PACKAGE_JSON = 'package.json'
6
+
7
+ const kit_manifest_schema = z.object({
8
+ peerDependencies: z.record(z.string(), z.string()).default({}),
9
+ devDependencies: z.record(z.string(), z.string()),
10
+ })
11
+
12
+ type KitManifest = z.infer<typeof kit_manifest_schema>
13
+
14
+ function read_kit_manifest(): KitManifest {
15
+ const content = readFileSync(package_path(PACKAGE_JSON), 'utf8')
16
+
17
+ return kit_manifest_schema.parse(JSON.parse(content))
18
+ }
19
+
20
+ // The version kit's own devDependencies carry for each name, so a consumer runs the toolchain kit
21
+ // is verified with rather than a copy of it that drifts.
22
+ function versions_of(names: ReadonlyArray<string>): Record<string, string> {
23
+ const { devDependencies: versions } = read_kit_manifest()
24
+ const entries = names.map((name): [string, string] => {
25
+ const version = versions[name]
26
+ if (version === undefined) throw new Error(`Missing development version for ${name}`)
27
+
28
+ return [name, version]
29
+ })
30
+
31
+ return Object.fromEntries(entries)
32
+ }
33
+
34
+ function peer_names(): ReadonlyArray<string> {
35
+ return Object.keys(read_kit_manifest().peerDependencies)
36
+ }
37
+
38
+ const kit_development_versions = { peer_names, versions_of }
39
+
40
+ export { kit_development_versions }
@@ -8,13 +8,13 @@ const START_HINT =
8
8
  // reached the branch, so `josh start` has no setup pull request to open (joshuafolkken/kit#2816).
9
9
  function is_kit_committed(root: string): boolean {
10
10
  // `./` resolves the path from `root` rather than from the repository's top level.
11
- const manifest = start_exec.read_output('git', ['show', 'HEAD:./package.json'], root)
11
+ const manifest = start_exec.git_read(['show', 'HEAD:./package.json'], root)
12
12
 
13
13
  return manifest?.includes(`"${KIT_PACKAGE_NAME}"`) ?? false
14
14
  }
15
15
 
16
16
  function is_git_work_tree(root: string): boolean {
17
- return start_exec.succeeds('git', ['rev-parse', '--is-inside-work-tree'], root)
17
+ return start_exec.git_succeeds(['rev-parse', '--is-inside-work-tree'], root)
18
18
  }
19
19
 
20
20
  // The line `josh init` ends with in a Git repository whose checkout does not hold kit yet: the setup is
@@ -0,0 +1,35 @@
1
+ import { package_manager_version } from '#scripts/version/package-manager-version'
2
+ import semver from 'semver'
3
+
4
+ // pnpm sets `npm_config_user_agent` to `pnpm/<version> npm/? node/<version> <platform> <arch>` for
5
+ // every script and `pnpm exec` it runs, so the pnpm that invoked `josh init` is readable from it.
6
+ const PNPM_USER_AGENT_REGEX = /^pnpm\/(\S+)/u
7
+
8
+ function running_pnpm_version(user_agent = ''): string | undefined {
9
+ const version = PNPM_USER_AGENT_REGEX.exec(user_agent)?.[1]
10
+
11
+ return version === undefined ? undefined : (semver.valid(version) ?? undefined)
12
+ }
13
+
14
+ function is_older_than(kit_pin: string, version: string): boolean {
15
+ const kit_version = semver.valid(package_manager_version.extract_pnpm_pin(kit_pin) ?? '')
16
+
17
+ return kit_version !== null && semver.lt(kit_version, version)
18
+ }
19
+
20
+ // Never pin a project below the pnpm installing it: the lockfile that pnpm just wrote can name a
21
+ // version an older pnpm fails to resolve, so `pnpm install` under kit's older pin breaks the
22
+ // first `josh init` (joshuafolkken/kit#3367). The kit pin keeps its integrity suffix when it wins.
23
+ function choose(kit_pin: string | undefined, user_agent: string | undefined): string | undefined {
24
+ const running = running_pnpm_version(user_agent)
25
+ if (kit_pin === undefined || running === undefined) return kit_pin
26
+
27
+ return is_older_than(kit_pin, running) ? `pnpm@${running}` : kit_pin
28
+ }
29
+
30
+ const package_manager_pin = {
31
+ running_pnpm_version,
32
+ choose,
33
+ }
34
+
35
+ export { package_manager_pin }
@@ -1,39 +1,80 @@
1
1
  import { git_gh_exec } from '#scripts/gh/git-gh-exec'
2
2
  import { git_location_environment } from '#scripts/git/git-location-environment'
3
+ import { git_spawn_sync } from '#scripts/git/git-spawn-sync'
4
+ import { COMMAND_TIMEOUT_MS, SUITE_TIMEOUT_MS } from '#scripts/lib/timeouts'
3
5
  import { execaSync } from 'execa'
6
+ import type { Visibility } from './start-plan'
4
7
 
5
- // The three spawn shapes every `josh start` step uses, shared by `start-steps.ts`, the setup pull
6
- // request step and the `josh init` hint so none of them keeps a copy of its own.
8
+ // The spawn shapes every `josh start` step uses, shared by `start-steps.ts`, the setup pull request
9
+ // step and the `josh init` hint so none of them keeps a copy of its own.
7
10
  //
8
- // The three git-driving shapes below act on `root`, so `cwd` has to mean it: an inherited `GIT_DIR` and friends beat `cwd`
11
+ // The git shapes below act on `root`, so `cwd` has to mean it: an inherited `GIT_DIR` and friends beat `cwd`
9
12
  // and would carry a `git init` or commit into whichever repository a calling hook was firing in
10
13
  // (joshuafolkken/kit#3151).
14
+ //
15
+ // **Every git and gh call here is bounded** (joshuafolkken/kit#3262). A read asks the local
16
+ // repository and takes git's own budget. A write — and `gh repo create --push` — can fire the
17
+ // project's own commit and push hooks, which may run its whole suite, so it takes the suite budget:
18
+ // long enough for that, short enough that a push that never answers ends the run.
19
+ const SUCCESS_EXIT_CODE = 0
20
+
11
21
  function root_environment(): Record<string, string | undefined> {
12
22
  return git_location_environment.location_free_environment()
13
23
  }
14
24
 
15
- function succeeds(command: string, args: ReadonlyArray<string>, root: string): boolean {
16
- return (
17
- execaSync(command, args, { cwd: root, env: root_environment(), reject: false }).exitCode === 0
18
- )
25
+ function git_succeeds(args: ReadonlyArray<string>, root: string): boolean {
26
+ return git_spawn_sync.run(args, { cwd: root, env: root_environment() }).exit_code === 0
19
27
  }
20
28
 
21
- function read_output(
22
- command: string,
23
- args: ReadonlyArray<string>,
24
- root: string,
25
- ): string | undefined {
26
- const result = execaSync(command, args, { cwd: root, env: root_environment(), reject: false })
29
+ function git_read(args: ReadonlyArray<string>, root: string): string | undefined {
30
+ return git_spawn_sync.read(args, { cwd: root, env: root_environment() })
31
+ }
32
+
33
+ function git_run(args: ReadonlyArray<string>, root: string): void {
34
+ const options = { cwd: root, env: root_environment(), timeout: SUITE_TIMEOUT_MS }
35
+ const { exit_code } = git_spawn_sync.run(args, { ...options, stdio: 'inherit' })
36
+
37
+ if (exit_code === SUCCESS_EXIT_CODE) return
27
38
 
28
- return result.exitCode === 0 ? result.stdout.trim() : undefined
39
+ const outcome = exit_code === undefined ? 'did not finish' : `exited with ${String(exit_code)}`
40
+
41
+ throw new Error(`git ${args.join(' ')} ${outcome}`)
42
+ }
43
+
44
+ // Every gh spawn here dials GitHub directly, past a scanner's loopback proxy, the same as every
45
+ // other `gh` spawn under scripts/ (joshuafolkken/kit#2436). Each argument list is written inline so
46
+ // `gh-subcommand-guard.ts` reads its subcommand rather than `<dynamic>`.
47
+ interface GhProbeOptions {
48
+ cwd: string
49
+ env: Record<string, string | undefined>
50
+ timeout: number
51
+ reject: false
29
52
  }
30
53
 
31
- // Every step spawned here dials GitHub directly, past a scanner's loopback proxy, the same as every
32
- // other `gh` spawn under scripts/ (joshuafolkken/kit#2436); a local `git` step is unaffected by it.
33
- function run(command: string, args: ReadonlyArray<string>, root: string): void {
54
+ function gh_probe_options(root: string): GhProbeOptions {
34
55
  const environment = { ...root_environment(), ...git_gh_exec.direct_environment().env }
35
56
 
36
- execaSync(command, args, { env: environment, cwd: root, stdio: 'inherit' })
57
+ return { cwd: root, env: environment, timeout: COMMAND_TIMEOUT_MS, reject: false }
58
+ }
59
+
60
+ function is_gh_installed(root: string): boolean {
61
+ const result = execaSync('gh', ['--version'], gh_probe_options(root)) // NOSONAR S8705: execa array args (no shell), trusted dev CLI tooling
62
+
63
+ return result.exitCode === SUCCESS_EXIT_CODE
64
+ }
65
+
66
+ function is_gh_signed_in(root: string): boolean {
67
+ const result = execaSync('gh', ['auth', 'status'], gh_probe_options(root)) // NOSONAR S8705: execa array args (no shell), trusted dev CLI tooling
68
+
69
+ return result.exitCode === SUCCESS_EXIT_CODE
70
+ }
71
+
72
+ function create_github_repository(name: string, visibility: Visibility, root: string): void {
73
+ const source = ['--source', '.', '--remote', 'origin', '--push']
74
+ const { env } = gh_probe_options(root)
75
+ const options = { cwd: root, env, timeout: SUITE_TIMEOUT_MS, stdio: 'inherit' } as const
76
+
77
+ execaSync('gh', ['repo', 'create', name, `--${visibility}`, ...source], options) // NOSONAR S8705: execa array args (no shell), trusted dev CLI tooling
37
78
  }
38
79
 
39
80
  // A caller's own initialize command stands in for the in-process `josh init`, so it keeps the
@@ -42,6 +83,14 @@ function run_local(command: string, args: ReadonlyArray<string>, root: string):
42
83
  execaSync(command, args, { cwd: root, stdio: 'inherit' })
43
84
  }
44
85
 
45
- const start_exec = { succeeds, read_output, run, run_local }
86
+ const start_exec = {
87
+ git_succeeds,
88
+ git_read,
89
+ git_run,
90
+ is_gh_installed,
91
+ is_gh_signed_in,
92
+ create_github_repository,
93
+ run_local,
94
+ }
46
95
 
47
96
  export { start_exec }
@@ -22,12 +22,12 @@ const FIRST_SETUP_ISSUE_FILTER = `[.[] | select(.title == "${SETUP_TITLE}") | .n
22
22
 
23
23
  // Listings are read NUL-separated (`-z`), so a path is never quoted — a name a caller's initialize
24
24
  // command wrote need not be ASCII the way kit's own are (#2872).
25
- function paths_of(output: string | undefined): Array<string> {
26
- return (output ?? '').split('\0').filter((listed) => listed.length > 0)
25
+ function paths_of(output: string | undefined = ''): Array<string> {
26
+ return output.split('\0').filter((listed) => listed.length > 0)
27
27
  }
28
28
 
29
29
  function listing(args: ReadonlyArray<string>, root: string): Array<string> {
30
- return paths_of(start_exec.read_output('git', [...args, '-z'], root))
30
+ return paths_of(start_exec.git_read([...args, '-z'], root))
31
31
  }
32
32
 
33
33
  function setup_issue(issue_number: string): IssueInfo {
@@ -103,13 +103,13 @@ function switch_to(branch: string, current: string | undefined, root: string): v
103
103
 
104
104
  const reference = `refs/heads/${branch}`
105
105
 
106
- if (start_exec.succeeds('git', ['rev-parse', '--verify', '--quiet', reference], root)) {
106
+ if (start_exec.git_succeeds(['rev-parse', '--verify', '--quiet', reference], root)) {
107
107
  throw new Error(
108
108
  `The setup branch ${branch} already exists. Run git switch ${branch}, then josh start again.`,
109
109
  )
110
110
  }
111
111
 
112
- start_exec.run('git', ['switch', '--create', branch], root)
112
+ start_exec.git_run(['switch', '--create', branch], root)
113
113
  }
114
114
 
115
115
  // The paths are named on the commit as well as on the add, so anything the user had staged before
@@ -117,8 +117,8 @@ function switch_to(branch: string, current: string | undefined, root: string): v
117
117
  function commit_kit_paths(paths: ReadonlyArray<string>, issue: IssueInfo, root: string): void {
118
118
  if (paths.length === 0) return
119
119
 
120
- start_exec.run('git', ['add', '--', ...paths], root)
121
- start_exec.run('git', ['commit', '--message', issue.commit_message, '--', ...paths], root)
120
+ start_exec.git_run(['add', '--', ...paths], root)
121
+ start_exec.git_run(['commit', '--message', issue.commit_message, '--', ...paths], root)
122
122
  }
123
123
 
124
124
  // A caller's initialize command writes files kit cannot name, so with a `baseline` — what was already
@@ -143,7 +143,7 @@ function setup_paths(
143
143
  // Issue → branch → only the setup's files committed → push → pull request. The merge is left to a
144
144
  // person: it is the one step that changes main.
145
145
  async function open(root: string, baseline?: ReadonlyArray<string>): Promise<void> {
146
- const current = start_exec.read_output('git', ['symbolic-ref', '--short', 'HEAD'], root)
146
+ const current = start_exec.git_read(['symbolic-ref', '--short', 'HEAD'], root)
147
147
  const paths = setup_paths(root, baseline, current)
148
148
 
149
149
  if (paths.length === 0) {
@@ -156,7 +156,7 @@ async function open(root: string, baseline?: ReadonlyArray<string>): Promise<voi
156
156
 
157
157
  switch_to(issue.branch_name, current, root)
158
158
  commit_kit_paths(paths, issue, root)
159
- start_exec.run('git', ['push', '--set-upstream', 'origin', issue.branch_name], root)
159
+ start_exec.git_run(['push', '--set-upstream', 'origin', issue.branch_name], root)
160
160
  await git_pr.create_with_issue_info(issue)
161
161
  }
162
162
 
@@ -29,7 +29,7 @@ const GH_INSTALL_HINT =
29
29
  const GH_AUTH_HINT =
30
30
  'GitHub CLI is not signed in. Run gh auth login, then run josh start again. Nothing was changed.'
31
31
 
32
- const { succeeds, read_output, run, run_local } = start_exec
32
+ const { git_succeeds, git_read, run_local } = start_exec
33
33
 
34
34
  function read_git_state(root: string, shape: ProjectShape): GitState {
35
35
  const { has_git, has_github } = shape
@@ -46,8 +46,8 @@ function read_git_state(root: string, shape: ProjectShape): GitState {
46
46
  }
47
47
 
48
48
  const has_origin = git_spawn_sync.origin_url(root) !== undefined
49
- const branch = read_output('git', ['symbolic-ref', '--short', 'HEAD'], root)
50
- const has_commits = succeeds('git', ['rev-parse', '--verify', '--quiet', 'HEAD'], root)
49
+ const branch = git_read(['symbolic-ref', '--short', 'HEAD'], root)
50
+ const has_commits = git_succeeds(['rev-parse', '--verify', '--quiet', 'HEAD'], root)
51
51
  const has_kit_committed = kit_setup_state.is_kit_committed(root)
52
52
 
53
53
  return { has_git, has_github, has_origin, branch, has_commits, has_kit_committed }
@@ -56,8 +56,8 @@ function read_git_state(root: string, shape: ProjectShape): GitState {
56
56
  // Every step talks to GitHub, so the CLI is checked before the first write rather than at the step
57
57
  // that needs it — a missing `gh` then leaves the directory exactly as it was.
58
58
  function github_cli_refusal(root: string): string | undefined {
59
- if (!succeeds('gh', ['--version'], root)) return GH_INSTALL_HINT
60
- if (!succeeds('gh', ['auth', 'status'], root)) return GH_AUTH_HINT
59
+ if (!start_exec.is_gh_installed(root)) return GH_INSTALL_HINT
60
+ if (!start_exec.is_gh_signed_in(root)) return GH_AUTH_HINT
61
61
 
62
62
  return undefined
63
63
  }
@@ -94,11 +94,7 @@ function commit_all(context: StepContext): void {
94
94
  }
95
95
 
96
96
  function create_github_repository(context: StepContext): void {
97
- const name = path.basename(context.root)
98
- const visibility = `--${context.visibility}`
99
- const source = ['--source', '.', '--remote', 'origin', '--push']
100
-
101
- run('gh', ['repo', 'create', name, visibility, ...source], context.root)
97
+ start_exec.create_github_repository(path.basename(context.root), context.visibility, context.root)
102
98
  }
103
99
 
104
100
  // The repository was created by the step before, so `gh` resolves the placeholder from the
@@ -2,8 +2,8 @@
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { git_gh_command } from '#scripts/gh/git-gh-command'
4
4
  import { git_gh_exec } from '#scripts/gh/git-gh-exec'
5
+ import { issue_label_schema } from '#scripts/git/git-schemas'
5
6
  import { parse_json } from '#scripts/git/parse-json-array'
6
- import { issue_label_schema } from '#scripts/git/schemas'
7
7
  import { bounded_pool } from '#scripts/lib/bounded-pool'
8
8
  import { cli_flags } from '#scripts/lib/cli-flags'
9
9
  import { z } from 'zod'
@@ -1,6 +1,6 @@
1
1
  import { git_gh_command } from '#scripts/gh/git-gh-command'
2
+ import { open_issue_schema, type OpenIssueData } from '#scripts/git/git-schemas'
2
3
  import { parse_json_array_safe } from '#scripts/git/parse-json-array'
3
- import { open_issue_schema, type OpenIssueData } from '#scripts/git/schemas'
4
4
  import { has_any_label, NOT_DIRECTLY_RUNNABLE_LABELS } from './issue-labels'
5
5
  import { issue_rank, type DependencyRow } from './issue-rank'
6
6
 
@@ -1,8 +1,8 @@
1
1
  import { git_branch } from '#scripts/git/git-branch'
2
2
  import { git_command } from '#scripts/git/git-command'
3
- import { run_carry } from '#scripts/run/run-carry'
3
+ import { run_carry } from '#scripts/run/carry/run-carry'
4
4
  import { issue_file } from './issue-file'
5
- import { AUTO_OK_LABEL, has_label_name } from './issue-labels'
5
+ import { AUTO_OK_LABEL, has_label_name, RELEASE_LABEL } from './issue-labels'
6
6
  import { issue_state_cli } from './issue-state-cli'
7
7
 
8
8
  // Whether `josh issue:file` applies `auto-ok` to the Issue it files (joshuafolkken/kit#3213). Work
@@ -12,9 +12,13 @@ import { issue_state_cli } from './issue-state-cli'
12
12
  // default. `--no-auto-ok` is the one exception, declared for an Issue that needs a person's judgement
13
13
  // (a Tier B toss-up or a Tier C action). A filing to another repository is never opted in: that
14
14
  // repository's own `backlogrun` would implement and merge it though nobody opted that repository in.
15
+ // A `release` Issue is never opted in either (joshuafolkken/kit#3360): a release is Tier C, so only a
16
+ // person applies `auto-ok` to it, whatever the carry record or the branch's Issue says.
15
17
 
16
18
  interface AutoOkSignals {
17
19
  is_opted_out: boolean
20
+ // The filing carries the `release` label.
21
+ is_release: boolean
18
22
  is_carried: boolean
19
23
  // The labels of the Issue the current branch names; `undefined` when the branch names none or the
20
24
  // Issue could not be read.
@@ -31,6 +35,10 @@ const CROSS_REPOSITORY: AutoOkDecision = {
31
35
  is_applied: false,
32
36
  reason: 'the Issue is filed to another repository',
33
37
  }
38
+ const RELEASE: AutoOkDecision = {
39
+ is_applied: false,
40
+ reason: `a ${RELEASE_LABEL} Issue is opted in by a person only`,
41
+ }
34
42
  const CARRIED: AutoOkDecision = { is_applied: true, reason: 'a backlogrun carry record is live' }
35
43
  const BRANCH_OPTED_IN: AutoOkDecision = {
36
44
  is_applied: true,
@@ -41,9 +49,22 @@ const NOT_OPTED_IN: AutoOkDecision = {
41
49
  reason: 'no backlogrun carry record is live and the branch names no auto-ok Issue',
42
50
  }
43
51
 
44
- // The opt-out wins over every signal, then the carry record, then the branch's Issue.
45
- function decide(signals: AutoOkSignals): AutoOkDecision {
52
+ type Withholding = Pick<AutoOkSignals, 'is_opted_out' | 'is_release'>
53
+
54
+ // The two signals that withhold `auto-ok` before any other is read, or `undefined` when neither holds.
55
+ function withheld(signals: Withholding): AutoOkDecision | undefined {
46
56
  if (signals.is_opted_out) return OPTED_OUT
57
+ if (signals.is_release) return RELEASE
58
+
59
+ return undefined
60
+ }
61
+
62
+ // The opt-out and the `release` label win over every signal, then the carry record, then the branch's
63
+ // Issue.
64
+ function decide(signals: AutoOkSignals): AutoOkDecision {
65
+ const decision = withheld(signals)
66
+
67
+ if (decision !== undefined) return decision
47
68
  if (signals.is_carried) return CARRIED
48
69
  if (has_label_name(signals.branch_labels ?? [], AUTO_OK_LABEL)) return BRANCH_OPTED_IN
49
70
 
@@ -76,17 +97,21 @@ async function branch_labels(current: string): Promise<ReadonlyArray<string> | u
76
97
  }
77
98
 
78
99
  // Each read is skipped once an earlier signal has already decided the answer; a cross-repository
79
- // filing is refused before any signal is read.
100
+ // filing is refused before any signal is read. `labels` are the ones the filing declares.
80
101
  async function resolve(
81
102
  is_opted_out: boolean,
82
103
  target: string,
83
104
  current: string,
105
+ labels: ReadonlyArray<string> = [],
84
106
  ): Promise<AutoOkDecision> {
85
- if (is_opted_out) return OPTED_OUT
107
+ const withholding = { is_opted_out, is_release: has_label_name(labels, RELEASE_LABEL) }
108
+ const decision = withheld(withholding)
109
+
110
+ if (decision !== undefined) return decision
86
111
  if (!issue_file.is_same_repository(target, current)) return CROSS_REPOSITORY
87
112
  if (await is_carried()) return CARRIED
88
113
 
89
- return decide({ is_opted_out, is_carried: false, branch_labels: await branch_labels(current) })
114
+ return decide({ ...withholding, is_carried: false, branch_labels: await branch_labels(current) })
90
115
  }
91
116
 
92
117
  const issue_auto_ok = { decide, line_of, resolve }
@@ -34,8 +34,8 @@ async function read_upstream(reference: string): Promise<UpstreamEntry | undefin
34
34
  const match = REF_PATTERN.exec(reference)
35
35
  if (match === null) return undefined
36
36
 
37
- const [, repo, number] = match
38
- const body = await read_body(number ?? '', repo)
37
+ const [, repo, number = ''] = match
38
+ const body = await read_body(number, repo)
39
39
 
40
40
  return body === undefined ? undefined : { ref: reference, body }
41
41
  }
@@ -33,11 +33,11 @@ function has_heading(body: string, heading: string): boolean {
33
33
  return markdown_section.has_line(body, heading)
34
34
  }
35
35
 
36
- // The lines under `heading`, up to the next `##` heading; `undefined` when the heading is absent.
37
- function section_lines(body: string, heading: string): ReadonlyArray<string> | undefined {
38
- if (!has_heading(body, heading)) return undefined
36
+ // The lines under the upstream heading, up to the next `##` heading; `undefined` when it is absent.
37
+ function upstream_section_lines(body: string): ReadonlyArray<string> | undefined {
38
+ if (!has_heading(body, UPSTREAM_ISSUES_HEADING)) return undefined
39
39
 
40
- return markdown_section.section_lines(body, heading)
40
+ return markdown_section.section_lines(body, UPSTREAM_ISSUES_HEADING)
41
41
  }
42
42
 
43
43
  // A heading that looks like the upstream one but is not exactly it (`## Upstream`, `## Upstreams`,
@@ -59,8 +59,8 @@ function tokens_of(section: ReadonlyArray<string>): ReadonlyArray<string> {
59
59
  .filter((token) => token.length > 0)
60
60
  }
61
61
 
62
- function has_two_segments(path: string | undefined): boolean {
63
- const segments = (path ?? '').split(PATH_SEPARATOR)
62
+ function has_two_segments(path: string | undefined = ''): boolean {
63
+ const segments = path.split(PATH_SEPARATOR)
64
64
 
65
65
  return segments.length === PATH_SEGMENT_COUNT && segments.every((segment) => segment.length > 0)
66
66
  }
@@ -115,7 +115,7 @@ function classify_backlinks(
115
115
  ): BacklinkVerdict {
116
116
  if (has_wrong_upstream_heading(origin_body)) return WRONG_HEADING
117
117
 
118
- const section = section_lines(origin_body, UPSTREAM_ISSUES_HEADING)
118
+ const section = upstream_section_lines(origin_body)
119
119
 
120
120
  return section === undefined
121
121
  ? verdict_without_section(origin_body)
@@ -125,7 +125,7 @@ function classify_backlinks(
125
125
  // The `owner/repo#N` references the upstream section lists, so the CLI knows which upstream bodies to
126
126
  // fetch and check for `## Origin`.
127
127
  function upstream_references(origin_body: string): ReadonlyArray<string> {
128
- const section = section_lines(origin_body, UPSTREAM_ISSUES_HEADING)
128
+ const section = upstream_section_lines(origin_body)
129
129
  if (section === undefined) return []
130
130
 
131
131
  return tokens_of(section).filter((token) => is_qualified_reference(token))
@@ -138,7 +138,7 @@ function upstream_references(origin_body: string): ReadonlyArray<string> {
138
138
  function needs_upstreams(origin_body: string): boolean {
139
139
  if (has_wrong_upstream_heading(origin_body)) return false
140
140
 
141
- const section = section_lines(origin_body, UPSTREAM_ISSUES_HEADING)
141
+ const section = upstream_section_lines(origin_body)
142
142
  if (section === undefined) return false
143
143
 
144
144
  return !is_malformed_upstream_section(section)
@@ -2,6 +2,7 @@
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { git_gh_command } from '#scripts/gh/git-gh-command'
4
4
  import { cli_body } from '#scripts/josh/cli-body'
5
+ import { cli_flags } from '#scripts/lib/cli-flags'
5
6
  import { error_text } from '#scripts/lib/error-message'
6
7
 
7
8
  // `josh issue:comment <N> --body <text> | --body-file <path>` — post one comment to an issue (a PR
@@ -28,25 +29,30 @@ interface CommentRequest {
28
29
  body: string
29
30
  }
30
31
 
31
- // The value after a flag word, or `undefined` when the flag is absent. `cli_body.resolve` owns the
32
- // both-flags-at-once refusal and the file read, so this stays the one narrow job of finding the token.
33
- function flag_value(argv: ReadonlyArray<string>, flag: string): string | undefined {
34
- const index = argv.indexOf(flag)
32
+ // The read is strict (joshuafolkken/kit#3261): an unknown flag refuses the call rather than being
33
+ // ignored. `cli_body.resolve` owns the both-flags-at-once refusal and the file read.
34
+ const OPTIONS = { body: { type: 'string' }, 'body-file': { type: 'string' } } as const
35
35
 
36
- return index === -1 ? undefined : argv[index + 1]
36
+ // The number is the one positional, and it must be a real issue number: a missing, malformed or
37
+ // second one refuses the whole call rather than posting to some other issue.
38
+ function single_issue_number(positionals: ReadonlyArray<string>): string | undefined {
39
+ const [issue_number, ...rest] = positionals
40
+ const is_single = rest.length === 0 && issue_number !== undefined
41
+
42
+ return is_single && ISSUE_NUMBER_PATTERN.test(issue_number) ? issue_number : undefined
37
43
  }
38
44
 
39
- // The number is the first argument, and it must be a real issue number: a missing or malformed one
40
- // refuses the whole call rather than posting to some other issue. `resolve` may throw when both body
41
- // flags are given, which `run` reports — the message names the two flags a caller must pick between.
45
+ // `resolve` may throw when both body flags are given, which `run` reports — the message names the two
46
+ // flags a caller must pick between.
42
47
  function parse_request(argv: ReadonlyArray<string>): CommentRequest | undefined {
43
- const [issue_number] = argv
44
-
45
- if (issue_number === undefined || !ISSUE_NUMBER_PATTERN.test(issue_number)) return undefined
48
+ const parsed = cli_flags.arguments_of(argv, OPTIONS)
49
+ if (parsed === undefined) return undefined
50
+ const issue_number = single_issue_number(parsed.positionals)
51
+ if (issue_number === undefined) return undefined
46
52
 
47
53
  const body = cli_body.resolve({
48
- inline: flag_value(argv, BODY_FLAG),
49
- file_path: flag_value(argv, BODY_FILE_FLAG),
54
+ inline: parsed.values.body,
55
+ file_path: parsed.values['body-file'],
50
56
  inline_flag: BODY_FLAG,
51
57
  file_flag: BODY_FILE_FLAG,
52
58
  })