@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
@@ -25,6 +25,8 @@ interface GateStepResult extends BufferedProcessResult {
25
25
  // What was actually run. The type check's command is resolved per project, so a failure on the
26
26
  // `check` step is only reproducible if the header names the command rather than the label.
27
27
  command: string
28
+ // Set when the check started past the machine-wide budget at the wait cap (joshuafolkken/kit#3371).
29
+ budget_note?: string | undefined
28
30
  }
29
31
 
30
32
  function is_gate_step_failed(result: GateStepResult): boolean {
@@ -141,25 +143,42 @@ function format_failure_actions(failed: ReadonlyArray<GateStepResult>): string {
141
143
  return `${GATE_COMMAND} — next:\n${lines.join('\n')}\n`
142
144
  }
143
145
 
146
+ // **A check that started past the core budget is said in the summary, not only in the log**
147
+ // (joshuafolkken/kit#3371). The wait cap admits a check that waited two minutes whatever the budget says,
148
+ // so a gate that ran over-subscribed would otherwise look exactly like one that fitted. The icon is
149
+ // local rather than in `status-icons.ts` because nothing reads this line back.
150
+ const WARN_ICON = '⚠'
151
+
152
+ function format_budget_notes(results: ReadonlyArray<GateStepResult>): string {
153
+ const lines = results.flatMap((result) =>
154
+ result.budget_note === undefined
155
+ ? []
156
+ : ` ${WARN_ICON} ${result.label} — ${result.budget_note}`,
157
+ )
158
+
159
+ return lines.length === 0 ? '' : `${lines.join('\n')}\n`
160
+ }
161
+
144
162
  // The total is wall-clock for the whole command, not the sum of the four — they run concurrently,
145
163
  // so a sum would report about three times what the caller waited.
146
164
  //
147
165
  // The failure actions and the log path are printed with the summary and immediately **above** the
148
166
  // verdict line, which stays last for the reason `josh-verdict.ts` gives (joshuafolkken/kit#1227).
149
167
  function print_gate_summary(
150
- failed: ReadonlyArray<GateStepResult>,
151
- elapsed_ms: number,
152
- step_count: string,
168
+ results: ReadonlyArray<GateStepResult>,
169
+ report: GateReport,
153
170
  log_path?: string,
154
171
  ): void {
155
- const total = format_seconds(elapsed_ms)
172
+ const failed = results.filter((result) => is_gate_step_failed(result))
173
+ const total = format_seconds(report.elapsed_ms)
156
174
  const verdict =
157
175
  failed.length === 0
158
- ? josh_verdict.format_gate_passed(step_count, total)
176
+ ? josh_verdict.format_gate_passed(report.step_count, total)
159
177
  : josh_verdict.format_gate_failed(failed.map((result) => result.label).join(', '), total)
178
+ const notes = format_budget_notes(results)
160
179
 
161
180
  process.stdout.write(
162
- `\n${format_failure_actions(failed)}${gate_log.format_log_notice(log_path)}${verdict}\n`,
181
+ `\n${notes}${format_failure_actions(failed)}${gate_log.format_log_notice(log_path)}${verdict}\n`,
163
182
  )
164
183
  }
165
184
 
@@ -183,15 +202,15 @@ function report_gate_steps(
183
202
  header: gate_step_header(result),
184
203
  output: result.output,
185
204
  }))
186
- const failed = results.filter((result) => is_gate_step_failed(result))
187
205
  const written_log = gate_log.write_gate_log(entries, report.log_path)
188
206
 
189
- print_gate_summary(failed, report.elapsed_ms, report.step_count, written_log)
207
+ print_gate_summary(results, report, written_log)
190
208
 
191
- return failed.map((result) => result.label)
209
+ return results.filter((result) => is_gate_step_failed(result)).map((result) => result.label)
192
210
  }
193
211
 
194
212
  const gate_report = {
213
+ format_budget_notes,
195
214
  format_failure_actions,
196
215
  has_checker_warning,
197
216
  is_gate_step_failed,
@@ -60,18 +60,42 @@ const FORCE_FLAG = '--force'
60
60
  //
61
61
  // A `base` that could not be read is `undefined`, and an older record carries none: both compare
62
62
  // unequal to anything, so the gate runs its checks. No base means no reuse, never reuse without one.
63
- function reusable_green_gate(
63
+ //
64
+ // **A refusal carries its reason** (joshuafolkken/kit#3307): a pre-push hook that fell back to the
65
+ // whole unit suite said nothing about why, so a merge base moved by ship's main sync and a lock file
66
+ // rewritten by the hook's own install each cost minutes before anyone could tell them apart.
67
+ type GateReuse = { stamp: FileMapStamp } | { miss: string }
68
+
69
+ function base_moved(recorded: string | undefined, base: string): string {
70
+ return `the merge base moved since the green gate (${recorded ?? 'none'} → ${base})`
71
+ }
72
+
73
+ function gate_reuse(
64
74
  tree: Record<string, string>,
65
75
  base: string | undefined,
66
76
  source?: string,
67
- ): FileMapStamp | undefined {
68
- if (base === undefined || Object.keys(tree).length === 0) return undefined
77
+ ): GateReuse {
78
+ if (base === undefined) return { miss: 'the merge base could not be read' }
79
+ if (Object.keys(tree).length === 0) return { miss: 'no file differs from the merge base' }
69
80
 
70
81
  const stamp = review_brief.matching_stamp(review_stamps.gate_stamp.read(source), tree)
71
82
 
72
- if (stamp?.base !== base) return undefined
83
+ if (stamp === undefined) return { miss: 'no green gate record matches the changed files' }
84
+ if (stamp.base !== base) return { miss: base_moved(stamp.base, base) }
85
+
86
+ return { stamp }
87
+ }
88
+
89
+ function stamp_of(reuse: GateReuse): FileMapStamp | undefined {
90
+ return 'stamp' in reuse ? reuse.stamp : undefined
91
+ }
73
92
 
74
- return stamp
93
+ function reusable_green_gate(
94
+ tree: Record<string, string>,
95
+ base: string | undefined,
96
+ source?: string,
97
+ ): FileMapStamp | undefined {
98
+ return stamp_of(gate_reuse(tree, base, source))
75
99
  }
76
100
 
77
101
  // **The sentence claims the result, and never merely the omission.** "verification skipped" on its
@@ -116,8 +140,15 @@ function format_skip(taken_at: string): string {
116
140
  })
117
141
  }
118
142
 
119
- const gate_skip = { FORCE_FLAG, format_reuse_notice, format_skip, reusable_green_gate }
143
+ const gate_skip = {
144
+ FORCE_FLAG,
145
+ format_reuse_notice,
146
+ format_skip,
147
+ gate_reuse,
148
+ reusable_green_gate,
149
+ stamp_of,
150
+ }
120
151
 
121
- export type { ReuseNotice }
152
+ export type { GateReuse, ReuseNotice }
122
153
 
123
154
  export { gate_skip }
@@ -1,7 +1,6 @@
1
1
  import { git_command } from '#scripts/git/git-command'
2
- import type { FileMapStamp } from '#scripts/josh/file-map-stamp'
3
2
  import { observation_ledger } from '#scripts/observations/observation-ledger'
4
- import { gate_skip } from './gate-skip'
3
+ import { gate_skip, type GateReuse } from './gate-skip'
5
4
  import type { GateTree } from './gate-tree'
6
5
 
7
6
  // What the two git hooks share when they decline to re-run a check `pnpm josh gate` already passed on
@@ -94,6 +93,8 @@ interface HookReuse {
94
93
  // The hook-specific narrowing: whether what this git operation carries is the tree the record
95
94
  // describes. `is_worktree_clean` for a push, `is_index_matching_worktree` for a commit.
96
95
  is_tree_carried: boolean
96
+ // The reason a refusal on that narrowing reports, in the hook's own words (joshuafolkken/kit#3307).
97
+ carry_miss: string
97
98
  extra_arguments: ReadonlyArray<string>
98
99
  force_env: string
99
100
  // The record to read, so a test can plant one without overwriting the record a live run relies on.
@@ -102,27 +103,28 @@ interface HookReuse {
102
103
  source?: string | undefined
103
104
  }
104
105
 
105
- // The stamp rather than a boolean, for the same reason `gate_skip.reusable_green_gate` hands one back:
106
- // the caller prints `taken_at`, and a record that does not describe this tree has no timestamp worth
107
- // printing.
106
+ // The stamp rather than a boolean, for the same reason `gate_skip.gate_reuse` hands one back: the
107
+ // caller prints `taken_at`, and a record that does not describe this tree has no timestamp worth
108
+ // printing. A refusal carries its reason instead (joshuafolkken/kit#3307).
108
109
  //
109
110
  // **Any argument at all refuses the reuse**, not only the force flag. A caller who narrowed the run
110
111
  // asked for that run rather than for a recorded result about a whole tree, and a hook's own line passes
111
112
  // none, so this costs it nothing.
112
- function reusable_green_hook(input: HookReuse): FileMapStamp | undefined {
113
- if (input.extra_arguments.length > 0 || is_force_requested(input.force_env)) return undefined
114
- if (!input.is_tree_carried) return undefined
113
+ function hook_reuse(input: HookReuse): GateReuse {
114
+ if (input.extra_arguments.length > 0) return { miss: 'arguments were passed to the hook' }
115
+ if (is_force_requested(input.force_env)) return { miss: `${input.force_env} is set` }
116
+ if (!input.is_tree_carried) return { miss: input.carry_miss }
115
117
 
116
- return gate_skip.reusable_green_gate(input.tree.files, input.tree.base, input.source)
118
+ return gate_skip.gate_reuse(input.tree.files, input.tree.base, input.source)
117
119
  }
118
120
 
119
121
  const hook_gate_reuse = {
120
122
  GATE_REUSING_TARGETS,
123
+ hook_reuse,
121
124
  is_force_requested,
122
125
  is_index_matching_worktree,
123
126
  is_worktree_clean,
124
127
  read_status_lines,
125
- reusable_green_hook,
126
128
  }
127
129
 
128
130
  export type { HookReuse }
@@ -0,0 +1,175 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import { availableParallelism, cpus, totalmem } from 'node:os'
3
+ import { execa } from 'execa'
4
+
5
+ // What the machine has free right now, read for the core budget's admission (joshuafolkken/kit#3371).
6
+ //
7
+ // **The budget used to be the core count, and that count is not what is free.** The ledger in
8
+ // `core-budget.ts` admits against `availableParallelism()`, so the load it never sees — Claude sessions,
9
+ // node processes that do not pass through `josh`, the window server — ran on top of a budget already
10
+ // spent. On 2026-10-07, three lanes on the 11-core / 18 GB machine reached a load average of 16.8 with
11
+ // 31% of the CPU in the kernel, compressing and swapping 4.4 GB of memory the ledger never counted.
12
+ //
13
+ // **This is a closed loop, not the open-loop formula joshuafolkken/kit#1637 rejected.** That proposal
14
+ // derived a share from the core count up front; this reads what is busy and free at the moment of
15
+ // admission and subtracts only the part the ledger does not already account for.
16
+ //
17
+ // **The busy cores are the CPU-time difference over a short window, not the load average.** The load
18
+ // average trails by a minute, so a gate started just after another one finished would see the finished
19
+ // one's load as external and wait out the decay. `os.cpus()` is one read with no process spawned.
20
+ //
21
+ // **A reading that fails answers undefined, and undefined is the old behavior.** A CI runner or a
22
+ // container — where the host's CPU times are not this process's quota — budgets by the core count and
23
+ // admits without looking at memory, exactly as before.
24
+
25
+ // The window the busy cores are measured over: long enough that one scheduler tick does not dominate it,
26
+ // short enough that a solo admission is not visibly delayed.
27
+ const SAMPLE_WINDOW_MS = 250
28
+ // **The background a developer machine always carries, which the gate's weights were measured beside.**
29
+ // On 2026-10-07 the 11-core machine idled at about 1.5 cores outside `josh` — Claude sessions 0.55, the
30
+ // window server 0.47, a browser 0.5. The solo gate's weights sum to exactly the core count and were
31
+ // measured with that load present, so only what exceeds it is load the budget must give way to;
32
+ // counting the baseline would defer a solo gate's unit suite on every run.
33
+ const BASELINE_CORES = 2
34
+ const BYTES_PER_MB = 1_048_576
35
+ const KB_PER_MB = 1024
36
+ const PERCENT = 100
37
+ const MEMINFO_PATH = '/proc/meminfo'
38
+ const LINUX_AVAILABLE = /^MemAvailable:\s+(?<kb>\d+) kB/mu
39
+ // The kernel's own memory-pressure figure: the percentage of memory still available, compressible and
40
+ // purgeable pages included — what `os.freemem()` leaves out on macOS, where it reads near zero on a
41
+ // machine with gigabytes to spare.
42
+ const PRESSURE_ARGUMENTS = ['-n', 'kern.memorystatus_level']
43
+ const PRESSURE_LEVEL = /^\s*(?<level>\d+)\s*$/u
44
+
45
+ interface MachineReading {
46
+ busy_cores: number | undefined
47
+ available_mb: number | undefined
48
+ }
49
+
50
+ // What the ledger's admitted reservations declare — the part of the machine's load already accounted for.
51
+ interface LedgerLoad {
52
+ cores: number
53
+ memory_mb: number
54
+ }
55
+
56
+ interface MachineBudget {
57
+ cores: number
58
+ memory_mb: number
59
+ }
60
+
61
+ interface CpuTimes {
62
+ busy: number
63
+ total: number
64
+ }
65
+
66
+ function cpu_times(): CpuTimes {
67
+ let busy = 0
68
+ let idle = 0
69
+
70
+ for (const { times } of cpus()) {
71
+ busy += times.user + times.nice + times.sys + times.irq
72
+ idle += times.idle
73
+ }
74
+
75
+ return { busy, total: busy + idle }
76
+ }
77
+
78
+ function busy_cores_between(before: CpuTimes, after: CpuTimes, cores: number): number | undefined {
79
+ const total = after.total - before.total
80
+
81
+ if (total <= 0) return undefined
82
+
83
+ return ((after.busy - before.busy) / total) * cores
84
+ }
85
+
86
+ // `kern.memorystatus_level` → `58`: the share of physical memory still available, as a percentage.
87
+ function parse_darwin_pressure(output: string, total_bytes: number): number | undefined {
88
+ const level = PRESSURE_LEVEL.exec(output)?.groups?.['level']
89
+
90
+ if (level === undefined) return undefined
91
+
92
+ return (Number(level) / PERCENT) * (total_bytes / BYTES_PER_MB)
93
+ }
94
+
95
+ function parse_linux_available(meminfo: string): number | undefined {
96
+ const kb = LINUX_AVAILABLE.exec(meminfo)?.groups?.['kb']
97
+
98
+ return kb === undefined ? undefined : Number(kb) / KB_PER_MB
99
+ }
100
+
101
+ async function read_available_mb(
102
+ platform: NodeJS.Platform = process.platform,
103
+ ): Promise<number | undefined> {
104
+ try {
105
+ if (platform === 'darwin') {
106
+ const { stdout } = await execa('sysctl', PRESSURE_ARGUMENTS)
107
+
108
+ return parse_darwin_pressure(stdout, totalmem())
109
+ }
110
+
111
+ return platform === 'linux'
112
+ ? parse_linux_available(await readFile(MEMINFO_PATH, 'utf8'))
113
+ : undefined
114
+ } catch {
115
+ return undefined
116
+ }
117
+ }
118
+
119
+ // A CPU quota narrower than the host makes the host-wide CPU times the wrong machine, so the busy cores
120
+ // are read only where the quota and the host agree.
121
+ function is_unquoted(): boolean {
122
+ return cpus().length > 0 && cpus().length === availableParallelism()
123
+ }
124
+
125
+ async function default_sleep(ms: number): Promise<void> {
126
+ await new Promise<void>((resolve) => {
127
+ setTimeout(resolve, ms)
128
+ })
129
+ }
130
+
131
+ async function read_machine(
132
+ sleep: (ms: number) => Promise<void> = default_sleep,
133
+ ): Promise<MachineReading> {
134
+ const before = cpu_times()
135
+ const [available_mb] = await Promise.all([read_available_mb(), sleep(SAMPLE_WINDOW_MS)])
136
+ const busy_cores = is_unquoted()
137
+ ? busy_cores_between(before, cpu_times(), cpus().length)
138
+ : undefined
139
+
140
+ return { busy_cores, available_mb }
141
+ }
142
+
143
+ // The busy cores the ledger does not account for, beyond the baseline the weights were measured beside,
144
+ // in whole cores — the budget is in whole cores, and a fraction of a core is not a core to give up.
145
+ function external_cores(busy_cores: number | undefined, ledger_cores: number): number {
146
+ if (busy_cores === undefined) return 0
147
+
148
+ return Math.floor(Math.max(0, busy_cores - ledger_cores - BASELINE_CORES))
149
+ }
150
+
151
+ // **The budget the ledger admits against: what is free plus what the ledger itself already holds.**
152
+ // The admitted reservations' load is inside the reading, and the admission walk counts their declared
153
+ // weight again, so it is added back — what is subtracted is only the load from outside the ledger. The
154
+ // caller passes every admitted claim's cores but only the memory of claims past
155
+ // `core_admission.RAMP_UP_MS`, whose tools have had time to allocate.
156
+ // Memory that could not be read is no limit at all, the behavior before memory was read.
157
+ function machine_budget(cores: number, reading: MachineReading, ledger: LedgerLoad): MachineBudget {
158
+ return {
159
+ cores: cores - external_cores(reading.busy_cores, ledger.cores),
160
+ memory_mb:
161
+ reading.available_mb === undefined ? Infinity : reading.available_mb + ledger.memory_mb,
162
+ }
163
+ }
164
+
165
+ const machine_capacity = {
166
+ BASELINE_CORES,
167
+ busy_cores_between,
168
+ machine_budget,
169
+ parse_darwin_pressure,
170
+ parse_linux_available,
171
+ read_machine,
172
+ }
173
+
174
+ export type { LedgerLoad, MachineBudget, MachineReading }
175
+ export { machine_capacity }
@@ -96,14 +96,17 @@ async function reusable_green_commit(
96
96
  source?: string,
97
97
  start_directory: string = process.cwd(),
98
98
  ): Promise<FileMapStamp | undefined> {
99
- const reusable = hook_gate_reuse.reusable_green_hook({
99
+ const reuse = hook_gate_reuse.hook_reuse({
100
100
  tree,
101
101
  is_tree_carried: tree.is_index_matching_worktree,
102
+ carry_miss: 'the index differs from the working tree',
102
103
  extra_arguments,
103
104
  force_env: FORCE_ENV,
104
105
  source,
105
106
  })
106
107
 
108
+ const reusable = gate_skip.stamp_of(reuse)
109
+
107
110
  if (reusable === undefined) return undefined
108
111
 
109
112
  return (await is_recorded_check_this_check(start_directory)) ? reusable : undefined
@@ -2,6 +2,7 @@ import { existsSync, globSync, readFileSync } from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { find_local_bin_upwards } from '#scripts/build/local-bin'
4
4
  import { project_profile } from '#scripts/init/project-profile'
5
+ import { ancestor_directories } from '#scripts/lib/ancestor-directories'
5
6
  import { json_value } from '#scripts/lib/json-value'
6
7
  import { SKIP_MARKER } from '#scripts/test/skip-marker'
7
8
  import { z } from 'zod'
@@ -43,15 +44,11 @@ const DEPENDENCY_FIELDS = ['dependencies', 'devDependencies']
43
44
  const object_schema = z.record(z.string(), z.unknown())
44
45
 
45
46
  function project_root(directory: string): string {
46
- let current = directory
47
-
48
- while (!existsSync(path.join(current, PACKAGE_JSON))) {
49
- const parent = path.dirname(current)
50
- if (parent === current) return directory
51
- current = parent
52
- }
53
-
54
- return current
47
+ return (
48
+ ancestor_directories.nearest(directory, (candidate) =>
49
+ existsSync(path.join(candidate, PACKAGE_JSON)),
50
+ ) ?? directory
51
+ )
55
52
  }
56
53
 
57
54
  function is_ignored(entry: string): boolean {
@@ -1,4 +1,5 @@
1
1
  import { find_local_bin_upwards } from '#scripts/build/local-bin'
2
+ import { COMMAND_TIMEOUT_MS } from '#scripts/lib/timeouts'
2
3
  import { execa } from 'execa'
3
4
 
4
5
  // joshuafolkken/kit#934: `josh gate`'s type-check step used to be `tsc --noEmit` for every project.
@@ -28,8 +29,6 @@ const TOOLKIT_BINS: ReadonlyArray<string> = ['josh-app', 'josh-game']
28
29
  // a bracketed warning printed first would otherwise be read as the command list.
29
30
  const USAGE_COMMANDS_PATTERN = /Usage:[^<>\n]*<([^<>\n]+)>/u
30
31
  const COMMAND_SEPARATOR = '|'
31
- // A toolkit that prints its usage answers in milliseconds; anything slower is hung.
32
- const USAGE_TIMEOUT_MS = 30_000
33
32
 
34
33
  function parse_usage_commands(output: string): ReadonlyArray<string> {
35
34
  const group = USAGE_COMMANDS_PATTERN.exec(output)?.[1]
@@ -46,7 +45,7 @@ async function read_toolkit_commands(bin_path: string): Promise<ReadonlyArray<st
46
45
  const result = await execa(bin_path, [], {
47
46
  all: true,
48
47
  reject: false,
49
- timeout: USAGE_TIMEOUT_MS,
48
+ timeout: COMMAND_TIMEOUT_MS,
50
49
  })
51
50
 
52
51
  return parse_usage_commands(result.all)
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url'
4
4
  import type { FileMapStamp } from '#scripts/josh/file-map-stamp'
5
5
  import { GATE_COMMAND } from '#scripts/josh/josh-command-types'
6
6
  import { composite_arguments, USAGE_ERROR_EXIT_CODE } from '#scripts/josh/josh-composite-arguments'
7
+ import { lane_ledger } from '#scripts/lane/lane-ledger'
7
8
  import { bounded_pool } from '#scripts/lib/bounded-pool'
8
9
  import { buffered_process, FAIL_EXIT_CODE } from '#scripts/lib/buffered-process'
9
10
  import { review_stamps } from '#scripts/review/review-stamps'
@@ -166,7 +167,7 @@ interface BudgetContext {
166
167
  function default_budget(): BudgetContext {
167
168
  return {
168
169
  available_cores: availableParallelism(),
169
- is_reserved: !unit_worker_share.is_nested_run(),
170
+ is_reserved: !unit_worker_share.is_nested_run() && !core_budget.is_held(),
170
171
  }
171
172
  }
172
173
 
@@ -176,6 +177,7 @@ function default_budget(): BudgetContext {
176
177
  interface ReservedStep {
177
178
  step: GateStep
178
179
  weight: number
180
+ memory_mb: number
179
181
  }
180
182
 
181
183
  async function build_reserved_steps(
@@ -187,6 +189,7 @@ async function build_reserved_steps(
187
189
  plan.checks.map(async (check) => ({
188
190
  step: await build_gate_step(check, start_directory, plan),
189
191
  weight: gate_plan.check_weight(check, plan, available_cores),
192
+ memory_mb: gate_plan.check_memory(check, plan, available_cores),
190
193
  })),
191
194
  )
192
195
  }
@@ -204,8 +207,11 @@ async function run_reserved_step(
204
207
 
205
208
  return await core_budget.with_core_reservation(
206
209
  reserved.weight,
207
- async () => await run_gate_step(reserved.step),
208
- { budget: budget.available_cores },
210
+ async (admission) => ({
211
+ ...(await run_gate_step(reserved.step)),
212
+ budget_note: core_budget.describe_overflow(admission),
213
+ }),
214
+ { budget: budget.available_cores, memory_mb: reserved.memory_mb },
209
215
  )
210
216
  }
211
217
 
@@ -222,10 +228,17 @@ async function run_gate_steps(
222
228
  ): Promise<ReadonlyArray<GateStepResult>> {
223
229
  const reserved = await build_reserved_steps(process.cwd(), plan, budget.available_cores)
224
230
 
225
- return await bounded_pool.bounded_map(
226
- reserved,
227
- plan.concurrency,
228
- async (entry) => await run_reserved_step(entry, budget),
231
+ // **The checks run under the held mark, so a check's own dispatch claims nothing**
232
+ // (joshuafolkken/kit#3345). Each check is a `pnpm josh <target>` child, and a target that declares
233
+ // a weight would otherwise claim a second place for the cores reserved for it here. A nested gate
234
+ // sets the mark too, since its checks are covered by the outer gate's reservation.
235
+ return await core_budget.with_held_mark(
236
+ async () =>
237
+ await bounded_pool.bounded_map(
238
+ reserved,
239
+ plan.concurrency,
240
+ async (entry) => await run_reserved_step(entry, budget),
241
+ ),
229
242
  )
230
243
  }
231
244
 
@@ -299,6 +312,9 @@ interface GateOptions {
299
312
  // The scoped records' paths, overridable so a test can plant a green pair rather than depend on the
300
313
  // surrounding checkout's — the reason `scoped-green.ts` takes a `source` per stamp.
301
314
  scoped_sources?: ScopedSources
315
+ // The lane ledger a finished gate's duration is appended to (joshuafolkken/kit#3355). Only the CLI
316
+ // entry resolves it, so a suite driving the gate never writes a measurement of checks that never ran.
317
+ ledger_path?: string | undefined
302
318
  }
303
319
 
304
320
  // **The gate refuses to start when the scoped pair has not been green on this tree**
@@ -339,14 +355,20 @@ async function settle_gate(
339
355
  tree: GateTree,
340
356
  options: GateOptions & { started_at: number },
341
357
  ): Promise<number> {
358
+ const elapsed_ms = performance.now() - options.started_at
342
359
  const failed_labels = gate_report.report_gate_steps(results, {
343
360
  is_verbose: options.is_verbose ?? false,
344
361
  log_path: options.log_path,
345
- elapsed_ms: performance.now() - options.started_at,
362
+ elapsed_ms,
346
363
  step_count: String(plan.checks.length),
347
364
  })
365
+ const is_passed = failed_labels.length === 0
348
366
 
349
- if (failed_labels.length > 0) return FAIL_EXIT_CODE
367
+ if (options.ledger_path !== undefined) {
368
+ await lane_ledger.record_gate(options.ledger_path, elapsed_ms, is_passed)
369
+ }
370
+
371
+ if (!is_passed) return FAIL_EXIT_CODE
350
372
 
351
373
  await record_whole_gate(plan, results, tree, options)
352
374
 
@@ -474,7 +496,9 @@ async function run_gate_command(
474
496
  // summary, written last, is the first thing lost. Setting the code lets the writes drain and the
475
497
  // process end on its own, which it can, since every child has already exited by here.
476
498
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
477
- process.exitCode = await run_gate_command(process.argv.slice(FIRST_ARGUMENT_INDEX))
499
+ process.exitCode = await run_gate_command(process.argv.slice(FIRST_ARGUMENT_INDEX), {
500
+ ledger_path: await lane_ledger.target(),
501
+ })
478
502
  }
479
503
 
480
504
  const verification_gate = {
@@ -14,8 +14,8 @@ const FULL_PAGE_QUERY = '?per_page=100'
14
14
  // `gh api` expands `{owner}` and `{repo}` from the current repository, so the unqualified form needs
15
15
  // no extra lookup to name a path. An explicit `repo` names another repository — the form a
16
16
  // cross-repository reference takes.
17
- function repo_api_path(repo?: string): string {
18
- return `repos/${repo ?? '{owner}/{repo}'}`
17
+ function repo_api_path(repo = '{owner}/{repo}'): string {
18
+ return `repos/${repo}`
19
19
  }
20
20
 
21
21
  // The issue collection — what a listing pages through and what a creation posts to. One segment,
@@ -1,8 +1,8 @@
1
+ import { blocking_issue_schema } from '#scripts/git/git-schemas'
1
2
  import {
2
3
  parse_json_array_or_undefined,
3
4
  parse_json_object_safe,
4
5
  } from '#scripts/git/parse-json-array'
5
- import { blocking_issue_schema } from '#scripts/git/schemas'
6
6
  import type { IssueReference } from '#scripts/issue/issue-reference'
7
7
  import { z } from 'zod'
8
8
  import { MERGED_STATE, to_gh_state } from './git-gh-rest-state'
@@ -163,8 +163,8 @@ function empty_blocked_by(): BlockedBy {
163
163
  // repository and names a different issue there (joshuafolkken/kit#1126). Undefined when the field is
164
164
  // absent or shaped otherwise; the caller then falls back to the repository it is reading in, which is
165
165
  // what an unqualified relation has always meant.
166
- function repo_of_url(repository_url: string | undefined): string | undefined {
167
- const match = REPOSITORY_URL.exec(repository_url ?? '')
166
+ function repo_of_url(repository_url: string | undefined = ''): string | undefined {
167
+ const match = REPOSITORY_URL.exec(repository_url)
168
168
 
169
169
  return match?.[REPOSITORY_URL_GROUP]
170
170
  }