@joshuafolkken/kit 1.1178.0 → 1.1240.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/settings.json +2 -2
  3. package/.claude/skills/dependency-update/SKILL.md +4 -8
  4. package/.claude/skills/epic-commands/SKILL.md +2 -2
  5. package/.claude/skills/epic-commands/epic-bundle.md +1 -1
  6. package/.claude/skills/workflow-commands/SKILL.md +2 -4
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +7 -3
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +23 -2
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +9 -4
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +21 -13
  13. package/.claude/skills/workflow-commands/chain-rule.md +1 -1
  14. package/.claude/skills/workflow-commands/delegation.md +2 -2
  15. package/.claude/skills/workflow-commands/followup.md +10 -0
  16. package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
  17. package/.claude/skills/workflow-commands/halfrun.md +1 -1
  18. package/.claude/skills/workflow-commands/issue-scout.md +1 -1
  19. package/.claude/skills/workflow-commands/kickoff.md +2 -1
  20. package/.claude/skills/workflow-commands/latest-gate.md +1 -1
  21. package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
  22. package/.claude/skills/workflow-commands/pre-gate-cut.md +6 -1
  23. package/.claude/skills/workflow-commands/prerequisite.md +2 -2
  24. package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
  25. package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
  26. package/.codex/hooks.json +2 -2
  27. package/.cursorrules +6 -41
  28. package/.github/actions/setup-node/action.yml +1 -1
  29. package/.github/workflows/auto-tag.yml +3 -4
  30. package/.github/workflows/ci.yml +32 -31
  31. package/.github/workflows/dependabot-auto-merge.yml +13 -6
  32. package/.github/workflows/publish.yml +50 -4
  33. package/.github/workflows/sonar-qube.yml +3 -0
  34. package/.pnpmfile.mjs +4 -3
  35. package/.prettierignore +8 -2
  36. package/CLAUDE.md +18 -20
  37. package/README.md +44 -38
  38. package/SECURITY.md +15 -1
  39. package/dist/CLAUDE.md +18 -20
  40. package/dist/commands/document-section-cli.js +734 -0
  41. package/dist/hooks/audit-provision.js +346 -0
  42. package/dist/hooks/batch-guard.js +5 -4
  43. package/dist/hooks/{chunk-G6KRCKEC.js → chunk-4TZVB44A.js} +496 -378
  44. package/dist/hooks/chunk-5VNDSNG3.js +12 -0
  45. package/dist/hooks/chunk-CDY4NFQG.js +15 -0
  46. package/dist/hooks/chunk-ELCN5LCH.js +135 -0
  47. package/dist/hooks/{chunk-VQLLNFNT.js → chunk-F3ELQ2AF.js} +3 -3
  48. package/dist/hooks/{chunk-I4XSL4TT.js → chunk-GPTFMK5E.js} +1 -1
  49. package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
  50. package/dist/hooks/{chunk-UAGLLIGZ.js → chunk-KHGB5S3Y.js} +7 -6
  51. package/dist/hooks/{chunk-GDS5N2KB.js → chunk-KHX4U3BG.js} +735 -683
  52. package/dist/hooks/{chunk-VESKRQOF.js → chunk-KRBERBBI.js} +3 -3
  53. package/dist/hooks/{chunk-DO6NWWHU.js → chunk-QVSWU24O.js} +4 -4
  54. package/dist/hooks/{chunk-S6X6WYKR.js → chunk-WK2XSHSI.js} +1643 -923
  55. package/dist/hooks/{chunk-ZAL3NRGL.js → chunk-XZYKEM6J.js} +25 -10
  56. package/dist/hooks/{chunk-32GQTL4F.js → chunk-ZQGD4GBY.js} +4 -2
  57. package/dist/hooks/codex-hook-adapter.js +14 -12
  58. package/dist/hooks/duplicate-read-guard.js +6 -5
  59. package/dist/hooks/format-edited.js +4 -3
  60. package/dist/hooks/inputs.json +203 -179
  61. package/dist/hooks/investigation-guard.js +6 -5
  62. package/dist/hooks/pretool-guard.js +12 -10
  63. package/dist/hooks/session-lang.js +2 -4
  64. package/dist/hooks/stop-guard.js +66 -72
  65. package/dist/josh.js +1088 -78
  66. package/dist/version/index.d.ts +7 -2
  67. package/dist/version/index.js +152 -75
  68. package/eslint/base.js +23 -1
  69. package/eslint/rules/code-quality.js +3 -0
  70. package/eslint/rules/early-return-one-liner.js +98 -0
  71. package/eslint/rules/unicorn.js +16 -1
  72. package/package.json +7 -6
  73. package/pnpm-workspace.yaml +9 -4
  74. package/prettier/basic.d.ts +3 -1
  75. package/prettier/index.d.ts +3 -1
  76. package/prettier/static.d.ts +3 -1
  77. package/prompts/coding-standards.md +1 -1
  78. package/prompts/collaboration-workflow/file-edits.md +4 -4
  79. package/prompts/collaboration-workflow/glossary.md +1 -1
  80. package/prompts/collaboration-workflow/issue-template.md +1 -0
  81. package/prompts/collaboration-workflow/operating-rules.md +17 -9
  82. package/prompts/collaboration-workflow/overview.md +2 -2
  83. package/prompts/collaboration-workflow/principles.md +14 -12
  84. package/prompts/collaboration-workflow/report-format.md +1 -1
  85. package/prompts/collaboration-workflow/residency.md +1 -1
  86. package/prompts/collaboration-workflow/rule-delivery.md +6 -6
  87. package/prompts/collaboration-workflow/shell-body.md +6 -6
  88. package/prompts/collaboration-workflow/turn-batching.md +1 -1
  89. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
  90. package/prompts/collaboration-workflow/wip-cap.md +10 -10
  91. package/prompts/review-rubric.md +1 -2
  92. package/prompts/review.md +1 -1
  93. package/scripts/agent/agent-headless.ts +29 -0
  94. package/scripts/agent/agent-launch-environment.ts +6 -2
  95. package/scripts/agent/codex-agent-argv.ts +2 -1
  96. package/scripts/auto-ok/auto-ok-cli.ts +1 -1
  97. package/scripts/backlog/backlog-arrival.ts +3 -3
  98. package/scripts/backlog/backlog-budget.ts +1 -1
  99. package/scripts/backlog/backlog-drive-args.ts +135 -0
  100. package/scripts/backlog/backlog-drive-cli.ts +11 -137
  101. package/scripts/backlog/backlog-drive-launch.ts +39 -0
  102. package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
  103. package/scripts/backlog/backlog-drive-named.ts +3 -3
  104. package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
  105. package/scripts/backlog/backlog-drive-owner.ts +1 -1
  106. package/scripts/backlog/backlog-drive-restore.ts +1 -1
  107. package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
  108. package/scripts/backlog/backlog-drive.ts +1 -1
  109. package/scripts/backlog/backlog-next.ts +16 -5
  110. package/scripts/backlog/backlog-offer-cli.ts +6 -6
  111. package/scripts/backlog/backlog-offer.ts +1 -1
  112. package/scripts/backlog/backlog-plan-cli.ts +1 -1
  113. package/scripts/backlog/backlog-pool.ts +57 -11
  114. package/scripts/backlog/backlog-ready.ts +1 -1
  115. package/scripts/backlog/backlog-scope.ts +1 -1
  116. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  117. package/scripts/backlog/backlog-stalled.ts +1 -1
  118. package/scripts/build/build-codex-hooks.ts +3 -1
  119. package/scripts/build/build-commands.ts +66 -0
  120. package/scripts/build/build-hooks.ts +2 -1
  121. package/scripts/build/build-library.ts +12 -3
  122. package/scripts/build/build.ts +70 -0
  123. package/scripts/build/local-bin.ts +3 -13
  124. package/scripts/delegation/investigation-reads.ts +3 -1
  125. package/scripts/discovery/repo-origin.ts +5 -5
  126. package/scripts/doctor/doctor-consumer.ts +2 -2
  127. package/scripts/doctor/doctor-io.ts +2 -3
  128. package/scripts/document/capped-print-part.ts +30 -0
  129. package/scripts/document/capped-print.ts +3 -4
  130. package/scripts/document/document-byte-budget.ts +11 -2
  131. package/scripts/document/document-scan.ts +117 -0
  132. package/scripts/document/document-section.ts +143 -16
  133. package/scripts/document/entry-read-budget.ts +5 -3
  134. package/scripts/document/entry-read-set.ts +10 -89
  135. package/scripts/document/read-set-cost.ts +98 -0
  136. package/scripts/epic/epic-add-body.ts +1 -0
  137. package/scripts/epic/epic-audit-checks.ts +1 -1
  138. package/scripts/epic/epic-audit-rationale.ts +4 -3
  139. package/scripts/epic/epic-bundle-cli.ts +14 -3
  140. package/scripts/epic/epic-bundle-poll.ts +41 -0
  141. package/scripts/epic/epic-busy.ts +1 -1
  142. package/scripts/epic/epic-candidate-confirm.ts +3 -2
  143. package/scripts/epic/epic-classify.ts +1 -1
  144. package/scripts/epic/epic-cli-argv.ts +78 -46
  145. package/scripts/epic/epic-cli-remove.ts +11 -19
  146. package/scripts/epic/epic-cli.ts +74 -73
  147. package/scripts/epic/epic-close-comment.ts +1 -1
  148. package/scripts/epic/epic-close.ts +1 -1
  149. package/scripts/epic/epic-cross-repo.ts +5 -5
  150. package/scripts/epic/epic-graph.ts +1 -1
  151. package/scripts/epic/epic-index.ts +1 -1
  152. package/scripts/epic/epic-issue.ts +2 -6
  153. package/scripts/epic/epic-lane-offer.ts +2 -2
  154. package/scripts/epic/epic-next.ts +29 -42
  155. package/scripts/epic/epic-solo-stale.ts +1 -1
  156. package/scripts/epic/epic-triage.ts +8 -4
  157. package/scripts/epic/epic-validate.ts +1 -1
  158. package/scripts/followup/git-followup-finish.ts +2 -2
  159. package/scripts/followup/git-followup-label.ts +2 -2
  160. package/scripts/followup/git-followup-merged.ts +2 -2
  161. package/scripts/followup/git-pr-followup-checks.ts +142 -0
  162. package/scripts/followup/git-pr-followup.ts +4 -129
  163. package/scripts/gate/core-admission.ts +162 -0
  164. package/scripts/gate/core-budget.ts +190 -68
  165. package/scripts/gate/gate-plan.ts +80 -7
  166. package/scripts/gate/gate-report.ts +28 -9
  167. package/scripts/gate/gate-skip.ts +38 -7
  168. package/scripts/gate/hook-gate-reuse.ts +12 -10
  169. package/scripts/gate/machine-capacity.ts +175 -0
  170. package/scripts/gate/pre-commit-type-check.ts +4 -1
  171. package/scripts/gate/project-checks.ts +6 -9
  172. package/scripts/gate/type-check-step.ts +2 -3
  173. package/scripts/gate/verification-gate.ts +34 -10
  174. package/scripts/gh/git-gh-api-path.ts +2 -2
  175. package/scripts/gh/git-gh-issue-rest.ts +3 -3
  176. package/scripts/gh/git-gh-issue-write.ts +53 -7
  177. package/scripts/gh/git-gh-pr-read.ts +64 -19
  178. package/scripts/gh/git-gh-pr-rest.ts +1 -1
  179. package/scripts/gh/git-gh-pr.ts +5 -1
  180. package/scripts/gh/git-pr-ai-review.ts +1 -1
  181. package/scripts/gh/git-pr-checks-eval.ts +1 -3
  182. package/scripts/gh/git-pr-checks-parse.ts +1 -1
  183. package/scripts/gh/git-pr-checks-watch.ts +6 -5
  184. package/scripts/gh/git-pr-checks.ts +7 -5
  185. package/scripts/gh/git-pr-coderabbit.ts +1 -1
  186. package/scripts/gh/git-pr-error.ts +1 -3
  187. package/scripts/gh/git-pr.ts +9 -13
  188. package/scripts/gh/github-issue-url.ts +2 -2
  189. package/scripts/git/constants.ts +2 -6
  190. package/scripts/git/git-command.ts +20 -218
  191. package/scripts/git/git-diff-reads.ts +225 -0
  192. package/scripts/git/git-error.ts +4 -12
  193. package/scripts/git/git-pre-push-hook.ts +139 -0
  194. package/scripts/git/git-prompt.ts +2 -6
  195. package/scripts/git/git-spawn-sync.ts +3 -1
  196. package/scripts/git/git-spawn.ts +1 -1
  197. package/scripts/git/git-staging.ts +4 -6
  198. package/scripts/git/git-status.ts +3 -9
  199. package/scripts/hooks/codex-hook-adapter.ts +1 -1
  200. package/scripts/hooks/edited-cspell.ts +2 -4
  201. package/scripts/hooks/format-edited-file.ts +3 -12
  202. package/scripts/hooks/pre-push-unit.ts +31 -9
  203. package/scripts/hooks/reserved-run.ts +2 -2
  204. package/scripts/hooks/run-hook.sh +5 -4
  205. package/scripts/hooks/stop-guard.ts +5 -3
  206. package/scripts/init/init-actions.ts +0 -5
  207. package/scripts/init/init-ai-copy.ts +13 -2
  208. package/scripts/init/init-logic-json-merge.ts +19 -0
  209. package/scripts/init/init-logic-secretlint.ts +7 -7
  210. package/scripts/init/init-logic-workspace.ts +41 -2
  211. package/scripts/init/init-logic.ts +16 -26
  212. package/scripts/init/init.ts +10 -16
  213. package/scripts/init/initial-commit.ts +4 -4
  214. package/scripts/init/kit-development-versions.ts +40 -0
  215. package/scripts/init/kit-setup-state.ts +2 -2
  216. package/scripts/init/package-manager-pin.ts +35 -0
  217. package/scripts/init/start-exec.ts +68 -19
  218. package/scripts/init/start-setup-pr.ts +9 -9
  219. package/scripts/init/start-steps.ts +6 -10
  220. package/scripts/issue/defect-rate-cli.ts +1 -1
  221. package/scripts/issue/git-next-issues.ts +1 -1
  222. package/scripts/issue/issue-auto-ok.ts +32 -7
  223. package/scripts/issue/issue-backlinks-cli.ts +2 -2
  224. package/scripts/issue/issue-backlinks.ts +9 -9
  225. package/scripts/issue/issue-comment-cli.ts +19 -13
  226. package/scripts/issue/issue-file-cli.ts +72 -22
  227. package/scripts/issue/issue-file.ts +29 -1
  228. package/scripts/issue/issue-labels.ts +13 -0
  229. package/scripts/issue/issue-release-cli.ts +155 -0
  230. package/scripts/issue/issue-release.ts +58 -0
  231. package/scripts/issue/issue-scout-closed.ts +1 -1
  232. package/scripts/issue/issue-state-cli.ts +30 -74
  233. package/scripts/josh/josh-command-map.ts +2 -0
  234. package/scripts/josh/josh-command-types.ts +14 -0
  235. package/scripts/josh/josh-commands-ai.ts +4 -4
  236. package/scripts/josh/josh-commands-backlog.ts +4 -1
  237. package/scripts/josh/josh-commands-development.ts +18 -0
  238. package/scripts/josh/josh-commands-document.ts +1 -0
  239. package/scripts/josh/josh-commands-issue.ts +8 -1
  240. package/scripts/josh/josh-commands-lane.ts +24 -1
  241. package/scripts/josh/josh-commands-maintenance.ts +6 -6
  242. package/scripts/josh/josh-commands-run.ts +12 -7
  243. package/scripts/josh/josh-in-process.ts +28 -9
  244. package/scripts/josh/josh-logic.ts +34 -15
  245. package/scripts/josh/process-identity.ts +1 -3
  246. package/scripts/josh/session-language-cli.ts +3 -4
  247. package/scripts/lane/lane-await.ts +5 -1
  248. package/scripts/lane/lane-child-invocation.ts +2 -2
  249. package/scripts/lane/lane-cli.ts +12 -4
  250. package/scripts/lane/lane-dispatch.ts +14 -10
  251. package/scripts/lane/lane-environment.ts +5 -1
  252. package/scripts/lane/lane-handoff.ts +1 -1
  253. package/scripts/lane/lane-install.ts +2 -13
  254. package/scripts/lane/lane-launch-cli.ts +15 -1
  255. package/scripts/lane/lane-ledger.ts +145 -0
  256. package/scripts/lane/lane-leftover.ts +44 -30
  257. package/scripts/lane/lane-load.ts +108 -0
  258. package/scripts/lane/lane-measure-cli.ts +149 -0
  259. package/scripts/lane/lane-occupancy.ts +3 -0
  260. package/scripts/lane/lane-open.ts +5 -5
  261. package/scripts/lane/lane-paths.ts +13 -0
  262. package/scripts/lane/lane-reap.ts +1 -3
  263. package/scripts/lane/lane-registry.ts +11 -0
  264. package/scripts/lane/lane-relaunch.ts +1 -1
  265. package/scripts/lane/lane-stats.ts +224 -0
  266. package/scripts/lane/lane-stray.ts +140 -0
  267. package/scripts/lane/lane-vacant.ts +36 -0
  268. package/scripts/lane/openai-lane-ship.ts +2 -2
  269. package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
  270. package/scripts/lane/openai-lane-supervisor.ts +7 -165
  271. package/scripts/lane/openai-review-broker.ts +2 -4
  272. package/scripts/lib/ancestor-directories.ts +35 -0
  273. package/scripts/lib/animation-helpers.ts +2 -2
  274. package/scripts/lib/buffered-process.ts +3 -7
  275. package/scripts/lib/cli-flags.ts +71 -1
  276. package/scripts/lib/spawn-exit.ts +2 -2
  277. package/scripts/lib/timeouts.ts +35 -2
  278. package/scripts/lines/line-budget.ts +4 -6
  279. package/scripts/notify/telegram-test.ts +6 -0
  280. package/scripts/observations/observation-ledger-line.ts +6 -6
  281. package/scripts/observations/observation-ledger-migrate.ts +55 -4
  282. package/scripts/observations/observation-ledger.ts +26 -7
  283. package/scripts/overrides/lockfile-overrides.ts +1 -1
  284. package/scripts/overrides/overrides-logic.ts +1 -1
  285. package/scripts/propagate/propagate-git.ts +1 -1
  286. package/scripts/propagate/propagate-publish.ts +2 -2
  287. package/scripts/propagate/propagate-steps.ts +20 -15
  288. package/scripts/propagate/propagate.ts +36 -27
  289. package/scripts/refactor/import-graph.ts +18 -1
  290. package/scripts/refactor/refactor-lint.ts +4 -4
  291. package/scripts/release/github-release-environment.ts +1 -0
  292. package/scripts/release/github-release-runs.ts +115 -0
  293. package/scripts/release/github-release.ts +13 -67
  294. package/scripts/repo/repository-labels.ts +2 -2
  295. package/scripts/repo/required-checks-logic.ts +1 -3
  296. package/scripts/review/live-evidence.ts +2 -4
  297. package/scripts/review/review-brief-cli.ts +1 -1
  298. package/scripts/review/review-finding-ledger.ts +1 -1
  299. package/scripts/review/review-findings-cli.ts +1 -3
  300. package/scripts/rules/decision-oracle-batch.ts +128 -0
  301. package/scripts/rules/decision-oracle-tokens.ts +21 -0
  302. package/scripts/rules/decision-oracle.ts +8 -120
  303. package/scripts/rules/delivered-rules.ts +26 -182
  304. package/scripts/rules/direct-filing.ts +1 -1
  305. package/scripts/rules/early-heartbeat.ts +3 -3
  306. package/scripts/rules/git-argv.ts +1 -1
  307. package/scripts/rules/git-force.ts +1 -1
  308. package/scripts/rules/implementation-cut.ts +2 -2
  309. package/scripts/rules/index-guard.ts +1 -1
  310. package/scripts/rules/issue-comments.ts +118 -0
  311. package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
  312. package/scripts/rules/lane-background.ts +2 -2
  313. package/scripts/rules/lane-park.ts +18 -0
  314. package/scripts/rules/lane-split-park.ts +128 -0
  315. package/scripts/rules/oracle-firing.ts +1 -1
  316. package/scripts/rules/poll-loop.ts +1 -1
  317. package/scripts/rules/pre-gate-cut.ts +1 -1
  318. package/scripts/rules/protected-files.ts +59 -4
  319. package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
  320. package/scripts/rules/shell-body-trigger.ts +1 -1
  321. package/scripts/rules/stop-rules.ts +18 -7
  322. package/scripts/rules/test-declared-commit.ts +4 -4
  323. package/scripts/rules/wip-cap.ts +63 -0
  324. package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
  325. package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
  326. package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
  327. package/scripts/run/{run-carry.ts → carry/run-carry.ts} +1 -1
  328. package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
  329. package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +18 -5
  330. package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
  331. package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
  332. package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
  333. package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +1 -0
  334. package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
  335. package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
  336. package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +20 -2
  337. package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
  338. package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
  339. package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
  340. package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
  341. package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
  342. package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
  343. package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
  344. package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
  345. package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
  346. package/scripts/run/run-ending.ts +2 -2
  347. package/scripts/run/run-halfrun-resume.ts +6 -1
  348. package/scripts/run/run-headless.ts +1 -1
  349. package/scripts/run/run-invocation.ts +85 -84
  350. package/scripts/run/run-liveness.ts +1 -1
  351. package/scripts/run/run-parent-cut-hook.ts +1 -1
  352. package/scripts/run/run-preflight.ts +6 -6
  353. package/scripts/run/run-prep-cli.ts +1 -1
  354. package/scripts/run/run-prrun-resume.ts +1 -1
  355. package/scripts/run/run-report-cli.ts +4 -4
  356. package/scripts/run/run-report.ts +2 -2
  357. package/scripts/run/run-status-cli.ts +1 -1
  358. package/scripts/run/run-step-cli.ts +3 -3
  359. package/scripts/run/run-step.ts +7 -4
  360. package/scripts/run/run-stop-notify.ts +1 -1
  361. package/scripts/run/run-stranded-detect.ts +4 -4
  362. package/scripts/run/run-stranded.ts +2 -2
  363. package/scripts/run/run-watcher-guard-cli.ts +1 -1
  364. package/scripts/run/run-watcher-guard.ts +5 -3
  365. package/scripts/run/run-watcher-hook.ts +1 -1
  366. package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +2 -2
  367. package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
  368. package/scripts/run/ship/run-ship-lock.ts +50 -0
  369. package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
  370. package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
  371. package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +4 -3
  372. package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
  373. package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
  374. package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +11 -12
  375. package/scripts/run/ship/run-ship-stop-text.ts +13 -0
  376. package/scripts/run/{run-ship-sync.ts → ship/run-ship-sync.ts} +25 -11
  377. package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
  378. package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +25 -11
  379. package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +58 -7
  380. package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +59 -8
  381. package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
  382. package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +13 -15
  383. package/scripts/run/wake/run-wake-restart.ts +58 -0
  384. package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
  385. package/scripts/run/{run-wake.ts → wake/run-wake.ts} +16 -65
  386. package/scripts/safe-chain/preinstall-command.ts +20 -21
  387. package/scripts/safe-chain/project-config.ts +19 -15
  388. package/scripts/sync/sync-ai-files.ts +1 -1
  389. package/scripts/sync/sync-configs.ts +2 -1
  390. package/scripts/sync/sync.ts +15 -2
  391. package/scripts/test/unit-worker-share.ts +1 -0
  392. package/scripts/time-runtime/time-background.ts +5 -1
  393. package/scripts/time-runtime/time-instant.ts +3 -1
  394. package/scripts/time-runtime/time-spans.ts +6 -139
  395. package/scripts/time-runtime/time-tool-call.ts +160 -0
  396. package/scripts/version/effective-upstream.ts +2 -20
  397. package/scripts/version/latest-corepack.ts +124 -93
  398. package/scripts/version/latest-regression.ts +1 -1
  399. package/scripts/version/latest-update.ts +4 -4
  400. package/scripts/version/npm-registry.ts +20 -5
  401. package/scripts/version/publishable-range-check.ts +2 -2
  402. package/scripts/version/publishable-range.ts +1 -1
  403. package/scripts/version/release-age.ts +48 -38
  404. package/scripts/version/release-hold.ts +1 -1
  405. package/scripts/version/safe-chain-version-update.ts +40 -0
  406. package/scripts/version/upgrade-shell-command.ts +48 -8
  407. package/scripts/version/version-check-logic.ts +23 -14
  408. package/scripts/version/version-commands.ts +23 -11
  409. package/scripts/version/version-remote.ts +28 -13
  410. package/templates/pnpm-workspace.basic.yaml +4 -0
  411. package/templates/workflows/dependabot-auto-merge.yml +5 -5
  412. package/templates/workflows/github-release.yml +6 -0
  413. package/scripts/build/build-config-merge.ts +0 -27
  414. package/scripts/build/build-managed-marker.ts +0 -27
  415. package/scripts/build/build-self-sync-guard.ts +0 -27
  416. package/scripts/build/build-version.ts +0 -26
  417. package/scripts/version/preinstall-version-update.ts +0 -86
  418. /package/scripts/git/{schemas.ts → git-schemas.ts} +0 -0
  419. /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
  420. /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
  421. /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
  422. /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
  423. /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
  424. /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
  425. /package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +0 -0
  426. /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
  427. /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
  428. /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
  429. /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
  430. /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
  431. /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
  432. /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
  433. /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
  434. /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
  435. /package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +0 -0
  436. /package/scripts/run/{run-ship-pre-detach.ts → ship/run-ship-pre-detach.ts} +0 -0
  437. /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
  438. /package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +0 -0
  439. /package/scripts/run/{run-ship.ts → ship/run-ship.ts} +0 -0
  440. /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
  441. /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
  442. /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
@@ -0,0 +1,160 @@
1
+ import { cost_blocks } from '#scripts/cost-runtime/cost-blocks'
2
+ import { time_background } from './time-background'
3
+ import { time_bundle_call } from './time-bundle-call'
4
+ import { time_markers, type PhaseMarker } from './time-markers'
5
+ import { time_shell } from './time-shell'
6
+ import { time_single_check } from './time-single-check'
7
+ import { time_transcript_line } from './time-transcript-line'
8
+ import { time_writes } from './time-writes'
9
+
10
+ // Reading a `tool_use` block into the label a span carries — split out of `time-spans.ts` when it
11
+ // reached its line limit (joshuafolkken/kit#3264). `time_spans` re-exports `UNKNOWN_TOOL` and
12
+ // `to_tool_call` under the names they always had, so the move changed no call site.
13
+
14
+ const { NO_MESSAGE_ID } = time_transcript_line
15
+
16
+ const UNKNOWN_TOOL = 'unknown'
17
+
18
+ // What a tool span is labelled with. `josh_command` is empty for everything that is not a
19
+ // `pnpm josh <cmd>` invocation, and the report drops empty labels rather than printing a bucket.
20
+ //
21
+ // `marker` names the workflow boundary the call is, for the phase breakdown that slices the same
22
+ // spans by stage (joshuafolkken/kit#1269). It is carried here rather than re-derived later because
23
+ // the tool's *input* is what decides it, and a span keeps no input — only the label read off it.
24
+ //
25
+ // `is_bundleable` and `targets` are carried for exactly that reason too (joshuafolkken/kit#1344).
26
+ // Whether a call could have gone out beside another, and what it names, are both read off the input —
27
+ // so a module asking about them after the fact would have nothing to read. `time-bundle-call.ts`
28
+ // decides both; the rule each one follows is stated there.
29
+ //
30
+ // `check_key` is the fourth, and the third time this reason has applied (joshuafolkken/kit#1383). Two
31
+ // runs of one verification check are the same call only if they named the same files, and the files
32
+ // are in the input — so `josh_command` alone cannot say, and nothing downstream could recover it.
33
+ // `time-single-check.ts` decides it.
34
+ //
35
+ // `message_id` is the fifth, and it is the one that says which *turn* a call belongs to
36
+ // (joshuafolkken/kit#1406). It is read off the line the `tool_use` block sat on rather than off the
37
+ // result that closes the span, because a `tool_result` line carries no message id at all — so a span
38
+ // that did not keep it here could never be attributed to the turn that issued it.
39
+ //
40
+ // `writes` is the sixth, and it is what `targets` could not say (joshuafolkken/kit#1472). `targets`
41
+ // names what a call mentioned; `marker` says a call was *an* edit but not of what, and for a tool
42
+ // outside `BUNDLEABLE_TOOLS` it arrived with no target at all. So a consumer subtracting edits from
43
+ // reads — `investigation-reads.ts` — had nothing to subtract for `MultiEdit` / `NotebookEdit`, and
44
+ // nothing at all to tell an in-place `sed -i` from the `sed -n` it shares a label with.
45
+ // `time-writes.ts` decides it, from the input this span is about to discard.
46
+ // `issue` is the seventh, and it is the one `branch` could not carry (joshuafolkken/kit#1617). Under
47
+ // lanes the session writing the transcript stays on the default branch while the work runs in a linked
48
+ // work tree, so `branch` names no issue on any line of the run; the `in-progress` label call does, and
49
+ // like every field above it that answer is read off the *input* a span is about to discard.
50
+ // `time_markers.NO_ISSUE` for every call that declares nothing, which is all but one call per run.
51
+ interface ToolCall {
52
+ label: string
53
+ josh_command: string
54
+ // Every josh subcommand the call ran, where `josh_command` is only the first (joshuafolkken/kit#1883).
55
+ // A chained `pnpm josh lint:related && pnpm josh test:related` carries both; the count tables expand
56
+ // it so the ones `josh_command` drops are still counted.
57
+ josh_commands: ReadonlyArray<string>
58
+ check_key: string
59
+ marker: PhaseMarker
60
+ is_bundleable: boolean
61
+ targets: ReadonlyArray<string>
62
+ // Carried beside `targets` because sharing a target means something different depending on it: a
63
+ // second write to one file is independent work, a read of a file just written is not
64
+ // (joshuafolkken/kit#1509).
65
+ is_writing: boolean
66
+ // The conservative write answer `is_read_only_call` asks of a live call, carried so a reader of the
67
+ // transcript asks the same read-only question as the guard (joshuafolkken/kit#3139).
68
+ may_write: boolean
69
+ // Whether this is a subagent launch whose prompt builds on an earlier launch's finding
70
+ // (joshuafolkken/kit#1854). Carried for the reason every field here is — the prompt it is read from
71
+ // is the input, and a span keeps none. `time-agent-bundles.ts` reads it.
72
+ has_prior_reference: boolean
73
+ writes: ReadonlyArray<string>
74
+ message_id: string
75
+ issue: number
76
+ // The background run this call reads the output of, and `''` for every other call
77
+ // (joshuafolkken/kit#1662). It is the eighth field carried for the reason the seven above are:
78
+ // the id sits in the command string, which a span does not keep, and it is the only thing that
79
+ // says a `tail` two turns later is the join of a command started minutes ago.
80
+ reads_background: string
81
+ }
82
+
83
+ const NO_CALL: ToolCall = {
84
+ label: '',
85
+ josh_command: '',
86
+ josh_commands: [],
87
+ check_key: time_single_check.NO_CHECK,
88
+ marker: time_markers.NO_MARKER,
89
+ message_id: NO_MESSAGE_ID,
90
+ writes: [],
91
+ issue: time_markers.NO_ISSUE,
92
+ reads_background: time_background.NO_BACKGROUND,
93
+ ...time_bundle_call.not_bundleable(),
94
+ }
95
+ const UNKNOWN_CALL: ToolCall = {
96
+ label: UNKNOWN_TOOL,
97
+ josh_command: '',
98
+ josh_commands: [],
99
+ check_key: time_single_check.NO_CHECK,
100
+ marker: time_markers.NO_MARKER,
101
+ message_id: NO_MESSAGE_ID,
102
+ writes: [],
103
+ issue: time_markers.NO_ISSUE,
104
+ reads_background: time_background.NO_BACKGROUND,
105
+ ...time_bundle_call.not_bundleable(),
106
+ }
107
+
108
+ // Everything but Bash: the tool's own name is the label, and nothing it runs is a shell command to
109
+ // read a check key, a josh command or a declared issue off.
110
+ function non_bash_call(name: string, input: unknown, message_id: string): ToolCall {
111
+ return {
112
+ label: name,
113
+ josh_command: '',
114
+ josh_commands: [],
115
+ check_key: time_single_check.NO_CHECK,
116
+ marker: time_markers.tool_marker(name, input),
117
+ message_id,
118
+ writes: time_writes.tool_writes(name, input),
119
+ issue: time_markers.NO_ISSUE,
120
+ // A non-Bash tool joins a backgrounded command too — `BashOutput` carries the shell id in a
121
+ // field of its own, `Monitor` names the same `…/tasks/<id>.output` path in a command — so the
122
+ // same reader is asked here rather than the field being left empty for every tool but `Bash`.
123
+ reads_background: time_background.read_id_of(input),
124
+ ...time_bundle_call.tool_facts(name, input),
125
+ }
126
+ }
127
+
128
+ function bash_call(input: unknown, message_id: string): ToolCall {
129
+ const command = time_shell.bash_command(input)
130
+ const josh_command = time_shell.josh_command_of(command)
131
+
132
+ return {
133
+ label: time_shell.bash_label(command),
134
+ josh_command,
135
+ josh_commands: time_shell.josh_commands_of(command),
136
+ check_key: time_single_check.check_key(josh_command, command),
137
+ marker: time_markers.bash_marker(command),
138
+ message_id,
139
+ writes: time_writes.bash_writes(command),
140
+ issue: time_markers.bash_issue(command),
141
+ reads_background: time_background.read_id_of(input),
142
+ ...time_bundle_call.bash_facts(command),
143
+ }
144
+ }
145
+
146
+ function to_tool_call(name: string, input: unknown, message_id: string): ToolCall {
147
+ if (name !== cost_blocks.BASH_TOOL) return non_bash_call(name, input, message_id)
148
+
149
+ return bash_call(input, message_id)
150
+ }
151
+
152
+ const time_tool_call = {
153
+ UNKNOWN_TOOL,
154
+ NO_CALL,
155
+ UNKNOWN_CALL,
156
+ to_tool_call,
157
+ }
158
+
159
+ export type { ToolCall }
160
+ export { time_tool_call }
@@ -1,13 +1,11 @@
1
1
  import { existsSync, readFileSync } from 'node:fs'
2
2
  import { createRequire } from 'node:module'
3
3
  import path from 'node:path'
4
+ import { ancestor_directories } from '#scripts/lib/ancestor-directories'
4
5
  import { json_value } from '#scripts/lib/json-value'
5
6
  import { package_named_version_schema } from '#scripts/lib/schemas'
6
7
 
7
8
  const PACKAGE_JSON = 'package.json'
8
- // Node module paths never nest anywhere near this deep; the bound only guards against an unexpected
9
- // filesystem cycle so the walk-up cannot loop forever.
10
- const MAX_WALK_DEPTH = 64
11
9
 
12
10
  // Options for `resolve_effective_upstream_version`. `resolve_marker` is a subpath specifier used for
13
11
  // `createRequire().resolve()` when the package root itself is not directly resolvable — e.g. kit does
@@ -31,26 +29,10 @@ function read_matching_version(manifest_path: string, package_name: string): str
31
29
  return parsed.data.version
32
30
  }
33
31
 
34
- // The directory itself and each of its ancestors up to the filesystem root, nearest-first. Bounded
35
- // by MAX_WALK_DEPTH so an unexpected filesystem cycle cannot produce an unbounded list.
36
- function ancestor_directories(start_directory: string): Array<string> {
37
- const directories: Array<string> = []
38
- let current = start_directory
39
-
40
- for (let depth = 0; depth < MAX_WALK_DEPTH; depth += 1) {
41
- directories.push(current)
42
- const parent = path.dirname(current)
43
- if (parent === current) break
44
- current = parent
45
- }
46
-
47
- return directories
48
- }
49
-
50
32
  // Walk up from a resolved file's directory to the package root, returning the version of the first
51
33
  // ancestor package.json whose `name` matches the target — or undefined when no matching root exists.
52
34
  function walk_up_for_version(start_directory: string, package_name: string): string | undefined {
53
- for (const directory of ancestor_directories(start_directory)) {
35
+ for (const directory of ancestor_directories.list(start_directory)) {
54
36
  const version = read_matching_version(path.join(directory, PACKAGE_JSON), package_name)
55
37
  if (version !== undefined) return version
56
38
  }
@@ -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
@@ -30,14 +30,19 @@
30
30
  * manifest that arrived with the two fields out of step kept the pnpm dual-declaration
31
31
  * warning forever. The alignment is idempotent, so running it unconditionally is free.
32
32
  *
33
- * Registry and pnpm failures stay non-fatal: they are logged and swallowed (exit 0)
34
- * so the rest of the `josh latest` chain (`latest:update`, `audit`) keeps running.
33
+ * A failed bump exits non-zero with its cause, the manifest restored (#3361). It used to be
34
+ * swallowed as a skip, so `latest:scope --record` marked the run fresh and a pnpm that could not
35
+ * self-update (the Corepack shim refuses it) stayed pinned silently. `josh latest` runs this step
36
+ * last, right before the record, so the dependency update and the audit still finish and only the
37
+ * record is withheld. A registry answer not newer than the pin, or nothing aged past the
38
+ * quarantine window yet, remains a non-fatal skip.
35
39
  *
36
40
  * Usage: tsx scripts/version/latest-corepack.ts
37
41
  */
38
42
  import { readFileSync, writeFileSync } from 'node:fs'
39
43
  import { fileURLToPath } from 'node:url'
40
44
  import { json_value } from '#scripts/lib/json-value'
45
+ import { COMMAND_TIMEOUT_MS } from '#scripts/lib/timeouts'
41
46
  import { execaSync } from 'execa'
42
47
  import semver from 'semver'
43
48
  import { z } from 'zod'
@@ -49,14 +54,23 @@ const PACKAGE_MANAGER_RE = /"packageManager"\s*:\s*"pnpm@(\d+)(?:[^\d]|$)/u
49
54
  const PINNED_VERSION_RE = /"packageManager"\s*:\s*"pnpm@([^"+]+)/u
50
55
  const TARGET_PREFIX = 'pnpm@'
51
56
  const FAILURE_EXIT_CODE = 1
52
- const NPMRC_PATH = '.npmrc'
53
- const VIEW_TIMEOUT_MS = 30_000
57
+ const WORKSPACE_PATH = 'pnpm-workspace.yaml'
54
58
  const SHA512_BYTES = 64
55
59
  const INTEGRITY_RE = /^sha512-([A-Za-z0-9+/]+={0,2})$/u
56
60
  const PACKAGE_MANAGER_VALUE_RE = /("packageManager"\s*:\s*")pnpm@[^"]+(")/u
61
+
62
+ // `target` is undefined both when the registry did not answer and when it answered but nothing has
63
+ // aged past the quarantine window yet; only the first is a failure (#3361).
64
+ interface TargetResolution {
65
+ target: string | undefined
66
+ is_registry_reachable: boolean
67
+ }
68
+
57
69
  const PNPM_DEVELOPMENT_MANAGER_SCHEMA = z.object({ name: z.literal('pnpm'), version: z.string() })
58
70
  const DEV_ENGINES_SCHEMA = z.object({ packageManager: PNPM_DEVELOPMENT_MANAGER_SCHEMA })
59
71
  const DEV_ENGINES_PNPM_SCHEMA = z.object({ devEngines: DEV_ENGINES_SCHEMA.optional() })
72
+ const COREPACK_REMEDY =
73
+ 'pnpm runs through the Corepack shim, where pnpm 11+ refuses self-update (ERR_PNPM_CANT_SELF_UPDATE_IN_COREPACK). Run `corepack disable pnpm`, install a standalone pnpm (https://pnpm.io/installation), then rerun josh latest.'
60
74
 
61
75
  function extract_development_engines_version(package_json_content: string): string | undefined {
62
76
  const parsed = DEV_ENGINES_PNPM_SCHEMA.safeParse(
@@ -121,29 +135,40 @@ function extract_times_json(stdout: string): Record<string, string> | undefined
121
135
  function query_release_times(): Record<string, string> | undefined {
122
136
  const result = execaSync('pnpm', ['view', 'pnpm', 'time', '--json'], {
123
137
  reject: false,
124
- timeout: VIEW_TIMEOUT_MS,
138
+ timeout: COMMAND_TIMEOUT_MS,
125
139
  })
126
140
  if ((result.exitCode ?? FAILURE_EXIT_CODE) !== 0) return undefined
127
141
 
128
142
  return extract_times_json(result.stdout)
129
143
  }
130
144
 
145
+ function to_resolution(
146
+ version: string | undefined,
147
+ is_registry_reachable: boolean,
148
+ ): TargetResolution {
149
+ const target = version === undefined ? undefined : `${TARGET_PREFIX}${version}`
150
+
151
+ return { target, is_registry_reachable }
152
+ }
153
+
131
154
  // Ask the registry for the newest pnpm release on the pinned major that has aged past the
132
- // quarantine window. Returns undefined when the query fails or nothing qualifies yet.
133
- function query_major_latest_version(major: string): string | undefined {
155
+ // quarantine window.
156
+ function resolve_major_target(major: string): TargetResolution {
134
157
  const times = query_release_times()
135
- if (times === undefined) return undefined
158
+ if (times === undefined) return to_resolution(undefined, false)
136
159
 
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).
141
- return release_age.select_aged_version(
160
+ // The project's own `pnpm-workspace.yaml`, deliberately not the upward walk the version check
161
+ // uses: `josh latest` reads `package.json` and writes the pnpm pin relative to the working
162
+ // directory, so it has no subdirectory case — and honouring an ancestor's policy here could freeze
163
+ // pnpm bumps in a project that declares none (joshuafolkken/kit#808).
164
+ const version = release_age.select_aged_version(
142
165
  times,
143
166
  major,
144
- release_age.read_minimum_release_age(NPMRC_PATH),
167
+ release_age.read_minimum_release_age(WORKSPACE_PATH),
145
168
  Date.now(),
146
169
  )
170
+
171
+ return to_resolution(version, true)
147
172
  }
148
173
 
149
174
  function parse_latest_version(stdout: string): string | undefined {
@@ -156,31 +181,47 @@ function parse_latest_version(stdout: string): string | undefined {
156
181
  function query_latest_version(): string | undefined {
157
182
  const result = execaSync('pnpm', ['view', 'pnpm', 'version', '--json'], {
158
183
  reject: false,
159
- timeout: VIEW_TIMEOUT_MS,
184
+ timeout: COMMAND_TIMEOUT_MS,
160
185
  })
161
186
  if ((result.exitCode ?? FAILURE_EXIT_CODE) !== 0) return undefined
162
187
 
163
188
  return parse_latest_version(result.stdout)
164
189
  }
165
190
 
166
- // The value handed to pnpm self-update: an exact registry-resolved version on the pinned
167
- // major, the latest exact version when no major can be read from package.json, or undefined when
168
- // the registry could not answer (the caller skips non-fatally).
169
- function resolve_corepack_target(major: string | undefined): string | undefined {
170
- const version = major === undefined ? query_latest_version() : query_major_latest_version(major)
171
- if (version === undefined) return undefined
191
+ // The target handed to pnpm self-update: an exact registry-resolved version on the pinned major, or
192
+ // the latest exact version when no major can be read from package.json. The latest-version query
193
+ // has no quarantine selection, so resolving nothing there always means the registry did not answer.
194
+ function resolve_target(major: string | undefined): TargetResolution {
195
+ if (major !== undefined) return resolve_major_target(major)
196
+ const version = query_latest_version()
197
+
198
+ return to_resolution(version, version !== undefined)
199
+ }
172
200
 
173
- return `pnpm@${version}`
201
+ function resolve_corepack_target(major: string | undefined): string | undefined {
202
+ return resolve_target(major).target
174
203
  }
175
204
 
176
- // Non-fatal skip message shared by both skip paths: keep the josh latest chain
177
- // (latest:update, audit) running instead of aborting on a bump failure.
205
+ // Non-fatal skip message for a registry answer with nothing to adopt — nothing to bump, nothing failed.
178
206
  function warn_skip(reason: string): void {
179
- console.warn(`⚠ Skipped pnpm bump (${reason}); the chain continues.`)
207
+ console.warn(`⚠ Skipped pnpm bump (${reason}).`)
208
+ }
209
+
210
+ // Every failure path ends here, so the exit status the `josh latest` chain stops on is one value.
211
+ function fail_bump(reason: string): number {
212
+ console.error(
213
+ `✖ pnpm bump failed (${reason}); the run is not recorded, so the next josh latest retries it.`,
214
+ )
215
+
216
+ return FAILURE_EXIT_CODE
180
217
  }
181
218
 
182
- function warn_unresolved(major: string | undefined): void {
183
- warn_skip(`no pnpm ${major ?? ''} release resolvable from the registry`)
219
+ // Corepack exports COREPACK_ROOT to the package manager it launches, and pnpm 11+ refuses
220
+ // `self-update` under it (ERR_PNPM_CANT_SELF_UPDATE_IN_COREPACK), so the remedy is named there.
221
+ function fail_self_update(status: number, environment: NodeJS.ProcessEnv = process.env): number {
222
+ if (environment['COREPACK_ROOT'] !== undefined) console.error(COREPACK_REMEDY)
223
+
224
+ return fail_bump(`pnpm self-update exited ${String(status)}`)
184
225
  }
185
226
 
186
227
  function run_pnpm_update(target: string): number {
@@ -193,17 +234,6 @@ function run_pnpm_update(target: string): number {
193
234
  return result.exitCode ?? FAILURE_EXIT_CODE
194
235
  }
195
236
 
196
- // The target is an already-resolved exact version, so a non-zero status here is a
197
- // genuine pnpm or network failure; a later run retries. Returns whether a skip
198
- // happened.
199
- function did_warn_skip(status: number): boolean {
200
- if (status === 0) return false
201
-
202
- warn_skip(`pnpm self-update exited ${String(status)}`)
203
-
204
- return true
205
- }
206
-
207
237
  // Realign `devEngines.packageManager.version` with the `packageManager` pin so the two
208
238
  // fields keep matching (pnpm suppresses the dual-declaration warning only on an exact
209
239
  // match). Runs on every path, not just after a successful bump: a repository that arrives
@@ -224,7 +254,7 @@ function restore_package_json(
224
254
  package_json_path: string = PACKAGE_JSON_PATH,
225
255
  ): void {
226
256
  writeFileSync(package_json_path, content)
227
- console.info('✔ Restored package.json pin (pnpm bump skipped)')
257
+ console.info('✔ Restored package.json pin (pnpm bump failed)')
228
258
  }
229
259
 
230
260
  function decode_integrity(encoded: string): string | undefined {
@@ -247,7 +277,7 @@ function extract_encoded_integrity(stdout: string): string | undefined {
247
277
  function query_integrity(target: string): string | undefined {
248
278
  const result = execaSync('pnpm', ['view', target, 'dist.integrity', '--json'], {
249
279
  reject: false,
250
- timeout: VIEW_TIMEOUT_MS,
280
+ timeout: COMMAND_TIMEOUT_MS,
251
281
  })
252
282
  if ((result.exitCode ?? FAILURE_EXIT_CODE) !== 0) return undefined
253
283
 
@@ -292,35 +322,19 @@ function notify_skipped_bump(target: string, pinned_version: string): void {
292
322
  warn_skip(`registry answered ${target}, below the pinned ${pinned_version}`)
293
323
  }
294
324
 
295
- // Resolve the pnpm target with the pin floor applied. Undefined means the bump was
296
- // skipped and the reason has already been logged.
297
- function resolve_floored_target(original: string, major: string | undefined): string | undefined {
298
- const target = resolve_corepack_target(major)
299
-
300
- if (target === undefined) {
301
- warn_unresolved(major)
302
-
303
- return undefined
304
- }
305
-
306
- const pinned_version = extract_pinned_version(original)
307
- if (!is_target_not_newer_than_pin(target, pinned_version)) return target
308
-
309
- notify_skipped_bump(target, pinned_version ?? '')
310
-
311
- return undefined
312
- }
313
-
314
- function restore_after_update(original: string, target: string, integrity: string): void {
325
+ function restore_after_update(original: string, target: string, integrity: string): number {
315
326
  try {
316
327
  restore_integrity(target, integrity)
328
+
329
+ return 0
317
330
  } catch {
318
331
  restore_package_json(original)
319
- warn_skip('pnpm self-update wrote an unexpected pin')
332
+
333
+ return fail_bump('pnpm self-update wrote an unexpected pin')
320
334
  }
321
335
  }
322
336
 
323
- function pin_unpinned_manifest(original: string, target: string, integrity: string): void {
337
+ function pin_unpinned_manifest(original: string, target: string, integrity: string): number {
324
338
  const manifest = z.record(z.string(), z.unknown()).parse(json_value.parse_or_undefined(original))
325
339
  const pinned = JSON.stringify(
326
340
  { ...manifest, packageManager: `${target}+${integrity}` },
@@ -334,53 +348,71 @@ function pin_unpinned_manifest(original: string, target: string, integrity: stri
334
348
  )
335
349
 
336
350
  const result = execaSync('pnpm', ['--version'], { reject: false })
337
- if (result.exitCode === 0 && result.stdout.trim() === target.slice(TARGET_PREFIX.length)) return
351
+ if (result.exitCode === 0 && result.stdout.trim() === target.slice(TARGET_PREFIX.length)) return 0
338
352
 
339
353
  restore_package_json(original)
340
- warn_skip('the newly pinned pnpm version could not start')
341
- }
342
354
 
343
- function update_pinned_manifest(original: string, target: string, integrity: string): void {
344
- const is_skipped = did_warn_skip(run_pnpm_update(target))
355
+ return fail_bump('the newly pinned pnpm version could not start')
356
+ }
345
357
 
346
- if (is_skipped) {
347
- restore_package_json(original)
358
+ // The target is an already-resolved exact version, so a non-zero status here is a genuine
359
+ // pnpm, Corepack or network failure.
360
+ function update_pinned_manifest(original: string, target: string, integrity: string): number {
361
+ const status = run_pnpm_update(target)
362
+ if (status === 0) return restore_after_update(original, target, integrity)
348
363
 
349
- return
350
- }
364
+ restore_package_json(original)
351
365
 
352
- restore_after_update(original, target, integrity)
366
+ return fail_self_update(status)
353
367
  }
354
368
 
355
369
  // Query integrity before mutating the manifest; self-update writes both pins without it.
356
- function bump_package_manager(original: string, target: string): void {
370
+ function bump_package_manager(original: string, target: string): number {
357
371
  const integrity = query_integrity(target)
372
+ if (integrity === undefined) return fail_bump(`no integrity for ${target}`)
373
+ if (!PACKAGE_MANAGER_RE.test(original)) return pin_unpinned_manifest(original, target, integrity)
358
374
 
359
- if (integrity === undefined) {
360
- warn_skip(`no integrity for ${target}`)
375
+ return update_pinned_manifest(original, target, integrity)
376
+ }
361
377
 
362
- return
363
- }
378
+ // An unreachable registry fails, so the unrecorded run is retried. A reachable one with nothing
379
+ // aged past the quarantine window is the kit#768 skip — like a not-newer answer, there is nothing
380
+ // to adopt yet, and failing would stall every run for up to the whole window.
381
+ function report_unresolved(major: string | undefined, is_registry_reachable: boolean): number {
382
+ const label = `pnpm ${major ?? ''}`.trimEnd()
383
+ if (!is_registry_reachable) return fail_bump(`the registry did not answer for ${label}`)
364
384
 
365
- if (!PACKAGE_MANAGER_RE.test(original)) {
366
- pin_unpinned_manifest(original, target, integrity)
385
+ warn_skip(`no ${label} release has aged past the quarantine window yet`)
367
386
 
368
- return
387
+ return 0
388
+ }
389
+
390
+ // Resolve the pnpm target with the pin floor applied, then bump.
391
+ function bump_to_registry_target(original: string, major: string | undefined): number {
392
+ const { target, is_registry_reachable } = resolve_target(major)
393
+ if (target === undefined) return report_unresolved(major, is_registry_reachable)
394
+
395
+ const pinned_version = extract_pinned_version(original)
396
+
397
+ if (!is_target_not_newer_than_pin(target, pinned_version)) {
398
+ return bump_package_manager(original, target)
369
399
  }
370
400
 
371
- update_pinned_manifest(original, target, integrity)
401
+ notify_skipped_bump(target, pinned_version ?? '')
402
+
403
+ return 0
372
404
  }
373
405
 
374
- function main(): void {
406
+ function main(): number {
375
407
  const original = readFileSync(PACKAGE_JSON_PATH, 'utf8')
376
- const major = extract_pnpm_major(original)
377
- const target = resolve_floored_target(original, major)
378
- if (target !== undefined) bump_package_manager(original, target)
408
+ const status = bump_to_registry_target(original, extract_pnpm_major(original))
379
409
 
380
410
  sync_development_engines()
411
+
412
+ return status
381
413
  }
382
414
 
383
- if (process.argv[1] === fileURLToPath(import.meta.url)) main()
415
+ if (process.argv[1] === fileURLToPath(import.meta.url)) process.exitCode = main()
384
416
 
385
417
  const latest_corepack = {
386
418
  extract_pnpm_major,
@@ -388,12 +420,11 @@ const latest_corepack = {
388
420
  is_target_not_newer_than_pin,
389
421
  notify_skipped_bump,
390
422
  extract_times_json,
391
- query_major_latest_version,
392
423
  resolve_corepack_target,
393
424
  run_pnpm_update,
394
425
  query_integrity,
395
426
  restore_integrity,
396
- did_warn_skip,
427
+ fail_self_update,
397
428
  sync_development_engines,
398
429
  restore_package_json,
399
430
  main,
@@ -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 }