@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
@@ -11,12 +11,12 @@
11
11
  * major equals the `packageManager` pin keeps both invariants: never below the adopted
12
12
  * major, and advancing while that major is the current one.
13
13
  *
14
- * The `minimum-release-age` quarantine is applied natively from `.npmrc` (kit#768).
15
- * safe-chain filters the registry only when the process tree was launched through one of
16
- * its wrapped shell commands, so `josh latest` and `pnpm josh latest` used to resolve
14
+ * The `minimumReleaseAge` quarantine is applied natively from `pnpm-workspace.yaml` (kit#768,
15
+ * #3267). safe-chain filters the registry only when the process tree was launched through one
16
+ * of its wrapped shell commands, so `josh latest` and `pnpm josh latest` used to resolve
17
17
  * different answers and the pin oscillated. Reading the window from the repo-managed
18
- * `.npmrc` and filtering by publish timestamp makes the resolution identical in every
19
- * invocation context; right after a pnpm release the answer is simply the previous
18
+ * `pnpm-workspace.yaml` and filtering by publish timestamp makes the resolution identical in
19
+ * every invocation context; right after a pnpm release the answer is simply the previous
20
20
  * release, exactly as the filtered view behaved.
21
21
  *
22
22
  * The resolved version is floored at the current `packageManager` pin: a filtered registry
@@ -38,6 +38,7 @@
38
38
  import { readFileSync, writeFileSync } from 'node:fs'
39
39
  import { fileURLToPath } from 'node:url'
40
40
  import { json_value } from '#scripts/lib/json-value'
41
+ import { COMMAND_TIMEOUT_MS } from '#scripts/lib/timeouts'
41
42
  import { execaSync } from 'execa'
42
43
  import semver from 'semver'
43
44
  import { z } from 'zod'
@@ -49,8 +50,7 @@ const PACKAGE_MANAGER_RE = /"packageManager"\s*:\s*"pnpm@(\d+)(?:[^\d]|$)/u
49
50
  const PINNED_VERSION_RE = /"packageManager"\s*:\s*"pnpm@([^"+]+)/u
50
51
  const TARGET_PREFIX = 'pnpm@'
51
52
  const FAILURE_EXIT_CODE = 1
52
- const NPMRC_PATH = '.npmrc'
53
- const VIEW_TIMEOUT_MS = 30_000
53
+ const WORKSPACE_PATH = 'pnpm-workspace.yaml'
54
54
  const SHA512_BYTES = 64
55
55
  const INTEGRITY_RE = /^sha512-([A-Za-z0-9+/]+={0,2})$/u
56
56
  const PACKAGE_MANAGER_VALUE_RE = /("packageManager"\s*:\s*")pnpm@[^"]+(")/u
@@ -121,7 +121,7 @@ function extract_times_json(stdout: string): Record<string, string> | undefined
121
121
  function query_release_times(): Record<string, string> | undefined {
122
122
  const result = execaSync('pnpm', ['view', 'pnpm', 'time', '--json'], {
123
123
  reject: false,
124
- timeout: VIEW_TIMEOUT_MS,
124
+ timeout: COMMAND_TIMEOUT_MS,
125
125
  })
126
126
  if ((result.exitCode ?? FAILURE_EXIT_CODE) !== 0) return undefined
127
127
 
@@ -134,14 +134,14 @@ function query_major_latest_version(major: string): string | undefined {
134
134
  const times = query_release_times()
135
135
  if (times === undefined) return undefined
136
136
 
137
- // The project's own `.npmrc`, deliberately not the upward walk the version check uses: `josh
138
- // latest` reads `package.json` and writes the pnpm pin relative to the working directory, so it
139
- // has no subdirectory case — and honouring a user-level policy here could freeze pnpm bumps in a
140
- // project that declares none (joshuafolkken/kit#808).
137
+ // The project's own `pnpm-workspace.yaml`, deliberately not the upward walk the version check
138
+ // uses: `josh latest` reads `package.json` and writes the pnpm pin relative to the working
139
+ // directory, so it has no subdirectory case — and honouring an ancestor's policy here could freeze
140
+ // pnpm bumps in a project that declares none (joshuafolkken/kit#808).
141
141
  return release_age.select_aged_version(
142
142
  times,
143
143
  major,
144
- release_age.read_minimum_release_age(NPMRC_PATH),
144
+ release_age.read_minimum_release_age(WORKSPACE_PATH),
145
145
  Date.now(),
146
146
  )
147
147
  }
@@ -156,7 +156,7 @@ function parse_latest_version(stdout: string): string | undefined {
156
156
  function query_latest_version(): string | undefined {
157
157
  const result = execaSync('pnpm', ['view', 'pnpm', 'version', '--json'], {
158
158
  reject: false,
159
- timeout: VIEW_TIMEOUT_MS,
159
+ timeout: COMMAND_TIMEOUT_MS,
160
160
  })
161
161
  if ((result.exitCode ?? FAILURE_EXIT_CODE) !== 0) return undefined
162
162
 
@@ -179,8 +179,8 @@ function warn_skip(reason: string): void {
179
179
  console.warn(`⚠ Skipped pnpm bump (${reason}); the chain continues.`)
180
180
  }
181
181
 
182
- function warn_unresolved(major: string | undefined): void {
183
- warn_skip(`no pnpm ${major ?? ''} release resolvable from the registry`)
182
+ function warn_unresolved(major: string | undefined = ''): void {
183
+ warn_skip(`no pnpm ${major} release resolvable from the registry`)
184
184
  }
185
185
 
186
186
  function run_pnpm_update(target: string): number {
@@ -247,7 +247,7 @@ function extract_encoded_integrity(stdout: string): string | undefined {
247
247
  function query_integrity(target: string): string | undefined {
248
248
  const result = execaSync('pnpm', ['view', target, 'dist.integrity', '--json'], {
249
249
  reject: false,
250
- timeout: VIEW_TIMEOUT_MS,
250
+ timeout: COMMAND_TIMEOUT_MS,
251
251
  })
252
252
  if ((result.exitCode ?? FAILURE_EXIT_CODE) !== 0) return undefined
253
253
 
@@ -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