@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,7 +2,7 @@ function extract_yaml_top_level_keys(content: string): Array<string> {
2
2
  return content.split('\n').flatMap((line) => {
3
3
  const key = /^([a-zA-Z][a-zA-Z0-9_-]*):/u.exec(line)?.[1]
4
4
 
5
- return key ? [key] : []
5
+ return key ?? []
6
6
  })
7
7
  }
8
8
 
@@ -125,6 +125,45 @@ function add_missing_build_entries(existing: string, template: string): string {
125
125
  return existing.replace(block, () => [block, ...missing].join('\n'))
126
126
  }
127
127
 
128
+ interface MovedNpmrcSetting {
129
+ npmrc_key: string
130
+ workspace_key: string
131
+ value_pattern: RegExp
132
+ }
133
+
134
+ // `.npmrc` settings pnpm 12 reads only from `pnpm-workspace.yaml`, with the value shape each takes.
135
+ // A project's own value (a longer age window, `engine-strict=false`) replaces the template's, so the
136
+ // move neither loosens nor tightens what the project chose (joshuafolkken/kit#3267).
137
+ const MOVED_NPMRC_SETTINGS: ReadonlyArray<MovedNpmrcSetting> = [
138
+ { npmrc_key: 'minimum-release-age', workspace_key: 'minimumReleaseAge', value_pattern: /^\d+$/u },
139
+ { npmrc_key: 'engine-strict', workspace_key: 'engineStrict', value_pattern: /^(?:true|false)$/u },
140
+ ]
141
+
142
+ // The last assignment wins, as it does when npm reads the file.
143
+ function npmrc_value(npmrc: string, key: string): string | undefined {
144
+ const pattern = new RegExp(String.raw`^[ \t]*${key}[ \t]*=[ \t]*(\S+)[ \t]*$`, 'gmu')
145
+
146
+ return [...npmrc.matchAll(pattern)].at(-1)?.[1]
147
+ }
148
+
149
+ function carry_npmrc_setting(template: string, npmrc: string, setting: MovedNpmrcSetting): string {
150
+ const value = npmrc_value(npmrc, setting.npmrc_key)
151
+ if (value === undefined || !setting.value_pattern.test(value)) return template
152
+ const line_pattern = new RegExp(`^${setting.workspace_key}:.*$`, 'mu')
153
+
154
+ return template.replace(line_pattern, () => `${setting.workspace_key}: ${value}`)
155
+ }
156
+
157
+ function carry_npmrc_settings(template: string, npmrc: string): string {
158
+ let result = template
159
+
160
+ for (const setting of MOVED_NPMRC_SETTINGS) {
161
+ result = carry_npmrc_setting(result, npmrc, setting)
162
+ }
163
+
164
+ return result
165
+ }
166
+
128
167
  function merge_workspace_yaml(existing: string, template: string): string {
129
168
  if (!existing.trim()) return template
130
169
  const normalized = existing.endsWith('\n') ? existing : `${existing}\n`
@@ -138,6 +177,6 @@ function merge_workspace_yaml(existing: string, template: string): string {
138
177
  return append_user_blocks(cleaned, new_keys, template)
139
178
  }
140
179
 
141
- const init_logic_workspace = { merge_workspace_yaml, template_build_values }
180
+ const init_logic_workspace = { carry_npmrc_settings, merge_workspace_yaml, template_build_values }
142
181
 
143
182
  export { init_logic_workspace }
@@ -14,16 +14,15 @@ const DEV_ENGINES_VALUE = {
14
14
  packageManager: { name: 'pnpm', version: '>=12.1.0', onFail: 'error' },
15
15
  }
16
16
 
17
- // New projects resolve kit from public npm. Existing registry and auth lines remain untouched
18
- // by merge_npmrc so consumers of other GitHub Packages can migrate separately.
19
- const NPMRC_LINES: ReadonlyArray<string> = [
17
+ // The settings lines earlier kit releases wrote into a project `.npmrc`. pnpm 12 reads only registry
18
+ // and auth settings from `.npmrc`, so all four are inert there: the age window and engine check now
19
+ // live in `pnpm-workspace.yaml`, and `confirmModulesPurge` no longer exists (joshuafolkken/kit#3267).
20
+ const LEGACY_NPMRC_LINES: ReadonlySet<string> = new Set([
20
21
  'engine-strict=true',
21
22
  'minimum-release-age=1440',
22
23
  'confirmModulesPurge=false',
23
- // Pin full GitHub Packages tarball URLs in the lockfile so pnpm >=11.5 frozen-lockfile
24
- // installs hit the correct authenticated download path (avoids ERR_PNPM_FETCH_401 on CI).
25
24
  'lockfile-include-tarball-url=true',
26
- ]
25
+ ])
27
26
 
28
27
  const CSPELL_IMPORT = '@joshuafolkken/kit/cspell'
29
28
 
@@ -254,10 +253,6 @@ function generate_cspell_config(): string {
254
253
  return `version: "0.2"\nimport:\n - "${CSPELL_IMPORT}"\nwords: []\nignorePaths: []\n`
255
254
  }
256
255
 
257
- function generate_npmrc(): string {
258
- return `${NPMRC_LINES.join('\n')}\n`
259
- }
260
-
261
256
  // Append `missing` lines to `existing`, inserting a separating newline when the base is
262
257
  // non-empty and lacks a trailing one. Returns `existing` untouched when nothing is missing.
263
258
  function append_missing_lines(existing: string, missing: ReadonlyArray<string>): string {
@@ -267,18 +262,19 @@ function append_missing_lines(existing: string, missing: ReadonlyArray<string>):
267
262
  return `${prefix}${missing.join('\n')}\n`
268
263
  }
269
264
 
270
- // Insert-only: every existing line survives verbatim, including a `_authToken` line the consumer
271
- // added themselves. Kit removed the env-var form until #759, on the premise that pnpm always
272
- // ignores it in a project .npmrc — true only while `npmrcAuthFile` is unset. A consumer that
273
- // declares the project file its trusted auth file (Cloudflare Workers Builds via
274
- // `PNPM_CONFIG_NPMRC_AUTH_FILE`, for instance) gets the credential expanded, and that setting can
275
- // live in a deploy platform's dashboard — so no reading of the repository tells an inert line
276
- // apart from a live one. Deleting it took down a working production deploy; leaving it costs at
277
- // most the warning pnpm already prints. See docs/authentication.md §4(d).
265
+ // Removes only an exact kit-written legacy line; every other line survives verbatim, including a
266
+ // `_authToken` line the consumer added themselves and a setting carrying a value of their own. Kit
267
+ // removed the env-var auth form until #759, on the premise that pnpm always ignores it in a project
268
+ // .npmrc — true only while `npmrcAuthFile` is unset. A consumer that declares the project file its
269
+ // trusted auth file (Cloudflare Workers Builds via `PNPM_CONFIG_NPMRC_AUTH_FILE`, for instance) gets
270
+ // the credential expanded, and that setting can live in a deploy platform's dashboard — so no
271
+ // reading of the repository tells an inert auth line apart from a live one. Deleting it took down a
272
+ // working production deploy. See docs/authentication.md §4(d).
278
273
  function merge_npmrc(content: string): string {
279
- const missing = NPMRC_LINES.filter((line) => !content.includes(line))
274
+ const lines = content.split('\n')
275
+ const kept = lines.filter((line) => !LEGACY_NPMRC_LINES.has(line.trim()))
280
276
 
281
- return append_missing_lines(content, missing)
277
+ return kept.length === lines.length ? content : kept.join('\n')
282
278
  }
283
279
 
284
280
  // A gitignore line worth appending during a union merge: real ignore patterns only.
@@ -349,10 +345,6 @@ function strip_kit_only_vscode_settings_content(raw: string): string {
349
345
  return json_format.format_json(stripped)
350
346
  }
351
347
 
352
- function get_npmrc_lines(): ReadonlyArray<string> {
353
- return NPMRC_LINES
354
- }
355
-
356
348
  function get_development_engines_value(): typeof DEV_ENGINES_VALUE {
357
349
  return DEV_ENGINES_VALUE
358
350
  }
@@ -436,7 +428,6 @@ const init_logic = {
436
428
  get_tsconfig_exclude_entries,
437
429
  generate_lefthook_config,
438
430
  generate_cspell_config,
439
- generate_npmrc,
440
431
  merge_npmrc,
441
432
  merge_gitignore,
442
433
  get_tsconfig_extends_entry,
@@ -450,7 +441,6 @@ const init_logic = {
450
441
  SAFE_CHAIN_CMD,
451
442
  GUARDED_LEFTHOOK_CMD,
452
443
  upgrade_prepare_lefthook_warning,
453
- get_npmrc_lines,
454
444
  get_development_engines_value,
455
445
  get_ai_copy_files,
456
446
  get_ai_copy_file_mappings,
@@ -23,6 +23,7 @@ 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'
27
28
  import { project_profile, type ProjectProfile, type ProjectShape } from './project-profile'
28
29
 
@@ -81,21 +82,10 @@ function tool_dependency_names(has_git: boolean): ReadonlyArray<string> {
81
82
 
82
83
  // Versions mirror kit's own devDependencies, so the consumer runs the toolchain kit is verified with.
83
84
  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)
85
+ return kit_development_versions.versions_of([
86
+ ...kit_development_versions.peer_names(),
87
+ ...tool_dependency_names(has_git),
88
+ ])
99
89
  }
100
90
 
101
91
  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
@@ -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,6 +1,6 @@
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
5
  import { AUTO_OK_LABEL, has_label_name } from './issue-labels'
6
6
  import { issue_state_cli } from './issue-state-cli'
@@ -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
  })