@joshuafolkken/kit 1.1178.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 (425) 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 +6 -2
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +23 -2
  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 +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 +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 +6 -1
  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 +32 -31
  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-S6X6WYKR.js → chunk-55HHG5PX.js} +1210 -711
  43. package/dist/hooks/{chunk-ZAL3NRGL.js → chunk-5VMKCCC4.js} +25 -10
  44. package/dist/hooks/chunk-5VNDSNG3.js +12 -0
  45. package/dist/hooks/{chunk-32GQTL4F.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-VESKRQOF.js → chunk-F56MFS4B.js} +3 -3
  49. package/dist/hooks/{chunk-UAGLLIGZ.js → chunk-FIVDHQGT.js} +7 -6
  50. package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
  51. package/dist/hooks/{chunk-G6KRCKEC.js → chunk-JUDKDCDP.js} +496 -378
  52. package/dist/hooks/{chunk-GDS5N2KB.js → chunk-NV2JRZZO.js} +718 -681
  53. package/dist/hooks/{chunk-VQLLNFNT.js → chunk-RNWHN5BP.js} +3 -3
  54. package/dist/hooks/{chunk-DO6NWWHU.js → chunk-TQNPDZNH.js} +4 -4
  55. package/dist/hooks/{chunk-I4XSL4TT.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 +217 -195
  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 +66 -58
  64. package/dist/josh.js +868 -74
  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 +16 -5
  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 +1 -1
  111. package/scripts/backlog/backlog-pool.ts +57 -11
  112. package/scripts/backlog/backlog-ready.ts +1 -1
  113. package/scripts/backlog/backlog-scope.ts +1 -1
  114. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  115. package/scripts/backlog/backlog-stalled.ts +1 -1
  116. package/scripts/build/build-codex-hooks.ts +3 -1
  117. package/scripts/build/build-commands.ts +66 -0
  118. package/scripts/build/build-hooks.ts +2 -1
  119. package/scripts/build/build-library.ts +12 -3
  120. package/scripts/build/build.ts +70 -0
  121. package/scripts/build/local-bin.ts +3 -13
  122. package/scripts/delegation/investigation-reads.ts +3 -1
  123. package/scripts/discovery/repo-origin.ts +5 -5
  124. package/scripts/doctor/doctor-consumer.ts +2 -2
  125. package/scripts/doctor/doctor-io.ts +2 -3
  126. package/scripts/document/capped-print-part.ts +30 -0
  127. package/scripts/document/capped-print.ts +3 -4
  128. package/scripts/document/document-byte-budget.ts +11 -2
  129. package/scripts/document/document-scan.ts +117 -0
  130. package/scripts/document/document-section.ts +143 -16
  131. package/scripts/document/entry-read-budget.ts +5 -3
  132. package/scripts/document/entry-read-set.ts +10 -89
  133. package/scripts/document/read-set-cost.ts +98 -0
  134. package/scripts/epic/epic-add-body.ts +1 -0
  135. package/scripts/epic/epic-audit-checks.ts +1 -1
  136. package/scripts/epic/epic-audit-rationale.ts +4 -3
  137. package/scripts/epic/epic-bundle-cli.ts +14 -3
  138. package/scripts/epic/epic-bundle-poll.ts +41 -0
  139. package/scripts/epic/epic-busy.ts +1 -1
  140. package/scripts/epic/epic-candidate-confirm.ts +3 -2
  141. package/scripts/epic/epic-classify.ts +1 -1
  142. package/scripts/epic/epic-cli-argv.ts +78 -46
  143. package/scripts/epic/epic-cli-remove.ts +11 -19
  144. package/scripts/epic/epic-cli.ts +74 -73
  145. package/scripts/epic/epic-close-comment.ts +1 -1
  146. package/scripts/epic/epic-close.ts +1 -1
  147. package/scripts/epic/epic-cross-repo.ts +5 -5
  148. package/scripts/epic/epic-graph.ts +1 -1
  149. package/scripts/epic/epic-index.ts +1 -1
  150. package/scripts/epic/epic-issue.ts +2 -6
  151. package/scripts/epic/epic-lane-offer.ts +2 -2
  152. package/scripts/epic/epic-next.ts +29 -42
  153. package/scripts/epic/epic-solo-stale.ts +1 -1
  154. package/scripts/epic/epic-triage.ts +8 -4
  155. package/scripts/epic/epic-validate.ts +1 -1
  156. package/scripts/followup/git-followup-finish.ts +2 -2
  157. package/scripts/followup/git-followup-label.ts +2 -2
  158. package/scripts/followup/git-followup-merged.ts +2 -2
  159. package/scripts/followup/git-pr-followup-checks.ts +142 -0
  160. package/scripts/followup/git-pr-followup.ts +4 -129
  161. package/scripts/gate/core-budget.ts +69 -1
  162. package/scripts/gate/gate-plan.ts +47 -7
  163. package/scripts/gate/gate-skip.ts +38 -7
  164. package/scripts/gate/hook-gate-reuse.ts +12 -10
  165. package/scripts/gate/pre-commit-type-check.ts +4 -1
  166. package/scripts/gate/project-checks.ts +6 -9
  167. package/scripts/gate/type-check-step.ts +2 -3
  168. package/scripts/gate/verification-gate.ts +27 -8
  169. package/scripts/gh/git-gh-api-path.ts +2 -2
  170. package/scripts/gh/git-gh-issue-rest.ts +3 -3
  171. package/scripts/gh/git-gh-issue-write.ts +53 -7
  172. package/scripts/gh/git-gh-pr-read.ts +64 -19
  173. package/scripts/gh/git-gh-pr-rest.ts +1 -1
  174. package/scripts/gh/git-gh-pr.ts +5 -1
  175. package/scripts/gh/git-pr-ai-review.ts +1 -1
  176. package/scripts/gh/git-pr-checks-eval.ts +1 -3
  177. package/scripts/gh/git-pr-checks-parse.ts +1 -1
  178. package/scripts/gh/git-pr-checks-watch.ts +6 -5
  179. package/scripts/gh/git-pr-checks.ts +7 -5
  180. package/scripts/gh/git-pr-coderabbit.ts +1 -1
  181. package/scripts/gh/git-pr-error.ts +1 -3
  182. package/scripts/gh/git-pr.ts +9 -13
  183. package/scripts/gh/github-issue-url.ts +2 -2
  184. package/scripts/git/constants.ts +2 -6
  185. package/scripts/git/git-command.ts +20 -218
  186. package/scripts/git/git-diff-reads.ts +225 -0
  187. package/scripts/git/git-error.ts +4 -12
  188. package/scripts/git/git-pre-push-hook.ts +139 -0
  189. package/scripts/git/git-prompt.ts +2 -6
  190. package/scripts/git/git-spawn-sync.ts +3 -1
  191. package/scripts/git/git-spawn.ts +1 -1
  192. package/scripts/git/git-staging.ts +4 -6
  193. package/scripts/git/git-status.ts +3 -9
  194. package/scripts/hooks/codex-hook-adapter.ts +1 -1
  195. package/scripts/hooks/edited-cspell.ts +2 -4
  196. package/scripts/hooks/format-edited-file.ts +3 -12
  197. package/scripts/hooks/pre-push-unit.ts +31 -9
  198. package/scripts/hooks/reserved-run.ts +2 -2
  199. package/scripts/hooks/run-hook.sh +5 -4
  200. package/scripts/hooks/stop-guard.ts +5 -3
  201. package/scripts/init/init-actions.ts +0 -5
  202. package/scripts/init/init-ai-copy.ts +13 -2
  203. package/scripts/init/init-logic-json-merge.ts +19 -0
  204. package/scripts/init/init-logic-secretlint.ts +7 -7
  205. package/scripts/init/init-logic-workspace.ts +41 -2
  206. package/scripts/init/init-logic.ts +16 -26
  207. package/scripts/init/init.ts +5 -15
  208. package/scripts/init/initial-commit.ts +4 -4
  209. package/scripts/init/kit-development-versions.ts +40 -0
  210. package/scripts/init/kit-setup-state.ts +2 -2
  211. package/scripts/init/start-exec.ts +68 -19
  212. package/scripts/init/start-setup-pr.ts +9 -9
  213. package/scripts/init/start-steps.ts +6 -10
  214. package/scripts/issue/defect-rate-cli.ts +1 -1
  215. package/scripts/issue/git-next-issues.ts +1 -1
  216. package/scripts/issue/issue-auto-ok.ts +1 -1
  217. package/scripts/issue/issue-backlinks-cli.ts +2 -2
  218. package/scripts/issue/issue-backlinks.ts +9 -9
  219. package/scripts/issue/issue-comment-cli.ts +19 -13
  220. package/scripts/issue/issue-file-cli.ts +45 -18
  221. package/scripts/issue/issue-file.ts +24 -1
  222. package/scripts/issue/issue-scout-closed.ts +1 -1
  223. package/scripts/issue/issue-state-cli.ts +30 -74
  224. package/scripts/josh/josh-command-map.ts +2 -0
  225. package/scripts/josh/josh-command-types.ts +11 -0
  226. package/scripts/josh/josh-commands-ai.ts +4 -4
  227. package/scripts/josh/josh-commands-backlog.ts +4 -1
  228. package/scripts/josh/josh-commands-development.ts +10 -0
  229. package/scripts/josh/josh-commands-document.ts +1 -0
  230. package/scripts/josh/josh-commands-lane.ts +22 -0
  231. package/scripts/josh/josh-commands-maintenance.ts +4 -4
  232. package/scripts/josh/josh-commands-run.ts +12 -7
  233. package/scripts/josh/josh-in-process.ts +28 -9
  234. package/scripts/josh/josh-logic.ts +33 -15
  235. package/scripts/josh/process-identity.ts +1 -3
  236. package/scripts/josh/session-language-cli.ts +3 -4
  237. package/scripts/lane/lane-await.ts +5 -1
  238. package/scripts/lane/lane-child-invocation.ts +2 -2
  239. package/scripts/lane/lane-cli.ts +3 -3
  240. package/scripts/lane/lane-dispatch.ts +14 -10
  241. package/scripts/lane/lane-environment.ts +5 -1
  242. package/scripts/lane/lane-handoff.ts +1 -1
  243. package/scripts/lane/lane-install.ts +2 -13
  244. package/scripts/lane/lane-launch-cli.ts +15 -1
  245. package/scripts/lane/lane-ledger.ts +145 -0
  246. package/scripts/lane/lane-leftover.ts +1 -1
  247. package/scripts/lane/lane-load.ts +108 -0
  248. package/scripts/lane/lane-measure-cli.ts +149 -0
  249. package/scripts/lane/lane-occupancy.ts +3 -0
  250. package/scripts/lane/lane-reap.ts +1 -3
  251. package/scripts/lane/lane-relaunch.ts +1 -1
  252. package/scripts/lane/lane-stats.ts +224 -0
  253. package/scripts/lane/lane-vacant.ts +36 -0
  254. package/scripts/lane/openai-lane-ship.ts +2 -2
  255. package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
  256. package/scripts/lane/openai-lane-supervisor.ts +7 -165
  257. package/scripts/lane/openai-review-broker.ts +2 -4
  258. package/scripts/lib/ancestor-directories.ts +35 -0
  259. package/scripts/lib/animation-helpers.ts +2 -2
  260. package/scripts/lib/buffered-process.ts +3 -7
  261. package/scripts/lib/cli-flags.ts +71 -1
  262. package/scripts/lib/spawn-exit.ts +2 -2
  263. package/scripts/lib/timeouts.ts +35 -2
  264. package/scripts/lines/line-budget.ts +4 -6
  265. package/scripts/notify/telegram-test.ts +6 -0
  266. package/scripts/observations/observation-ledger-line.ts +6 -6
  267. package/scripts/observations/observation-ledger-migrate.ts +55 -4
  268. package/scripts/observations/observation-ledger.ts +26 -7
  269. package/scripts/overrides/lockfile-overrides.ts +1 -1
  270. package/scripts/overrides/overrides-logic.ts +1 -1
  271. package/scripts/propagate/propagate-git.ts +1 -1
  272. package/scripts/propagate/propagate-publish.ts +2 -2
  273. package/scripts/propagate/propagate-steps.ts +20 -15
  274. package/scripts/propagate/propagate.ts +36 -27
  275. package/scripts/refactor/import-graph.ts +18 -1
  276. package/scripts/refactor/refactor-lint.ts +4 -4
  277. package/scripts/repo/repository-labels.ts +2 -2
  278. package/scripts/repo/required-checks-logic.ts +1 -3
  279. package/scripts/review/live-evidence.ts +2 -4
  280. package/scripts/review/review-brief-cli.ts +1 -1
  281. package/scripts/review/review-finding-ledger.ts +1 -1
  282. package/scripts/review/review-findings-cli.ts +1 -3
  283. package/scripts/rules/decision-oracle-batch.ts +128 -0
  284. package/scripts/rules/decision-oracle-tokens.ts +21 -0
  285. package/scripts/rules/decision-oracle.ts +8 -120
  286. package/scripts/rules/delivered-rules.ts +26 -182
  287. package/scripts/rules/direct-filing.ts +1 -1
  288. package/scripts/rules/early-heartbeat.ts +3 -3
  289. package/scripts/rules/git-argv.ts +1 -1
  290. package/scripts/rules/git-force.ts +1 -1
  291. package/scripts/rules/implementation-cut.ts +2 -2
  292. package/scripts/rules/index-guard.ts +1 -1
  293. package/scripts/rules/issue-comments.ts +118 -0
  294. package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
  295. package/scripts/rules/lane-background.ts +2 -2
  296. package/scripts/rules/lane-park.ts +18 -0
  297. package/scripts/rules/lane-split-park.ts +128 -0
  298. package/scripts/rules/oracle-firing.ts +1 -1
  299. package/scripts/rules/poll-loop.ts +1 -1
  300. package/scripts/rules/pre-gate-cut.ts +1 -1
  301. package/scripts/rules/protected-files.ts +59 -4
  302. package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
  303. package/scripts/rules/shell-body-trigger.ts +1 -1
  304. package/scripts/rules/stop-rules.ts +18 -7
  305. package/scripts/rules/test-declared-commit.ts +4 -4
  306. package/scripts/rules/wip-cap.ts +63 -0
  307. package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
  308. package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
  309. package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
  310. package/scripts/run/{run-carry.ts → carry/run-carry.ts} +1 -1
  311. package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
  312. package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +2 -2
  313. package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
  314. package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
  315. package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
  316. package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
  317. package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
  318. package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +16 -2
  319. package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
  320. package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
  321. package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
  322. package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
  323. package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
  324. package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
  325. package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
  326. package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
  327. package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
  328. package/scripts/run/run-ending.ts +2 -2
  329. package/scripts/run/run-halfrun-resume.ts +6 -1
  330. package/scripts/run/run-headless.ts +1 -1
  331. package/scripts/run/run-invocation.ts +85 -84
  332. package/scripts/run/run-liveness.ts +1 -1
  333. package/scripts/run/run-parent-cut-hook.ts +1 -1
  334. package/scripts/run/run-preflight.ts +6 -6
  335. package/scripts/run/run-prep-cli.ts +1 -1
  336. package/scripts/run/run-prrun-resume.ts +1 -1
  337. package/scripts/run/run-report-cli.ts +4 -4
  338. package/scripts/run/run-report.ts +2 -2
  339. package/scripts/run/run-status-cli.ts +1 -1
  340. package/scripts/run/run-step-cli.ts +3 -3
  341. package/scripts/run/run-step.ts +2 -2
  342. package/scripts/run/run-stop-notify.ts +1 -1
  343. package/scripts/run/run-stranded-detect.ts +4 -4
  344. package/scripts/run/run-stranded.ts +2 -2
  345. package/scripts/run/run-watcher-guard-cli.ts +1 -1
  346. package/scripts/run/run-watcher-guard.ts +5 -3
  347. package/scripts/run/run-watcher-hook.ts +1 -1
  348. package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +2 -2
  349. package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
  350. package/scripts/run/ship/run-ship-lock.ts +50 -0
  351. package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
  352. package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
  353. package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +4 -3
  354. package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
  355. package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
  356. package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +11 -12
  357. package/scripts/run/ship/run-ship-stop-text.ts +13 -0
  358. package/scripts/run/{run-ship-sync.ts → ship/run-ship-sync.ts} +25 -11
  359. package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
  360. package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +23 -8
  361. package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +3 -3
  362. package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +59 -8
  363. package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
  364. package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +8 -2
  365. package/scripts/run/wake/run-wake-restart.ts +58 -0
  366. package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
  367. package/scripts/run/{run-wake.ts → wake/run-wake.ts} +1 -1
  368. package/scripts/safe-chain/preinstall-command.ts +20 -21
  369. package/scripts/safe-chain/project-config.ts +19 -15
  370. package/scripts/sync/sync-ai-files.ts +1 -1
  371. package/scripts/sync/sync-configs.ts +2 -1
  372. package/scripts/sync/sync.ts +15 -2
  373. package/scripts/test/unit-worker-share.ts +1 -0
  374. package/scripts/time-runtime/time-background.ts +5 -1
  375. package/scripts/time-runtime/time-instant.ts +3 -1
  376. package/scripts/time-runtime/time-spans.ts +6 -139
  377. package/scripts/time-runtime/time-tool-call.ts +160 -0
  378. package/scripts/version/effective-upstream.ts +2 -20
  379. package/scripts/version/latest-corepack.ts +17 -17
  380. package/scripts/version/latest-regression.ts +1 -1
  381. package/scripts/version/latest-update.ts +4 -4
  382. package/scripts/version/npm-registry.ts +20 -5
  383. package/scripts/version/publishable-range-check.ts +2 -2
  384. package/scripts/version/publishable-range.ts +1 -1
  385. package/scripts/version/release-age.ts +48 -38
  386. package/scripts/version/release-hold.ts +1 -1
  387. package/scripts/version/safe-chain-version-update.ts +40 -0
  388. package/scripts/version/upgrade-shell-command.ts +48 -8
  389. package/scripts/version/version-check-logic.ts +23 -14
  390. package/scripts/version/version-commands.ts +23 -11
  391. package/scripts/version/version-remote.ts +28 -13
  392. package/templates/pnpm-workspace.basic.yaml +4 -0
  393. package/templates/workflows/dependabot-auto-merge.yml +5 -5
  394. package/templates/workflows/github-release.yml +2 -0
  395. package/scripts/build/build-config-merge.ts +0 -27
  396. package/scripts/build/build-managed-marker.ts +0 -27
  397. package/scripts/build/build-self-sync-guard.ts +0 -27
  398. package/scripts/build/build-version.ts +0 -26
  399. package/scripts/version/preinstall-version-update.ts +0 -86
  400. /package/scripts/git/{schemas.ts → git-schemas.ts} +0 -0
  401. /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
  402. /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
  403. /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
  404. /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
  405. /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
  406. /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
  407. /package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +0 -0
  408. /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
  409. /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
  410. /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
  411. /package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +0 -0
  412. /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
  413. /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
  414. /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
  415. /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
  416. /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
  417. /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
  418. /package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +0 -0
  419. /package/scripts/run/{run-ship-pre-detach.ts → ship/run-ship-pre-detach.ts} +0 -0
  420. /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
  421. /package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +0 -0
  422. /package/scripts/run/{run-ship.ts → ship/run-ship.ts} +0 -0
  423. /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
  424. /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
  425. /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { package_with_deps_schema } from '#scripts/overrides/schemas'
1
+ import { package_with_deps_schema } from '#scripts/overrides/overrides-schemas'
2
2
  import semver from 'semver'
3
3
 
4
4
  // `pnpm update --latest` sets each range to whatever the registry reports as newest. When a
@@ -13,7 +13,7 @@ import { overrides_check } from '#scripts/overrides/overrides-logic'
13
13
  import { execaSync } from 'execa'
14
14
  import { fresh_metadata_cache } from './fresh-metadata-cache'
15
15
  import { latest_regression, type VersionRegression } from './latest-regression'
16
- import { preinstall_version_update } from './preinstall-version-update'
16
+ import { safe_chain_version_update } from './safe-chain-version-update'
17
17
 
18
18
  const PACKAGE_JSON_PATH = 'package.json'
19
19
  const LOCKFILE_PATH = 'pnpm-lock.yaml'
@@ -175,11 +175,11 @@ function main(): void {
175
175
  report_overrides(overrides)
176
176
  report_lockfile_overrides()
177
177
 
178
- // Skipped after a rollback so the tree really is left exactly as it was found — this sync writes
179
- // package.json to advance the pinned safe-chain version, which would contradict the notice that
178
+ // Skipped after a rollback so the tree really is left exactly as it was found — this sync rewrites
179
+ // the workflows' pinned safe-chain installer, which would contradict the notice that
180
180
  // nothing changed.
181
181
  if (outcome.status === 0 && !outcome.is_rolled_back) {
182
- preinstall_version_update.sync(PACKAGE_JSON_PATH)
182
+ safe_chain_version_update.sync()
183
183
  }
184
184
  }
185
185
 
@@ -1,6 +1,8 @@
1
1
  import { json_value } from '#scripts/lib/json-value'
2
+ import { FETCH_TIMEOUT_MS } from '#scripts/lib/timeouts'
2
3
  import { migrate_logic } from '#scripts/registry-migration/migrate-logic'
3
4
  import { execaSync } from 'execa'
5
+ import semver from 'semver'
4
6
  import { z } from 'zod'
5
7
  import { release_age } from './release-age'
6
8
 
@@ -12,7 +14,6 @@ const PUBLIC_REGISTRY = 'https://registry.npmjs.org'
12
14
  // The abbreviated packument carries `dist-tags` without every version's full manifest.
13
15
  const ABBREVIATED_ACCEPT = 'application/vnd.npm.install-v1+json'
14
16
  const FULL_ACCEPT = 'application/json'
15
- const REQUEST_TIMEOUT_MS = 10_000
16
17
  const SCOPE_SEPARATOR = '/'
17
18
  const ENCODED_SCOPE_SEPARATOR = '%2f'
18
19
  const SCOPE_PATTERN = /^@[^/]+(?=\/)/u
@@ -29,7 +30,10 @@ const FETCH_SCRIPT = [
29
30
  'process.stdout.write(await response.text())',
30
31
  ].join('\n')
31
32
 
32
- const latest_schema = z.looseObject({ 'dist-tags': z.looseObject({ latest: z.string() }) })
33
+ // A registry-supplied version is used only when it is exactly a semver version — it ends up inside an
34
+ // install command, so `1.0.0;curl…|sh` or any other text is refused at the source, not downstream.
35
+ const version_schema = z.string().refine((value) => semver.valid(value) === value)
36
+ const latest_schema = z.looseObject({ 'dist-tags': z.looseObject({ latest: version_schema }) })
33
37
  const times_schema = z.looseObject({ time: release_age.release_times_schema })
34
38
 
35
39
  // The packument URL for a package name; a scoped name keeps its `@` and encodes the separator, the
@@ -42,7 +46,7 @@ function read_packument(package_name: string, accept: string): unknown {
42
46
  const result = execaSync(
43
47
  process.execPath,
44
48
  ['--input-type=module', '-e', FETCH_SCRIPT, packument_url(package_name), accept],
45
- { reject: false, timeout: REQUEST_TIMEOUT_MS },
49
+ { reject: false, timeout: FETCH_TIMEOUT_MS },
46
50
  )
47
51
  if (result.exitCode !== 0) return undefined
48
52
 
@@ -59,7 +63,7 @@ function is_github_scope(scope: string): boolean {
59
63
  if (is_cached_github !== undefined) return is_cached_github
60
64
  const result = execaSync('pnpm', ['config', 'get', `${scope}${SCOPE_REGISTRY_SUFFIX}`], {
61
65
  reject: false,
62
- timeout: REQUEST_TIMEOUT_MS,
66
+ timeout: FETCH_TIMEOUT_MS,
63
67
  })
64
68
  const is_github = result.exitCode === 0 && migrate_logic.is_github_tarball(result.stdout.trim())
65
69
 
@@ -102,6 +106,17 @@ function has_public_version(package_name: string, version: string): boolean {
102
106
  return parsed.success && parsed.data.time[version] !== undefined
103
107
  }
104
108
 
105
- const npm_registry = { has_public_version, packument_url, read_latest, read_release_times }
109
+ // The version itself, or nothing when it is not exactly a semver version.
110
+ function valid_version(value: string): string | undefined {
111
+ return version_schema.safeParse(value).success ? value : undefined
112
+ }
113
+
114
+ const npm_registry = {
115
+ has_public_version,
116
+ packument_url,
117
+ read_latest,
118
+ read_release_times,
119
+ valid_version,
120
+ }
106
121
 
107
122
  export { npm_registry }
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { readFileSync } from 'node:fs'
8
8
  import { fileURLToPath } from 'node:url'
9
+ import { COMMAND_TIMEOUT_MS } from '#scripts/lib/timeouts'
9
10
  import { execaSync } from 'execa'
10
11
  import {
11
12
  publishable_range,
@@ -15,7 +16,6 @@ import {
15
16
  } from './publishable-range'
16
17
 
17
18
  const PACKAGE_JSON_PATH = 'package.json'
18
- const VIEW_TIMEOUT_MS = 30_000
19
19
  const FAILURE_EXIT_CODE = 1
20
20
 
21
21
  // A registry query rather than a direct fetch: safe-chain installs shims ahead of the package
@@ -29,7 +29,7 @@ const FAILURE_EXIT_CODE = 1
29
29
  function probe_range(name: string, range: string): ProbeResult {
30
30
  const result = execaSync('pnpm', ['view', `${name}@${range}`, 'version'], {
31
31
  reject: false,
32
- timeout: VIEW_TIMEOUT_MS,
32
+ timeout: COMMAND_TIMEOUT_MS,
33
33
  })
34
34
 
35
35
  return { exit_code: result.exitCode ?? FAILURE_EXIT_CODE, stdout: result.stdout }
@@ -1,4 +1,4 @@
1
- import { package_with_deps_schema } from '#scripts/overrides/schemas'
1
+ import { package_with_deps_schema } from '#scripts/overrides/overrides-schemas'
2
2
  import semver from 'semver'
3
3
 
4
4
  // kit ships a `preinstall` hook that installs @aikidosec/safe-chain, whose minimum-age policy hides
@@ -1,5 +1,6 @@
1
1
  import { readFileSync } from 'node:fs'
2
2
  import path from 'node:path'
3
+ import { yaml_document } from '#scripts/lib/yaml-document'
3
4
  import semver from 'semver'
4
5
  import { z } from 'zod'
5
6
 
@@ -7,30 +8,41 @@ import { z } from 'zod'
7
8
  // safe-chain's interception. safe-chain filters the registry only when the process tree was
8
9
  // started through one of its six wrapped shell commands, so `josh latest` and
9
10
  // `pnpm josh latest` used to resolve different "newest release" answers. These functions
10
- // make the resolution deterministic: the policy comes from the repo-managed `.npmrc`, the
11
- // publish timestamps from the registry, and the selection is a pure computation over both.
11
+ // make the resolution deterministic: the policy comes from the repo-managed `pnpm-workspace.yaml`,
12
+ // the publish timestamps from the registry, and the selection is a pure computation over both.
13
+ // pnpm 12 reads its settings from `pnpm-workspace.yaml` — a `minimum-release-age` line in `.npmrc`
14
+ // is ignored there, so reading it would report a window pnpm does not apply (kit#3267).
12
15
 
13
- const MINIMUM_RELEASE_AGE_RE = /^minimum-release-age\s*=\s*(\d+)\s*$/mu
14
16
  const NO_QUARANTINE_MINUTES = 0
15
- const NPMRC_PATH = '.npmrc'
17
+ const WORKSPACE_PATH = 'pnpm-workspace.yaml'
18
+ const workspace_age_schema = z.object({ minimumReleaseAge: z.number().int().nonnegative() })
16
19
 
17
20
  // A version → ISO publish date map, the shape both the registry and the GitHub Packages API are
18
21
  // reduced to before the aged-version selection runs.
19
22
  const release_times_schema = z.record(z.string(), z.string())
20
23
  const MS_PER_MINUTE = 60_000
21
24
 
22
- // The window `.npmrc` content declares, or nothing when it does not declare one — an explicit
23
- // `minimum-release-age=0` stays 0 rather than collapsing into "not declared".
24
- function parse_declared_minimum_release_age(npmrc_content: string): number | undefined {
25
- const raw = MINIMUM_RELEASE_AGE_RE.exec(npmrc_content)?.[1]
25
+ function parse_workspace(workspace_content: string): unknown {
26
+ try {
27
+ return yaml_document.parse_yaml(workspace_content)
28
+ } catch {
29
+ return undefined
30
+ }
31
+ }
32
+
33
+ // The window `pnpm-workspace.yaml` content declares, or nothing when it does not declare one or the
34
+ // document is malformed — an explicit `minimumReleaseAge: 0` stays 0 rather than collapsing into
35
+ // "not declared".
36
+ function parse_declared_minimum_release_age(workspace_content: string): number | undefined {
37
+ const parsed = workspace_age_schema.safeParse(parse_workspace(workspace_content))
26
38
 
27
- return raw === undefined ? undefined : Number(raw)
39
+ return parsed.success ? parsed.data.minimumReleaseAge : undefined
28
40
  }
29
41
 
30
- // The quarantine window in minutes from `.npmrc`; a missing or malformed entry means no
31
- // quarantine, matching pnpm's own default for the setting.
32
- function parse_minimum_release_age(npmrc_content: string): number {
33
- return parse_declared_minimum_release_age(npmrc_content) ?? NO_QUARANTINE_MINUTES
42
+ // The quarantine window in minutes from `pnpm-workspace.yaml`; a missing or malformed entry means
43
+ // no quarantine, matching pnpm's own default for the setting.
44
+ function parse_minimum_release_age(workspace_content: string): number {
45
+ return parse_declared_minimum_release_age(workspace_content) ?? NO_QUARANTINE_MINUTES
34
46
  }
35
47
 
36
48
  // Stable releases only, on the requested major, published at or before the cutoff. The
@@ -83,49 +95,47 @@ function select_aged_version(
83
95
  return eligible[0]
84
96
  }
85
97
 
86
- // The window declared in one `.npmrc`, or nothing when the file is absent, unreadable, or does not
87
- // declare the setting. Distinguishing "not declared" from "declared as 0" is what lets an explicit
88
- // `minimum-release-age=0` opt-out stop the walk instead of falling through to an ancestor's policy.
89
- function read_declared_minimum_release_age(npmrc_path: string): number | undefined {
98
+ // The window declared in one `pnpm-workspace.yaml`, or nothing when the file is absent, unreadable,
99
+ // or does not declare the setting. Distinguishing "not declared" from "declared as 0" is what lets an
100
+ // explicit `minimumReleaseAge: 0` opt-out stop the walk instead of falling through to an ancestor's.
101
+ function read_declared_minimum_release_age(workspace_path: string): number | undefined {
90
102
  try {
91
- return parse_declared_minimum_release_age(readFileSync(npmrc_path, 'utf8'))
103
+ return parse_declared_minimum_release_age(readFileSync(workspace_path, 'utf8'))
92
104
  } catch {
93
105
  return undefined
94
106
  }
95
107
  }
96
108
 
97
- // The quarantine window declared by one specific `.npmrc`, defaulting to the project's own. An
98
- // absent, unreadable, or undeclared setting means no quarantine — the policy is advisory, and a
99
- // project without one is not held back.
109
+ // The quarantine window declared by one specific `pnpm-workspace.yaml`, defaulting to the project's
110
+ // own. An absent, unreadable, or undeclared setting means no quarantine — the policy is advisory,
111
+ // and a project without one is not held back.
100
112
  //
101
113
  // Deliberately not the upward walk below: `josh latest` resolves this against the working directory
102
- // it also writes `corepack use` into, and honouring a user-level policy there could freeze pnpm
114
+ // it also writes `corepack use` into, and honouring an ancestor's policy there could freeze pnpm
103
115
  // bumps in a project that declares none (joshuafolkken/kit#808).
104
- function read_minimum_release_age(npmrc_path: string = NPMRC_PATH): number {
105
- return read_declared_minimum_release_age(npmrc_path) ?? NO_QUARANTINE_MINUTES
116
+ function read_minimum_release_age(workspace_path: string = WORKSPACE_PATH): number {
117
+ return read_declared_minimum_release_age(workspace_path) ?? NO_QUARANTINE_MINUTES
106
118
  }
107
119
 
108
- // The window declared by the nearest `.npmrc` at or above `start`, or nothing.
120
+ // The window declared by the nearest `pnpm-workspace.yaml` at or above `start`, or nothing.
109
121
  //
110
- // Two reasons this walks rather than reading one path. `josh version` and `josh latest` both run
111
- // from anywhere inside a project — and from outside one — so resolving against the working directory
112
- // alone would silently read "no quarantine" in every subdirectory. And pnpm merges config per key
113
- // across project, workspace and user levels, so a project `.npmrc` that only sets a registry must
114
- // not mask a `~/.npmrc` carrying the policy (joshuafolkken/kit#808).
122
+ // This walks rather than reading one path because `josh version` and `josh latest` both run from
123
+ // anywhere inside a project — and from outside one — so resolving against the working directory
124
+ // alone would silently read "no quarantine" in every subdirectory (joshuafolkken/kit#808). pnpm
125
+ // finds its workspace root the same way, by walking up to the nearest `pnpm-workspace.yaml`.
115
126
  //
116
- // `boundary` stops the walk at a known root. Production passes none — a user-level `~/.npmrc` is a
117
- // legitimate source — while tests bound the search so it cannot depend on the machine's real home.
127
+ // `boundary` stops the walk at a known root. Production passes none, while tests bound the search
128
+ // so it cannot depend on the directories above the machine's temp root.
118
129
  //
119
- // Simplification: the walk visits every ancestor, whereas pnpm consults only the project, the
120
- // workspace root, `~/.npmrc` and its global config. A stray `.npmrc` in an intermediate directory
121
- // would therefore be honoured here and not by pnpm. The consequence is one informational `Held:`
122
- // line — the value never suppresses a command — so the faithful four-level model is not worth its
123
- // complexity here.
130
+ // Simplification: a nearer workspace file that does not declare the window falls through to an
131
+ // ancestor's, and pnpm's user-level `config.yaml` is not consulted. The consequence is one
132
+ // informational `Held:` line — the value never suppresses a command — so modelling pnpm's exact
133
+ // lookup is not worth its complexity here.
124
134
  function find_declared_minimum_release_age(
125
135
  start: string,
126
136
  boundary: string | undefined,
127
137
  ): number | undefined {
128
- const declared = read_declared_minimum_release_age(path.join(start, NPMRC_PATH))
138
+ const declared = read_declared_minimum_release_age(path.join(start, WORKSPACE_PATH))
129
139
  if (declared !== undefined) return declared
130
140
  if (start === boundary) return undefined
131
141
  const parent = path.dirname(start)
@@ -8,7 +8,7 @@ import { release_age } from './release-age'
8
8
  // is by definition the youngest (joshuafolkken/kit#808).
9
9
  //
10
10
  // **The window binds unpinned resolution only.** Measured on pnpm 11.22.0 with
11
- // `minimum-release-age=1440` and a release published 3.5 h earlier:
11
+ // `minimumReleaseAge: 1440` and a release published 3.5 h earlier:
12
12
  //
13
13
  // pnpm add pkg@1.80.0 -> 1.80.0 (pnpm records a `minimumReleaseAgeExclude` entry)
14
14
  // pnpm add pkg -> 1.78.0 (the newest release past the window)
@@ -0,0 +1,40 @@
1
+ import { COMMAND_TIMEOUT_MS } from '#scripts/lib/timeouts'
2
+ import { ci_installer_pin } from '#scripts/safe-chain/ci-installer-pin'
3
+ import { npm_registry } from '#scripts/version/npm-registry'
4
+ import { execaSync } from 'execa'
5
+
6
+ const SAFE_CHAIN_PKG = '@aikidosec/safe-chain'
7
+
8
+ // The registry's answer is trusted only as an exact semver version: it ends up in workflow files and
9
+ // in the installer URL (joshuafolkken/kit#3266).
10
+ function fetch_latest_version(): string | undefined {
11
+ const result = execaSync('npm', ['view', SAFE_CHAIN_PKG, 'version'], {
12
+ reject: false,
13
+ timeout: COMMAND_TIMEOUT_MS,
14
+ })
15
+ if (result.exitCode !== 0 || !result.stdout) return undefined
16
+
17
+ return npm_registry.valid_version(result.stdout.trim())
18
+ }
19
+
20
+ // The hash-verified installer pinned in the workflows is the one place a safe-chain release is
21
+ // recorded; `preinstall` no longer names one (joshuafolkken/kit#3269). Every repository moves the
22
+ // files that carry the pin — app-kit's own `dast.yml` / `load.yml` included (joshuafolkken/kit#2830).
23
+ function sync(): void {
24
+ const latest = fetch_latest_version()
25
+
26
+ if (latest === undefined) {
27
+ console.warn(`\n⚠ Could not fetch a valid latest version of ${SAFE_CHAIN_PKG}; skipping`)
28
+
29
+ return
30
+ }
31
+
32
+ ci_installer_pin.sync(latest)
33
+ }
34
+
35
+ const safe_chain_version_update = {
36
+ sync,
37
+ fetch_latest_version,
38
+ }
39
+
40
+ export { safe_chain_version_update }
@@ -1,9 +1,28 @@
1
1
  import type { PackageVersionConfig } from './version-command-config'
2
2
 
3
+ // One upgrade the version commands offer and run: `text` is the line printed after `Run:`, and
4
+ // `steps` are the argument vectors executed in order, each only after the one before it succeeded.
5
+ // A command kit builds itself is spawned without a shell, so a registry-supplied version is never
6
+ // parsed by one; only the consumer's own configured command goes through `sh -c`.
7
+ interface UpgradeCommand {
8
+ text: string
9
+ steps: ReadonlyArray<ReadonlyArray<string>>
10
+ }
11
+
12
+ const STEP_SEPARATOR = ' && '
13
+
3
14
  function update_scope_flag(is_local: boolean): string {
4
15
  return is_local ? '-D' : '-g'
5
16
  }
6
17
 
18
+ function update_argv(
19
+ latest: string,
20
+ is_local: boolean,
21
+ config: PackageVersionConfig,
22
+ ): Array<string> {
23
+ return ['pnpm', 'add', update_scope_flag(is_local), `${config.package_name}@${latest}`]
24
+ }
25
+
7
26
  // The bare `pnpm add` invocation that installs one package at an exact version, in the global or the
8
27
  // project (dev-dependency) scope.
9
28
  function format_update_command(
@@ -11,20 +30,41 @@ function format_update_command(
11
30
  is_local: boolean,
12
31
  config: PackageVersionConfig,
13
32
  ): string {
14
- return `pnpm add ${update_scope_flag(is_local)} ${config.package_name}@${latest}`
33
+ return update_argv(latest, is_local, config).join(' ')
34
+ }
35
+
36
+ // The upgrade for one target. A project-scope install is followed by kit's lockfile repair
37
+ // (`fix-gh-packages`), which a global install does not need. The printed text is derived from the
38
+ // steps, so the hint and what actually runs cannot drift apart.
39
+ function build_upgrade_command(
40
+ latest: string,
41
+ is_local: boolean,
42
+ config: PackageVersionConfig,
43
+ ): UpgradeCommand {
44
+ const steps = [update_argv(latest, is_local, config)]
45
+ if (is_local) steps.push(['node_modules/.bin/tsx', config.fix_gh_packages_path])
46
+
47
+ return { text: steps.map((step) => step.join(' ')).join(STEP_SEPARATOR), steps }
48
+ }
49
+
50
+ // The consumer's own configured command — `pnpm add -g …`, or a chain with `&&` — is inside the trust
51
+ // boundary and needs a shell to mean what it says.
52
+ function shell_upgrade_command(command: string): UpgradeCommand {
53
+ return { text: command, steps: [['sh', '-c', command]] }
15
54
  }
16
55
 
17
- // The full shell command for one upgrade target. A project-scope install is chained with kit's
18
- // lockfile repair (`fix-gh-packages`), which a global install does not need.
19
56
  function build_upgrade_shell_command(
20
57
  latest: string,
21
58
  is_local: boolean,
22
59
  config: PackageVersionConfig,
23
60
  ): string {
24
- const add_command = format_update_command(latest, is_local, config)
25
- if (!is_local) return add_command
26
-
27
- return `${add_command} && node_modules/.bin/tsx ${config.fix_gh_packages_path}`
61
+ return build_upgrade_command(latest, is_local, config).text
28
62
  }
29
63
 
30
- export { build_upgrade_shell_command, format_update_command }
64
+ export type { UpgradeCommand }
65
+ export {
66
+ build_upgrade_command,
67
+ build_upgrade_shell_command,
68
+ format_update_command,
69
+ shell_upgrade_command,
70
+ }
@@ -1,6 +1,12 @@
1
1
  import { release_hold } from './release-hold'
2
2
  import { is_no_op_upgrade_command, type InstalledVersions } from './upgrade-command-guard'
3
- import { build_upgrade_shell_command, format_update_command } from './upgrade-shell-command'
3
+ import {
4
+ build_upgrade_command,
5
+ build_upgrade_shell_command,
6
+ format_update_command,
7
+ shell_upgrade_command,
8
+ type UpgradeCommand,
9
+ } from './upgrade-shell-command'
4
10
  import type { PackageVersionConfig } from './version-command-config'
5
11
 
6
12
  const NOT_INSTALLED = 'not installed'
@@ -161,11 +167,11 @@ function resolve_effective_upgrade_hint(report: UpstreamReport): EffectiveUpgrad
161
167
  // The consumer-supplied global upgrade command for a stale effective upstream, or nothing. Skipped
162
168
  // when the consumer did not opt in, the effective install is up to date / unresolved, or the command
163
169
  // provably cannot change the effective version it was meant to fix (see the note line instead).
164
- function build_effective_upgrade_commands(report: UpstreamReport): Array<string> {
170
+ function build_effective_upgrade_commands(report: UpstreamReport): Array<UpgradeCommand> {
165
171
  const hint = resolve_effective_upgrade_hint(report)
166
172
  if (hint === undefined || hint.is_no_op) return []
167
173
 
168
- return [hint.command]
174
+ return [shell_upgrade_command(hint.command)]
169
175
  }
170
176
 
171
177
  // The explanation that replaces a suppressed `Run:` hint, so a stale effective install is never left
@@ -202,8 +208,9 @@ function format_upstream_lines(report: UpstreamReport): Array<string> {
202
208
  // Drop repeated commands while preserving first-seen order. A consumer with several upstreams can
203
209
  // return the same chain-pinning global command from each of them (game-kit returns one command for
204
210
  // both app-kit and kit), which would otherwise print an identical `Run:` line once per upstream.
205
- function unique_upgrade_commands(commands: ReadonlyArray<string>): Array<string> {
206
- return [...new Set(commands)]
211
+ // Keyed on the printed text, which the steps are derived from or carried verbatim inside.
212
+ function unique_upgrade_commands(commands: ReadonlyArray<UpgradeCommand>): Array<UpgradeCommand> {
213
+ return [...new Map(commands.map((command) => [command.text, command])).values()]
207
214
  }
208
215
 
209
216
  // The outcome line for an effective install the upgrade command left exactly where it was.
@@ -235,37 +242,39 @@ function format_effective_outcome(report: UpstreamReport, after: string | undefi
235
242
 
236
243
  // The project-scope upgrade command when the upstream's project dependency is installed and stale.
237
244
  // Always local (with lockfile repair), since the project path is the only scope kit resolves.
238
- function build_project_upgrade_commands(report: UpstreamReport): Array<string> {
245
+ function build_project_upgrade_commands(report: UpstreamReport): Array<UpgradeCommand> {
239
246
  if (!is_target_stale(report.project_version, report.latest)) return []
240
247
 
241
- return [build_upgrade_shell_command(report.latest, true, report.config)]
248
+ return [build_upgrade_command(report.latest, true, report.config)]
242
249
  }
243
250
 
244
251
  // Build the upgrade commands for every upstream: the consumer's global command for a stale effective
245
252
  // install first, then the project-scope command, mirroring the main report's global-before-project
246
253
  // order. Upstreams without either stale target contribute nothing.
247
- function build_upstream_upgrade_commands(reports: ReadonlyArray<UpstreamReport>): Array<string> {
254
+ function build_upstream_upgrade_commands(
255
+ reports: ReadonlyArray<UpstreamReport>,
256
+ ): Array<UpgradeCommand> {
248
257
  return reports.flatMap((report) => [
249
258
  ...build_effective_upgrade_commands(report),
250
259
  ...build_project_upgrade_commands(report),
251
260
  ])
252
261
  }
253
262
 
254
- // Build the shell upgrade commands for whichever of the two targets are installed and stale.
263
+ // Build the upgrade commands for whichever of the two targets are installed and stale.
255
264
  // Order: global first, then project (mirrors the display order).
256
265
  function build_dual_upgrade_commands(
257
266
  snapshot: VersionSnapshot,
258
267
  config: PackageVersionConfig,
259
- ): Array<string> {
268
+ ): Array<UpgradeCommand> {
260
269
  const { global_version, project_version, latest } = snapshot
261
- const commands: Array<string> = []
270
+ const commands: Array<UpgradeCommand> = []
262
271
 
263
272
  if (is_target_stale(global_version, latest)) {
264
- commands.push(build_upgrade_shell_command(latest, false, config))
273
+ commands.push(build_upgrade_command(latest, false, config))
265
274
  }
266
275
 
267
276
  if (is_target_stale(project_version, latest)) {
268
- commands.push(build_upgrade_shell_command(latest, true, config))
277
+ commands.push(build_upgrade_command(latest, true, config))
269
278
  }
270
279
 
271
280
  return commands
@@ -298,7 +307,7 @@ function format_dual_version_output(
298
307
  const hints = unique_upgrade_commands([
299
308
  ...build_dual_upgrade_commands(snapshot, config),
300
309
  ...build_upstream_upgrade_commands(upstreams),
301
- ]).map((command) => `Run: ${command}`)
310
+ ]).map((command) => `Run: ${command.text}`)
302
311
  if (hints.length > 0) lines.push('', ...hints)
303
312
  if (extras.warning !== undefined) lines.push('', extras.warning)
304
313
 
@@ -1,8 +1,10 @@
1
+ import { INSTALL_TIMEOUT_MS } from '#scripts/lib/timeouts'
1
2
  import { execaSync } from 'execa'
2
3
  import { release_age } from './release-age'
3
4
  import { release_hold } from './release-hold'
4
5
  import { running_binary } from './running-binary'
5
6
  import type { InstalledVersions } from './upgrade-command-guard'
7
+ import type { UpgradeCommand } from './upgrade-shell-command'
6
8
  import {
7
9
  version_check_logic,
8
10
  type ReleaseHold,
@@ -24,8 +26,6 @@ import { version_targets } from './version-targets'
24
26
  const FAILURE_EXIT_CODE = 1
25
27
  const ALREADY_UP_TO_DATE = 'Already up to date'
26
28
  const NO_QUARANTINE_MINUTES = 0
27
- // A global install that goes to the network — the same ten minutes `lane-install.ts` gives one.
28
- const UPGRADE_TIMEOUT_MS = 600_000
29
29
 
30
30
  // What the local minimum-release-age policy permits for one package. Resolved per package because
31
31
  // the newest installable release is a property of that package's own publish history. Returns a hold
@@ -197,23 +197,35 @@ function run_check(config: VersionCommandConfig): void {
197
197
  )
198
198
  }
199
199
 
200
- // Through `sh -c` because an upgrade command is the user's own configured string — `pnpm add -g …`,
201
- // or a chain with `&&` — so it is inside the trust boundary and needs a shell to mean what it says.
202
- // The bound keeps a registry that never answers from holding `josh version` open forever; a timed-out
203
- // run has no exit code, so it falls through to the failure code like any other failed step.
204
- function run_upgrade_command(command: string): number {
205
- const result = execaSync('sh', ['-c', command], {
200
+ // One step as an argument vector — no shell unless the step itself is the consumer's `sh -c`
201
+ // (`shell_upgrade_command`). The bound keeps a registry that never answers from holding `josh
202
+ // version` open forever; a timed-out run has no exit code, so it falls through to the failure code
203
+ // like any other failed step.
204
+ function run_upgrade_step(step: ReadonlyArray<string>): number {
205
+ const [executable = '', ...rest] = step
206
+ const result = execaSync(executable, rest, {
206
207
  stdio: 'inherit',
207
208
  reject: false,
208
- timeout: UPGRADE_TIMEOUT_MS,
209
+ timeout: INSTALL_TIMEOUT_MS,
209
210
  })
210
211
 
211
212
  return result.exitCode ?? FAILURE_EXIT_CODE
212
213
  }
213
214
 
215
+ // Run one command's steps in order, stopping at the first failure — what the `&&` in its printed
216
+ // text says.
217
+ function run_upgrade_command(command: UpgradeCommand): number {
218
+ for (const step of command.steps) {
219
+ const code = run_upgrade_step(step)
220
+ if (code !== 0) return code
221
+ }
222
+
223
+ return 0
224
+ }
225
+
214
226
  // Run every upgrade command in order, returning the last non-zero exit code (or 0 when all
215
227
  // succeed) so a failure on either target is surfaced without aborting the remaining upgrades.
216
- function run_all_upgrade_commands(commands: ReadonlyArray<string>): number {
228
+ function run_all_upgrade_commands(commands: ReadonlyArray<UpgradeCommand>): number {
217
229
  let exit_code = 0
218
230
  const failed_commands: Array<string> = []
219
231
 
@@ -221,7 +233,7 @@ function run_all_upgrade_commands(commands: ReadonlyArray<string>): number {
221
233
  const code = run_upgrade_command(command)
222
234
  if (code === 0) continue
223
235
  exit_code = code
224
- failed_commands.push(command)
236
+ failed_commands.push(command.text)
225
237
  }
226
238
 
227
239
  if (failed_commands.length > 0) {
@@ -23,19 +23,11 @@ function require_endpoint(versions_endpoint: string | undefined, package_name: s
23
23
  )
24
24
  }
25
25
 
26
- // Fetch the latest published version: public npm's `latest` dist-tag first, which needs no
27
- // credentials (joshuafolkken/kit#2882), then the GitHub Packages versions endpoint for a package
28
- // public npm does not answer for. The endpoint is supplied per package (e.g.
29
- // `/users/joshuafolkken/packages/npm/kit/versions?per_page=1`) so the same fetcher serves kit, jgame,
30
- // and app-kit. Guards an undefined/empty endpoint and wraps `gh api` failures with an actionable
31
- // message instead of a raw ExecaSyncError stack. The detail is the shared layer's failure text: gh's
32
- // own stderr (e.g. "gh: Not Found (HTTP 404)") when it wrote one, execa's message otherwise, followed
33
- // by any JSON error body gh wrote to stdout.
34
- function fetch_latest_version(versions_endpoint: string | undefined, package_name: string): string {
35
- const endpoint = require_endpoint(versions_endpoint, package_name)
36
- const public_latest = npm_registry.read_latest(package_name)
37
- if (public_latest !== undefined) return public_latest
38
-
26
+ // The newest version name on the GitHub Packages versions endpoint. Wraps `gh api` failures with an
27
+ // actionable message instead of a raw ExecaSyncError stack. The detail is the shared layer's failure
28
+ // text: gh's own stderr (e.g. "gh: Not Found (HTTP 404)") when it wrote one, execa's message
29
+ // otherwise, followed by any JSON error body gh wrote to stdout.
30
+ function read_github_name(endpoint: string, package_name: string): string {
39
31
  try {
40
32
  return git_gh_exec.exec_gh_api_sync({ path: endpoint, jq_filter: '.[0].name' }).trim()
41
33
  } catch (error) {
@@ -50,6 +42,29 @@ function fetch_latest_version(versions_endpoint: string | undefined, package_nam
50
42
  }
51
43
  }
52
44
 
45
+ // Refused at the source, as public npm's answer is: the value goes into an install command, so
46
+ // anything but a semver version is never passed on (joshuafolkken/kit#3266).
47
+ function read_github_latest(endpoint: string, package_name: string): string {
48
+ const name = read_github_name(endpoint, package_name)
49
+ const version = npm_registry.valid_version(name)
50
+ if (version !== undefined) return version
51
+
52
+ throw new Error(
53
+ `Refused latest version for ${package_name} via ${endpoint}: ${JSON.stringify(name)} is not a semver version`,
54
+ )
55
+ }
56
+
57
+ // Fetch the latest published version: public npm's `latest` dist-tag first, which needs no
58
+ // credentials (joshuafolkken/kit#2882), then the GitHub Packages versions endpoint for a package
59
+ // public npm does not answer for. The endpoint is supplied per package (e.g.
60
+ // `/users/joshuafolkken/packages/npm/kit/versions?per_page=1`) so the same fetcher serves kit, jgame,
61
+ // and app-kit. Guards an undefined/empty endpoint before either source is asked.
62
+ function fetch_latest_version(versions_endpoint: string | undefined, package_name: string): string {
63
+ const endpoint = require_endpoint(versions_endpoint, package_name)
64
+
65
+ return npm_registry.read_latest(package_name) ?? read_github_latest(endpoint, package_name)
66
+ }
67
+
53
68
  // Rewrite the endpoint's page size. The endpoint is consumer-overridable, so `per_page` is set
54
69
  // through a query parser rather than by substituting the literal the default happens to carry.
55
70
  function with_page_size(endpoint: string, page_size: number): string {
@@ -4,3 +4,7 @@
4
4
  allowBuilds:
5
5
  esbuild: true
6
6
  unrs-resolver: true
7
+
8
+ # pnpm 12 reads these settings from this file only; the same lines in .npmrc are ignored (kit #3267).
9
+ minimumReleaseAge: 1440
10
+ engineStrict: true