@joshuafolkken/kit 1.1455.0 → 1.1514.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 (374) hide show
  1. package/.claude/settings.json +6 -6
  2. package/.claude/skills/dependency-update/SKILL.md +26 -55
  3. package/.claude/skills/epic-commands/SKILL.md +83 -156
  4. package/.claude/skills/epic-commands/epic-bundle.md +51 -79
  5. package/.claude/skills/epic-commands/execution-waves.md +43 -81
  6. package/.claude/skills/verify-ui/SKILL.md +24 -50
  7. package/.claude/skills/workflow-commands/SKILL.md +7 -9
  8. package/.claude/skills/workflow-commands/background-commands.md +31 -46
  9. package/.claude/skills/workflow-commands/backlogrun-child.md +65 -85
  10. package/.claude/skills/workflow-commands/backlogrun-lanes.md +86 -125
  11. package/.claude/skills/workflow-commands/backlogrun-park.md +68 -118
  12. package/.claude/skills/workflow-commands/backlogrun-progress.md +17 -28
  13. package/.claude/skills/workflow-commands/backlogrun-recovery.md +17 -26
  14. package/.claude/skills/workflow-commands/backlogrun-steps.md +35 -52
  15. package/.claude/skills/workflow-commands/backlogrun.md +31 -50
  16. package/.claude/skills/workflow-commands/chain-rule.md +31 -40
  17. package/.claude/skills/workflow-commands/delegation.md +12 -8
  18. package/.claude/skills/workflow-commands/entry-sequence.md +1 -3
  19. package/.claude/skills/workflow-commands/followup.md +55 -77
  20. package/.claude/skills/workflow-commands/fullrun-steps.md +3 -5
  21. package/.claude/skills/workflow-commands/fullrun.md +4 -6
  22. package/.claude/skills/workflow-commands/halfrun.md +3 -7
  23. package/.claude/skills/workflow-commands/into-target.md +1 -2
  24. package/.claude/skills/workflow-commands/issue-comments.md +43 -60
  25. package/.claude/skills/workflow-commands/issue-scout.md +3 -2
  26. package/.claude/skills/workflow-commands/kickoff.md +65 -74
  27. package/.claude/skills/workflow-commands/latest-gate.md +2 -6
  28. package/.claude/skills/workflow-commands/needs-human-review.md +2 -5
  29. package/.claude/skills/workflow-commands/observation-filing.md +59 -118
  30. package/.claude/skills/workflow-commands/observation-ledger.md +52 -112
  31. package/.claude/skills/workflow-commands/pre-gate-cut.md +88 -142
  32. package/.claude/skills/workflow-commands/prerequisite.md +11 -17
  33. package/.claude/skills/workflow-commands/progress-watcher.md +23 -33
  34. package/.claude/skills/workflow-commands/prrun.md +3 -5
  35. package/.claude/skills/workflow-commands/retrospective.md +35 -54
  36. package/.claude/skills/workflow-commands/split-assessment.md +32 -58
  37. package/.claude/skills/workflow-commands/target-repository.md +1 -2
  38. package/.claude/skills/workflow-commands/working-tree-hold.md +54 -73
  39. package/.codex/hooks.json +6 -6
  40. package/.github/actions/setup-node/action.yml +1 -1
  41. package/.github/workflows/ci.yml +1 -1
  42. package/.github/workflows/publish.yml +2 -2
  43. package/CLAUDE.md +7 -6
  44. package/cspell/index.yaml +7 -0
  45. package/dist/CLAUDE.md +7 -6
  46. package/dist/commands/document-section-cli.js +67 -34
  47. package/dist/hooks/batch-guard.js +6 -6
  48. package/dist/hooks/chunk-4KKD2UTK.js +66 -0
  49. package/dist/hooks/{chunk-GPLGZ6IC.js → chunk-6TS2JD6D.js} +14 -3
  50. package/dist/hooks/chunk-7DSBJVFH.js +50 -0
  51. package/dist/hooks/{chunk-5H4SSDSD.js → chunk-A65Y4YOR.js} +3 -3
  52. package/dist/hooks/{chunk-3C4XRYG6.js → chunk-APUO6U4J.js} +102 -19
  53. package/dist/hooks/{chunk-IX6TTTI6.js → chunk-EFXE6C37.js} +8 -22
  54. package/dist/hooks/chunk-GP4IEC4O.js +20 -0
  55. package/dist/hooks/chunk-JP45SOCJ.js +56 -0
  56. package/dist/hooks/{chunk-OLMO72ON.js → chunk-LAOV7NLM.js} +1 -1
  57. package/dist/hooks/chunk-M4HGZV7U.js +83 -0
  58. package/dist/hooks/{chunk-TBJFTSUY.js → chunk-O6GDRNJB.js} +9 -7
  59. package/dist/hooks/{chunk-HFGWYGUV.js → chunk-PF2NZL2A.js} +5 -5
  60. package/dist/hooks/{chunk-WNLVGT7E.js → chunk-PTICX4OL.js} +4 -5
  61. package/dist/hooks/{chunk-HHI6FO3R.js → chunk-RT27QRJW.js} +763 -1515
  62. package/dist/hooks/{chunk-7WQBPLUN.js → chunk-SYOTDYCI.js} +1149 -1095
  63. package/dist/hooks/{chunk-LIAFCXL7.js → chunk-TNUMY6NL.js} +1519 -1332
  64. package/dist/hooks/chunk-UPUZF3AN.js +279 -0
  65. package/dist/hooks/{chunk-Z4XI7CZS.js → chunk-WK6SMBIC.js} +1932 -784
  66. package/dist/hooks/codex-hook-adapter.js +17 -14
  67. package/dist/hooks/duplicate-read-guard.js +7 -7
  68. package/dist/hooks/format-edited.js +5 -4
  69. package/dist/hooks/inputs.json +302 -247
  70. package/dist/hooks/investigation-guard.js +8 -11
  71. package/dist/hooks/pretool-guard.js +16 -13
  72. package/dist/hooks/session-lang.js +372 -26
  73. package/dist/hooks/stop-guard.js +2593 -189
  74. package/dist/josh.js +2128 -282
  75. package/dist/managed-marker/index.js +1 -2
  76. package/dist/version/index.js +15 -12
  77. package/eslint/base.js +2 -1
  78. package/eslint/rules/unicorn.js +16 -0
  79. package/lefthook/base.yml +5 -3
  80. package/package.json +14 -12
  81. package/prompts/coding-standards.md +12 -22
  82. package/prompts/collaboration-workflow/file-edits.md +25 -45
  83. package/prompts/collaboration-workflow/gh-rest.md +19 -36
  84. package/prompts/collaboration-workflow/glossary.md +5 -7
  85. package/prompts/collaboration-workflow/issue-citation.md +6 -14
  86. package/prompts/collaboration-workflow/issue-template.md +40 -32
  87. package/prompts/collaboration-workflow/lane-instructions.md +35 -0
  88. package/prompts/collaboration-workflow/operating-rules.md +33 -46
  89. package/prompts/collaboration-workflow/output-bounds.md +15 -15
  90. package/prompts/collaboration-workflow/overview.md +14 -19
  91. package/prompts/collaboration-workflow/plan-comment.md +13 -13
  92. package/prompts/collaboration-workflow/principles.md +57 -54
  93. package/prompts/collaboration-workflow/proposal-request.md +1 -9
  94. package/prompts/collaboration-workflow/report-format.md +41 -49
  95. package/prompts/collaboration-workflow/residency.md +15 -11
  96. package/prompts/collaboration-workflow/rule-delivery.md +7 -15
  97. package/prompts/collaboration-workflow/shell-body.md +10 -20
  98. package/prompts/collaboration-workflow/turn-batching.md +8 -14
  99. package/prompts/collaboration-workflow/upstream-interrupt.md +26 -34
  100. package/prompts/collaboration-workflow/wip-cap.md +26 -32
  101. package/prompts/collaboration-workflow.md +25 -25
  102. package/prompts/refactoring.md +33 -43
  103. package/prompts/review-rubric.md +84 -129
  104. package/prompts/review.md +110 -141
  105. package/prompts/sonar-hotspot-handling.md +22 -50
  106. package/prompts/testing-guide.md +48 -99
  107. package/scripts/adopt/adopt-toolkits.ts +2 -4
  108. package/scripts/agent/agent-argv.ts +20 -4
  109. package/scripts/agent/agent-exit-record.ts +23 -1
  110. package/scripts/agent/agent-role-profile.ts +101 -45
  111. package/scripts/agent/agent-safe-text.ts +60 -0
  112. package/scripts/agent/claude-agent-argv.ts +14 -0
  113. package/scripts/agent/codex-agent-argv.ts +9 -2
  114. package/scripts/agent/lane-instructions.ts +144 -0
  115. package/scripts/backlog/backlog-budget-cli.ts +1 -2
  116. package/scripts/backlog/backlog-budget.ts +4 -4
  117. package/scripts/backlog/backlog-drive-carried.ts +26 -0
  118. package/scripts/backlog/backlog-drive-cli.ts +10 -5
  119. package/scripts/backlog/backlog-drive-named.ts +3 -5
  120. package/scripts/backlog/backlog-drive-restore.ts +28 -2
  121. package/scripts/backlog/backlog-drive-seats.ts +27 -0
  122. package/scripts/backlog/backlog-drive.ts +11 -6
  123. package/scripts/backlog/backlog-occupied.ts +56 -0
  124. package/scripts/backlog/backlog-ready.ts +59 -15
  125. package/scripts/behavior/behavior-rules.ts +3 -2
  126. package/scripts/cost-runtime/codex-usage.ts +4 -2
  127. package/scripts/cost-runtime/cost-cli.ts +1 -2
  128. package/scripts/delegation/duplicate-read-guard.ts +2 -2
  129. package/scripts/delegation/investigation-guard.ts +15 -65
  130. package/scripts/delegation/investigation-reads.ts +1 -2
  131. package/scripts/discovery/repo-discovery.ts +4 -2
  132. package/scripts/discovery/repo-origin.ts +2 -4
  133. package/scripts/doctor/doctor-logic.ts +6 -3
  134. package/scripts/document/document-byte-budget.ts +28 -27
  135. package/scripts/document/document-byte-check.ts +1 -2
  136. package/scripts/document/document-section-cli.ts +17 -5
  137. package/scripts/document/entry-read-budget.ts +3 -3
  138. package/scripts/document/read-set-cli.ts +4 -1
  139. package/scripts/epic/epic-audit-orphans.ts +5 -8
  140. package/scripts/epic/epic-audit-rationale.ts +8 -2
  141. package/scripts/epic/epic-audit.ts +52 -1
  142. package/scripts/epic/epic-bundle-cli.ts +1 -2
  143. package/scripts/epic/epic-bundle-evidence.ts +49 -26
  144. package/scripts/epic/epic-bundle-referenced.ts +14 -3
  145. package/scripts/epic/epic-bundle.ts +17 -9
  146. package/scripts/epic/epic-chains.ts +6 -2
  147. package/scripts/epic/epic-close-comment.ts +1 -2
  148. package/scripts/followup/git-followup-workflow.ts +19 -1
  149. package/scripts/gate/check-inputs.ts +129 -0
  150. package/scripts/gate/check-reuse.ts +162 -0
  151. package/scripts/gate/core-budget.ts +93 -44
  152. package/scripts/gate/core-wait.ts +130 -0
  153. package/scripts/gate/free-cores.ts +49 -0
  154. package/scripts/gate/gate-ledger.ts +17 -3
  155. package/scripts/gate/gate-plan.ts +23 -1
  156. package/scripts/gate/gate-report.ts +15 -2
  157. package/scripts/gate/gate-run-steps.ts +150 -0
  158. package/scripts/gate/held-mark.ts +55 -0
  159. package/scripts/gate/machine-capacity.ts +70 -11
  160. package/scripts/gate/pre-commit-type-check.ts +14 -1
  161. package/scripts/gate/verification-gate.ts +65 -127
  162. package/scripts/gh/git-gh-issue-rest.ts +1 -0
  163. package/scripts/gh/git-gh-issue-write.ts +24 -2
  164. package/scripts/gh/git-pr.ts +19 -3
  165. package/scripts/git/git-common-directory.ts +3 -3
  166. package/scripts/git/git-diff-reads.ts +16 -9
  167. package/scripts/git/git-execa-error.ts +1 -2
  168. package/scripts/git/main-merge-regenerate.ts +78 -0
  169. package/scripts/git/main-merge.ts +14 -5
  170. package/scripts/hooks/batch-guard.ts +5 -8
  171. package/scripts/hooks/format-edited-file.ts +10 -4
  172. package/scripts/hooks/pre-commit-lint.ts +91 -0
  173. package/scripts/hooks/pre-push-unit.ts +15 -4
  174. package/scripts/hooks/pretool-guard.ts +48 -79
  175. package/scripts/hooks/reserved-run.ts +1 -0
  176. package/scripts/hooks/run-hook.sh +10 -12
  177. package/scripts/hooks/step-zero-notice.ts +3 -2
  178. package/scripts/hooks/stop-actions.ts +204 -0
  179. package/scripts/hooks/stop-guard.ts +62 -21
  180. package/scripts/init/hook-command-rewrite.ts +6 -2
  181. package/scripts/init/hook-launch.ts +12 -7
  182. package/scripts/init/init-logic-deploy-vps.ts +4 -2
  183. package/scripts/issue/behavior-change-lint.ts +12 -6
  184. package/scripts/issue/expected-effect-lint.ts +59 -0
  185. package/scripts/issue/issue-cite-cli.ts +35 -15
  186. package/scripts/issue/issue-classification.ts +1 -1
  187. package/scripts/issue/issue-lint-cli.ts +5 -3
  188. package/scripts/issue/issue-scout-cli.ts +22 -16
  189. package/scripts/issue/markdown-section.ts +19 -5
  190. package/scripts/josh/command-usage.ts +110 -0
  191. package/scripts/josh/file-map-stamp.ts +1 -3
  192. package/scripts/josh/hook-decision.ts +17 -2
  193. package/scripts/josh/josh-command-map.ts +2 -0
  194. package/scripts/josh/josh-command-types.ts +55 -9
  195. package/scripts/josh/josh-commands-ai.ts +13 -0
  196. package/scripts/josh/josh-commands-development.ts +1 -1
  197. package/scripts/josh/josh-commands-hooks.ts +6 -0
  198. package/scripts/josh/josh-commands-lane.ts +10 -1
  199. package/scripts/josh/josh-commands-workflow.ts +5 -0
  200. package/scripts/josh/josh-logic.ts +6 -15
  201. package/scripts/josh/josh-usage-automation.ts +121 -0
  202. package/scripts/josh/josh-usage-backlog.ts +180 -0
  203. package/scripts/josh/josh-usage-developer.ts +230 -0
  204. package/scripts/josh/josh-usage-hooks.ts +38 -0
  205. package/scripts/josh/josh-usage-issue.ts +205 -0
  206. package/scripts/josh/josh-usage-review.ts +101 -0
  207. package/scripts/josh/josh-usage-run.ts +252 -0
  208. package/scripts/josh/josh-usage-session.ts +238 -0
  209. package/scripts/josh/josh-usage-workflow.ts +212 -0
  210. package/scripts/josh/josh.ts +14 -5
  211. package/scripts/josh/package-directory.ts +20 -0
  212. package/scripts/lane/lane-await.ts +3 -2
  213. package/scripts/lane/lane-capacity.ts +8 -2
  214. package/scripts/lane/lane-check-stats.ts +56 -0
  215. package/scripts/lane/lane-child-invocation.ts +49 -5
  216. package/scripts/lane/lane-dispatch.ts +3 -3
  217. package/scripts/lane/lane-guard-policy.ts +5 -5
  218. package/scripts/lane/lane-handoff.ts +8 -1
  219. package/scripts/lane/lane-install.ts +12 -4
  220. package/scripts/lane/lane-ledger.ts +52 -1
  221. package/scripts/lane/lane-limit-cli.ts +8 -9
  222. package/scripts/lane/lane-load.ts +9 -2
  223. package/scripts/lane/lane-measure-cli.ts +4 -0
  224. package/scripts/lane/lane-parked.ts +18 -0
  225. package/scripts/lane/lane-preserve.ts +29 -1
  226. package/scripts/lane/lane-process-load.ts +125 -0
  227. package/scripts/lane/lane-process-stats.ts +93 -0
  228. package/scripts/lane/lane-registry.ts +17 -10
  229. package/scripts/lane/lane-restart.ts +5 -26
  230. package/scripts/lane/lane-session-action.ts +146 -0
  231. package/scripts/lane/lane-session-broker.ts +68 -125
  232. package/scripts/lane/lane-spent.ts +14 -3
  233. package/scripts/lane/lane-stats.ts +1 -0
  234. package/scripts/lane/lane-withdraw-cli.ts +71 -0
  235. package/scripts/lane/lane-withdraw.ts +182 -0
  236. package/scripts/lint/format.ts +4 -4
  237. package/scripts/lint/lint-parallel.ts +10 -3
  238. package/scripts/lint/lint-related-scope.ts +4 -1
  239. package/scripts/managed-marker/managed-marker-logic.ts +1 -2
  240. package/scripts/notify/telegram-test.ts +10 -5
  241. package/scripts/overrides/lockfile-overrides.ts +1 -3
  242. package/scripts/propagate/propagate.ts +1 -2
  243. package/scripts/release/release-progress.ts +3 -2
  244. package/scripts/report/report-format-reference.ts +2 -0
  245. package/scripts/report/report-lint.ts +7 -4
  246. package/scripts/review/acceptance-map-check.ts +61 -0
  247. package/scripts/review/acceptance-map.ts +205 -0
  248. package/scripts/review/review-brief-cli.ts +1 -1
  249. package/scripts/review/review-record-cli.ts +6 -2
  250. package/scripts/rules/agent-model.ts +59 -0
  251. package/scripts/rules/delivered-rules.ts +11 -2
  252. package/scripts/rules/destructive-command.ts +6 -9
  253. package/scripts/rules/direct-filing.ts +3 -7
  254. package/scripts/rules/early-heartbeat.ts +7 -18
  255. package/scripts/rules/file-body.ts +6 -13
  256. package/scripts/rules/filing-cap.ts +4 -6
  257. package/scripts/rules/firing-point.ts +6 -5
  258. package/scripts/rules/git-force.ts +5 -9
  259. package/scripts/rules/guard-remedy.ts +117 -0
  260. package/scripts/rules/implementation-cut.ts +37 -35
  261. package/scripts/rules/index-guard.ts +4 -7
  262. package/scripts/rules/issue-comments.ts +67 -16
  263. package/scripts/rules/josh-git-bare.ts +3 -6
  264. package/scripts/rules/lane-background.ts +10 -21
  265. package/scripts/rules/lane-carry-conflict.ts +7 -17
  266. package/scripts/rules/lane-interactive-ask.ts +6 -11
  267. package/scripts/rules/lane-park.ts +5 -13
  268. package/scripts/rules/lane-split-park.ts +2 -7
  269. package/scripts/rules/lane-switch-main.ts +9 -12
  270. package/scripts/rules/last-prompt.ts +50 -5
  271. package/scripts/rules/oracle-consulted.ts +4 -5
  272. package/scripts/rules/oracle-list-cli.ts +7 -0
  273. package/scripts/rules/piped-verification.ts +84 -32
  274. package/scripts/rules/poll-loop.ts +7 -17
  275. package/scripts/rules/pre-gate-cut.ts +7 -21
  276. package/scripts/rules/prior-comment-read.ts +7 -5
  277. package/scripts/rules/protected-files.ts +5 -9
  278. package/scripts/rules/raw-field-body.ts +3 -7
  279. package/scripts/rules/rule-body-guard.ts +4 -11
  280. package/scripts/rules/rule-list.ts +4 -1
  281. package/scripts/rules/rule-registry-rows.ts +10 -0
  282. package/scripts/rules/rule-registry.ts +7 -4
  283. package/scripts/rules/rule-rewrite.ts +114 -0
  284. package/scripts/rules/rule-value.ts +7 -1
  285. package/scripts/rules/run-tail-rule.ts +6 -22
  286. package/scripts/rules/shell-body-trigger.ts +4 -6
  287. package/scripts/rules/shell-segments.ts +22 -0
  288. package/scripts/rules/stop-decision.ts +77 -0
  289. package/scripts/rules/stop-rules.ts +164 -77
  290. package/scripts/rules/tail-commands.ts +15 -1
  291. package/scripts/rules/test-declared-commit.ts +4 -10
  292. package/scripts/rules/third-party-write.ts +5 -10
  293. package/scripts/rules/worktree-guard.ts +5 -12
  294. package/scripts/run/board/run-board-cli.ts +2 -0
  295. package/scripts/run/board/run-board-closed.ts +8 -2
  296. package/scripts/run/board/run-board-github.ts +19 -3
  297. package/scripts/run/board/run-board-header.ts +4 -1
  298. package/scripts/run/board/run-board-idle.ts +31 -0
  299. package/scripts/run/board/run-board-labels.ts +2 -0
  300. package/scripts/run/board/run-board-layout.ts +1 -1
  301. package/scripts/run/board/run-board-read.ts +25 -12
  302. package/scripts/run/board/run-board-reason.ts +17 -4
  303. package/scripts/run/board/run-board-render-legend.ts +1 -0
  304. package/scripts/run/board/run-board-render-slot.ts +1 -1
  305. package/scripts/run/board/run-board-render-waits.ts +41 -0
  306. package/scripts/run/board/run-board-render.ts +6 -1
  307. package/scripts/run/board/run-board-row-yield.ts +1 -0
  308. package/scripts/run/board/run-board-state.ts +9 -0
  309. package/scripts/run/board/run-board-status.ts +89 -23
  310. package/scripts/run/board/run-board-thanks.ts +7 -2
  311. package/scripts/run/board/run-board-tick.ts +37 -8
  312. package/scripts/run/carry/run-carry-args.ts +1 -3
  313. package/scripts/run/carry/run-carry-conversation.ts +7 -2
  314. package/scripts/run/cut/run-cut-cli.ts +18 -22
  315. package/scripts/run/cut/run-cut-decline.ts +72 -0
  316. package/scripts/run/cut/run-cut-ready.ts +74 -0
  317. package/scripts/run/cut/run-cut-report.ts +29 -0
  318. package/scripts/run/cut/run-cut.ts +10 -1
  319. package/scripts/run/detached-launch.ts +23 -4
  320. package/scripts/run/entry/run-entry-cli.ts +8 -1
  321. package/scripts/run/event/run-event-render.ts +1 -0
  322. package/scripts/run/event/run-event-scope.ts +19 -1
  323. package/scripts/run/event/run-event-stream-emit.ts +13 -6
  324. package/scripts/run/event/run-event-stream.ts +4 -0
  325. package/scripts/run/hold/run-hold-cli.ts +14 -3
  326. package/scripts/run/hold/run-hold-session.ts +46 -0
  327. package/scripts/run/hold/run-hold.ts +13 -4
  328. package/scripts/run/launch-priority.ts +32 -0
  329. package/scripts/run/merge/run-merge-cli.ts +16 -42
  330. package/scripts/run/merge/run-merge-ship.ts +59 -1
  331. package/scripts/run/merge/run-merge-signals.ts +99 -0
  332. package/scripts/run/merge/run-merge-steps.ts +11 -1
  333. package/scripts/run/merge/run-merge.ts +22 -9
  334. package/scripts/run/run-ending.ts +31 -3
  335. package/scripts/run/run-liveness.ts +1 -1
  336. package/scripts/run/run-parent-cut-hook.ts +5 -10
  337. package/scripts/run/run-poll-hook.ts +157 -0
  338. package/scripts/run/run-preflight.ts +1 -2
  339. package/scripts/run/run-step-cli.ts +22 -15
  340. package/scripts/run/run-step-section.ts +85 -0
  341. package/scripts/run/run-stop-notify.ts +1 -2
  342. package/scripts/run/run-watcher-guard.ts +6 -3
  343. package/scripts/run/ship/run-ship-cli.ts +5 -12
  344. package/scripts/run/ship/run-ship-detach.ts +9 -0
  345. package/scripts/run/ship/run-ship-next.ts +10 -2
  346. package/scripts/run/ship/run-ship-overlap.ts +195 -0
  347. package/scripts/run/ship/run-ship-pre-detach.ts +33 -15
  348. package/scripts/run/ship/run-ship-preflight.ts +28 -2
  349. package/scripts/run/ship/run-ship-return.ts +31 -7
  350. package/scripts/run/ship/run-ship-review-steps.ts +25 -14
  351. package/scripts/run/ship/run-ship-review.ts +15 -2
  352. package/scripts/run/ship/run-ship-steps.ts +1 -1
  353. package/scripts/run/ship/run-ship-stop-context.ts +206 -0
  354. package/scripts/run/ship/run-ship-sync.ts +14 -6
  355. package/scripts/run/tidy/run-tidy-cli.ts +3 -3
  356. package/scripts/run/tidy/run-tidy-lanes.ts +3 -2
  357. package/scripts/safe-chain/project-config.ts +7 -2
  358. package/scripts/sync/sync-hook-safety.ts +8 -2
  359. package/scripts/test/test-e2e-guard.ts +30 -5
  360. package/scripts/test/test-unit-guard.ts +16 -1
  361. package/scripts/test/unit-worker-share.ts +26 -3
  362. package/scripts/time-runtime/time-batch-guard.ts +3 -3
  363. package/scripts/time-runtime/time-hook-transcript.ts +3 -2
  364. package/scripts/time-runtime/time-shell.ts +39 -4
  365. package/scripts/time-runtime/time-spans.ts +1 -0
  366. package/scripts/time-runtime/time-tool-call.ts +7 -0
  367. package/scripts/version/effective-upstream.ts +3 -4
  368. package/scripts/version/kit-version-config.ts +8 -2
  369. package/scripts/version/latest-corepack.ts +1 -2
  370. package/scripts/version/release-hold.ts +11 -4
  371. package/scripts/version/version-commands.ts +12 -4
  372. package/templates/gitignore +6 -2
  373. package/dist/hooks/chunk-D5QBTOCZ.js +0 -68
  374. package/dist/hooks/chunk-UT7YC3N2.js +0 -56
@@ -10,6 +10,8 @@ import { json_value } from '#scripts/lib/json-value'
10
10
  import { unit_worker_share } from '#scripts/test/unit-worker-share'
11
11
  import { z } from 'zod'
12
12
  import { core_admission, type Admission, type LedgerEntry, type Overflow } from './core-admission'
13
+ import { core_wait, type BudgetWait, type WaitReason } from './core-wait'
14
+ import { held_mark } from './held-mark'
13
15
  import { machine_capacity, type MachineBudget, type MachineReading } from './machine-capacity'
14
16
 
15
17
  // A machine-wide weighted core budget with admission control.
@@ -55,11 +57,22 @@ const MIN_BUDGET = 1
55
57
  // and spell check numbers are `gate-plan.ts`'s measured table, floored. The whole-tree eslint scan
56
58
  // behind `josh lines` and `josh refactor:scan` runs in one process: measured cold on the 11-core
57
59
  // machine on 2026-10-06, 113s of CPU over 98s of wall, so it gets one core.
60
+ //
61
+ // The rest ran outside the ledger until joshuafolkken/kit#3944, measured with `/usr/bin/time -l` on the
62
+ // same machine on 2026-10-17 under a load average of 16. Every one stays under one core, so each takes
63
+ // one: `prettier --check` over 40 staged files 2.1s of CPU over 1.7s of wall (`format`), `josh
64
+ // retrospective` 16.5s over 19.8s, and a fresh lane's `pnpm install --frozen-lockfile` 1.9s over 6.1s.
65
+ // `e2e` is the Playwright worker count `josh test:e2e` passes locally — the two-worker run CI already
66
+ // sizes for its four-core runner — since kit has no e2e suite of its own to time.
58
67
  const CORE_WEIGHTS = {
59
68
  lint: 2,
60
69
  type_check: 1,
61
70
  spell_check: 1,
62
71
  eslint_scan: 1,
72
+ format: 1,
73
+ retrospective: 1,
74
+ install: 1,
75
+ e2e: 2,
63
76
  } as const
64
77
 
65
78
  // **The memory each heavy tool holds, declared beside its cores**: peak
@@ -67,33 +80,37 @@ const CORE_WEIGHTS = {
67
80
  // 2026-10-07: the type check 1,244 MB, the spell check 330 MB, and one unit worker 319 MB over 54 test
68
81
  // files. Typed eslint was 1,207 MB over one directory and was observed at 1.6–2.4 GB per whole-tree
69
82
  // `eslint .` with three lanes running the same day, so lint and the whole-tree scan take that peak.
83
+ // On 2026-10-17: prettier 260 MB, the retrospective 862 MB and a lane install 65 MB.
70
84
  const MEMORY_MB = {
71
85
  lint: 2400,
72
86
  type_check: 1300,
73
87
  spell_check: 350,
74
88
  eslint_scan: 2400,
75
89
  unit_worker: 350,
90
+ format: 300,
91
+ retrospective: 900,
92
+ install: 100,
76
93
  } as const
77
94
 
78
- // **The environment mark that says a parent already holds this process's cores**
79
- // `josh gate` reserves per check and then spawns `pnpm josh lint`. Without
80
- // the mark, that child's own dispatch would claim a second place for the cores its parent already
81
- // holds. Every holder sets it, and every child inherits it through the environment and claims nothing.
82
- const HELD_KEY = 'JOSH_CORE_RESERVED'
83
- const HELD_VALUE = '1'
84
-
85
95
  // `memory_mb` and `admitted_at` are optional so a marker an older `josh` wrote still reads: it holds no
86
96
  // declared memory, and it counts as load from outside the ledger until it is gone.
97
+ // `command`, `checkout`, `lane`, `lanes_root` and `wait_reason` name a waiting claim on `run:board`
98
+ // (`core-wait.ts`).
87
99
  const reservation_schema = process_owner_schema.extend({
88
100
  weight: z.number(),
89
101
  claimed_at: z.number(),
90
102
  memory_mb: z.number().optional(),
91
103
  admitted_at: z.number().optional(),
104
+ command: z.string().optional(),
105
+ checkout: z.string().optional(),
106
+ lane: z.string().optional(),
107
+ lanes_root: z.string().optional(),
108
+ wait_reason: z.enum(core_wait.WAIT_REASONS).optional(),
92
109
  })
93
110
 
94
111
  type Reservation = z.infer<typeof reservation_schema>
95
112
 
96
- type ReservationFields = Pick<Reservation, 'weight' | 'claimed_at' | 'memory_mb' | 'admitted_at'>
113
+ type ReservationFields = Omit<Reservation, 'pid' | 'process_start'>
97
114
 
98
115
  interface LiveReservation extends LedgerEntry {
99
116
  reservation: Reservation
@@ -102,6 +119,9 @@ interface LiveReservation extends LedgerEntry {
102
119
  interface ReserveOptions {
103
120
  budget?: number
104
121
  memory_mb?: number | undefined
122
+ // What the claim is for, named on the waiting line and on `run:board`.
123
+ command?: string | undefined
124
+ report?: (line: string) => void
105
125
  read_machine?: () => Promise<MachineReading>
106
126
  wait_cap_ms?: number
107
127
  poll_interval_ms?: number
@@ -195,6 +215,10 @@ function write_reservation(target: string, fields: ReservationFields): void {
195
215
  stamp_file.replace_stamp(target, { ...process_identity.own_fields(), ...fields })
196
216
  }
197
217
 
218
+ function report_wait(line: string): void {
219
+ process.stderr.write(`${line}\n`)
220
+ }
221
+
198
222
  // The clock and cadence a wait runs on, resolved apart from the ledger fields so `reserve` stays under
199
223
  // the complexity limit — each default is a branch.
200
224
  interface TimingConfig {
@@ -206,6 +230,7 @@ interface TimingConfig {
206
230
 
207
231
  interface WaitContext extends TimingConfig {
208
232
  read_machine: () => Promise<MachineReading>
233
+ report: (line: string) => void
209
234
  key: string
210
235
  target: string
211
236
  directory: string
@@ -259,6 +284,19 @@ function overflow_of(
259
284
  }
260
285
  }
261
286
 
287
+ // The marker records why the claim waits, rewritten only when the reason changes, and the command says
288
+ // so once, when its wait starts.
289
+ function mark_waiting(context: WaitContext, reason: WaitReason): void {
290
+ const previous = context.fields.wait_reason
291
+
292
+ if (previous === reason) return
293
+
294
+ context.fields = { ...context.fields, wait_reason: reason }
295
+ write_reservation(context.target, context.fields)
296
+
297
+ if (previous === undefined) context.report(core_wait.wait_line(context.fields.command, reason))
298
+ }
299
+
262
300
  async function poll_admission(context: WaitContext): Promise<Admission | undefined> {
263
301
  const reservations = live_reservations(context.directory)
264
302
  const budget = await current_budget(context, reservations)
@@ -269,6 +307,8 @@ async function poll_admission(context: WaitContext): Promise<Admission | undefin
269
307
  return admit(context, overflow_of(context, reservations, budget))
270
308
  }
271
309
 
310
+ mark_waiting(context, core_wait.wait_reason(context.key, reservations, budget))
311
+
272
312
  return undefined
273
313
  }
274
314
 
@@ -288,13 +328,33 @@ async function await_admission(context: WaitContext): Promise<Admission> {
288
328
  return admission
289
329
  }
290
330
 
331
+ // What a wait reads the machine through and reports its start to.
332
+ function resolve_ports(options: ReserveOptions): Pick<WaitContext, 'read_machine' | 'report'> {
333
+ return {
334
+ read_machine: options.read_machine ?? machine_capacity.read_machine,
335
+ report: options.report ?? report_wait,
336
+ }
337
+ }
338
+
339
+ // The checkout the claim runs in, so `run:board` can say whose claim waits. The ledger is the
340
+ // machine's, so the checkout may be another repository's (`core-wait.ts` → `where_of`).
341
+ function claim_fields(
342
+ weight: number,
343
+ claimed_at: number,
344
+ options: ReserveOptions,
345
+ ): ReservationFields {
346
+ const { memory_mb, command } = options
347
+
348
+ return { weight, claimed_at, memory_mb, command, ...core_wait.where_of(process.cwd()) }
349
+ }
350
+
291
351
  async function reserve(weight: number, options: ReserveOptions = {}): Promise<Handle> {
292
352
  const timing = resolve_timing(options)
293
353
  const directory = options.directory ?? PLATFORM_TEMP_ROOT
294
354
  const claimed_at = timing.now()
295
355
  const name = marker_name(randomUUID())
296
356
  const target = path.join(directory, name)
297
- const fields = { weight, claimed_at, memory_mb: options.memory_mb }
357
+ const fields = claim_fields(weight, claimed_at, options)
298
358
 
299
359
  write_reservation(target, fields)
300
360
  const admission = await await_admission({
@@ -305,58 +365,42 @@ async function reserve(weight: number, options: ReserveOptions = {}): Promise<Ha
305
365
  budget: normalize_budget(options.budget ?? availableParallelism()),
306
366
  deadline: claimed_at + timing.wait_cap_ms,
307
367
  fields,
308
- read_machine: options.read_machine ?? machine_capacity.read_machine,
368
+ ...resolve_ports(options),
309
369
  })
310
370
 
311
371
  return { target, admission }
312
372
  }
313
373
 
374
+ // The claims waiting in the ledger now, for a `run:board` running in `home`.
375
+ function budget_waits(
376
+ directory: string = PLATFORM_TEMP_ROOT,
377
+ now: number = Date.now(),
378
+ home: string = process.cwd(),
379
+ ): Array<BudgetWait> {
380
+ return core_wait.waits_of(live_reservations(directory), now, home)
381
+ }
382
+
314
383
  function release(handle: Handle): void {
315
384
  sweep_marker(handle.target)
316
385
  }
317
386
 
318
387
  // The whole reservation lifetime around a step: claim a place, wait for admission, run, and free the
319
388
  // place on any exit. `finally`, so a step that threw still releases its cores. The step is told how it was
320
- // admitted, so the gate can report one that started past the budget.
389
+ // admitted, so the gate can report one that started past the budget, and the claim's ledger key.
321
390
  async function with_core_reservation<T>(
322
391
  weight: number,
323
- run: (admission: Admission) => Promise<T>,
392
+ run: (admission: Admission, key: string) => Promise<T>,
324
393
  options: ReserveOptions = {},
325
394
  ): Promise<T> {
326
395
  const handle = await reserve(weight, options)
327
396
 
328
397
  try {
329
- return await run(handle.admission)
398
+ return await run(handle.admission, path.basename(handle.target))
330
399
  } finally {
331
400
  release(handle)
332
401
  }
333
402
  }
334
403
 
335
- function is_held(): boolean {
336
- return process.env[HELD_KEY] === HELD_VALUE
337
- }
338
-
339
- // A function of its own so the restoring write does not sit after an `await`, where
340
- // `require-atomic-updates` reads any write to `process` as a possible race — the reason `josh.ts`
341
- // gives `record_exit_code`.
342
- function set_held_mark(value: string): void {
343
- process.env[HELD_KEY] = value
344
- }
345
-
346
- // Marks this process, and every child it spawns, as running inside a held reservation. The previous
347
- // value comes back afterwards, so a nested holder never clears the mark its outer holder set.
348
- async function with_held_mark<T>(run: () => Promise<T>): Promise<T> {
349
- const previous = process.env[HELD_KEY] ?? ''
350
-
351
- set_held_mark(HELD_VALUE)
352
-
353
- try {
354
- return await run()
355
- } finally {
356
- set_held_mark(previous)
357
- }
358
- }
359
-
360
404
  // **A command's reservation: the gate's claim, unless a parent already holds the cores**
361
405
  // `josh-logic.ts` calls this around every command that declares a weight.
362
406
  // Each command then only declares its weight, and no command carries a copy of the claim, run and
@@ -368,27 +412,32 @@ async function with_command_reservation<T>(
368
412
  run: () => Promise<T>,
369
413
  options: ReserveOptions = {},
370
414
  ): Promise<T> {
371
- if (is_held() || unit_worker_share.is_nested_run()) return await run()
415
+ if (held_mark.is_held() || unit_worker_share.is_nested_run()) return await run()
372
416
 
373
- return await with_core_reservation(weight, async () => await with_held_mark(run), options)
417
+ return await with_core_reservation(
418
+ weight,
419
+ async (_admission, key) => await held_mark.with_held_mark(run, key),
420
+ options,
421
+ )
374
422
  }
375
423
 
376
424
  const core_budget = {
377
425
  CORE_WEIGHTS,
378
- HELD_KEY,
379
- HELD_VALUE,
426
+ HELD_KEY: held_mark.HELD_KEY,
427
+ HELD_VALUE: held_mark.HELD_VALUE,
380
428
  MEMORY_MB,
381
429
  RESERVED_PREFIX,
382
430
  admitted_keys: core_admission.admitted_keys,
383
431
  admitted_load: core_admission.admitted_load,
432
+ budget_waits,
384
433
  describe_overflow: core_admission.describe_overflow,
385
- is_held,
434
+ is_held: held_mark.is_held,
386
435
  live_reservations,
387
436
  release,
388
437
  reserve,
389
438
  with_command_reservation,
390
439
  with_core_reservation,
391
- with_held_mark,
440
+ with_held_mark: held_mark.with_held_mark,
392
441
  }
393
442
 
394
443
  export type { LiveReservation, Reservation, ReserveOptions }
@@ -0,0 +1,130 @@
1
+ import path from 'node:path'
2
+ import { lane_paths } from '#scripts/lane/lane-paths'
3
+ import { core_admission, type LedgerEntry } from './core-admission'
4
+ import type { MachineBudget } from './machine-capacity'
5
+
6
+ // **Why a claim waits, and who is waiting** (joshuafolkken/kit#3943). A claim held back by the budget
7
+ // used to wait in silence: the command printed nothing and no board showed it, so a lane stalled on
8
+ // memory pressure read the same as one stuck. The waiting claim records its reason on its marker and
9
+ // says it once on its own output; `run:board` draws every marker still waiting. Everything here is a
10
+ // pure function of the ledger and the budget it is handed.
11
+
12
+ const WAIT_REASONS = ['cores', 'memory', 'pressure'] as const
13
+
14
+ type WaitReason = (typeof WAIT_REASONS)[number]
15
+
16
+ // The one wording of each reason, shared by the waiting command's line and the board's.
17
+ const REASON_TEXT: Record<WaitReason, string> = {
18
+ cores: 'not enough free cores',
19
+ memory: 'not enough free memory',
20
+ pressure: 'memory under pressure',
21
+ }
22
+
23
+ // What the board reads off a marker. Every field but the claim time is optional, so a marker an older
24
+ // `josh` wrote still reads, and one that never waited is not drawn.
25
+ interface WhereFields {
26
+ checkout?: string | undefined
27
+ lane?: string | undefined
28
+ lanes_root?: string | undefined
29
+ }
30
+
31
+ interface WaitMarker extends WhereFields {
32
+ claimed_at: number
33
+ admitted_at?: number | undefined
34
+ command?: string | undefined
35
+ wait_reason?: WaitReason | undefined
36
+ }
37
+
38
+ // Where a waiting claim runs: `lane` is the board's own repository's lane number, which the board
39
+ // links; `elsewhere` names another repository's checkout, which it cannot link. Neither is the board's
40
+ // own main checkout.
41
+ interface BudgetPlace {
42
+ lane: string | undefined
43
+ elsewhere: string | undefined
44
+ }
45
+
46
+ interface BudgetWait extends BudgetPlace {
47
+ command: string | undefined
48
+ waited_ms: number
49
+ reason: WaitReason
50
+ }
51
+
52
+ // **The cores decide first**: a claim that would not fit even with unlimited memory waits for cores;
53
+ // one that fits on cores waits for memory, named as pressure when the reading saw compression or swap.
54
+ function wait_reason(
55
+ key: string,
56
+ reservations: ReadonlyArray<LedgerEntry>,
57
+ budget: MachineBudget,
58
+ ): WaitReason {
59
+ const cores_only = { cores: budget.cores, memory_mb: Infinity }
60
+
61
+ if (!core_admission.is_admitted(key, reservations, cores_only)) return 'cores'
62
+
63
+ return budget.memory_pressured === true ? 'pressure' : 'memory'
64
+ }
65
+
66
+ // The line a waiting command prints once, when its wait starts.
67
+ function wait_line(command: string | undefined, reason: WaitReason): string {
68
+ return `⏳ ${command ?? 'a step'} waits for the core budget — ${REASON_TEXT[reason]}`
69
+ }
70
+
71
+ // The lanes root a checkout belongs to: a lane's parent, or the root beside a main checkout. Two
72
+ // checkouts of one repository share it; the ledger is machine-wide, so another repository's do not.
73
+ function lanes_root_of(checkout: string): string {
74
+ if (lane_paths.lane_issue_of(checkout) === undefined) return lane_paths.lane_root(checkout)
75
+
76
+ return path.dirname(path.resolve(checkout))
77
+ }
78
+
79
+ // Where a claim runs, read by the claimer under its own environment: `JOSH_LANE_ROOT` is a
80
+ // per-project setting, so a board cannot derive another repository's lanes root from its own.
81
+ function where_of(checkout: string): Required<WhereFields> {
82
+ return { checkout, lane: lane_paths.lane_issue_of(checkout), lanes_root: lanes_root_of(checkout) }
83
+ }
84
+
85
+ // Another repository's checkout is named by its lanes root and lane, or its directory, so its lane
86
+ // number is never linked to the board's repository's issue of that number.
87
+ function place_of(marker: WhereFields, home_root: string): BudgetPlace {
88
+ const { checkout, lane, lanes_root } = marker
89
+
90
+ if (checkout === undefined || lanes_root === undefined || lanes_root === home_root) {
91
+ return { lane, elsewhere: undefined }
92
+ }
93
+
94
+ const name = lane === undefined ? path.basename(checkout) : `${path.basename(lanes_root)}/${lane}`
95
+
96
+ return { lane: undefined, elsewhere: name }
97
+ }
98
+
99
+ function wait_of(marker: WaitMarker, now: number, home_root: string): Array<BudgetWait> {
100
+ const { command, wait_reason: reason } = marker
101
+
102
+ if (reason === undefined || marker.admitted_at !== undefined) return []
103
+
104
+ return [{ command, ...place_of(marker, home_root), waited_ms: now - marker.claimed_at, reason }]
105
+ }
106
+
107
+ // The claims still waiting, longest first, placed against the board's checkout `home`.
108
+ function waits_of(
109
+ entries: ReadonlyArray<{ reservation: WaitMarker }>,
110
+ now: number,
111
+ home: string,
112
+ ): Array<BudgetWait> {
113
+ const home_root = lanes_root_of(home)
114
+
115
+ return entries
116
+ .flatMap((entry) => wait_of(entry.reservation, now, home_root))
117
+ .toSorted((left, right) => right.waited_ms - left.waited_ms)
118
+ }
119
+
120
+ const core_wait = {
121
+ REASON_TEXT,
122
+ WAIT_REASONS,
123
+ wait_line,
124
+ wait_reason,
125
+ waits_of,
126
+ where_of,
127
+ }
128
+
129
+ export type { BudgetWait, WaitReason }
130
+ export { core_wait }
@@ -0,0 +1,49 @@
1
+ import { availableParallelism } from 'node:os'
2
+ import { core_admission } from './core-admission'
3
+ import { core_budget, type LiveReservation } from './core-budget'
4
+ import { held_mark } from './held-mark'
5
+ import { machine_capacity, type MachineReading } from './machine-capacity'
6
+
7
+ // The cores a unit suite may open workers on: the core count less the load from outside the core
8
+ // budget's ledger, the figure the ledger admits against (`machine_capacity.machine_budget`). The run
9
+ // count in `unit-worker-share.ts` divides the machine between unit runs; this is what bounds that
10
+ // division when something else holds the cores.
11
+ //
12
+ // Its own module because the reading needs the ledger, and `core-budget.ts` already imports
13
+ // `unit-worker-share.ts` — reading it from there would close a cycle.
14
+
15
+ interface FreeCoresOptions {
16
+ available_cores?: number
17
+ read_machine?: () => Promise<MachineReading>
18
+ directory?: string
19
+ }
20
+
21
+ // **The claim a parent command holds for this run is left out of the ledger.** It is admitted before
22
+ // this run starts a worker, so none of its weight is in the reading yet; counting it would subtract
23
+ // it from load it never added and mask that much outside load (`held_mark.held_reservation`).
24
+ function running_claims(directory: string | undefined): Array<LiveReservation> {
25
+ const own = held_mark.held_reservation()
26
+
27
+ return core_budget.live_reservations(directory).filter((entry) => entry.key !== own)
28
+ }
29
+
30
+ // `undefined` where the machine could not be read — a CI runner, a CPU quota narrower than the host —
31
+ // which `unit_worker_share.cap_to_free` reads as the share as it was.
32
+ async function read(options: FreeCoresOptions = {}): Promise<number | undefined> {
33
+ const reading = await (options.read_machine ?? machine_capacity.read_machine)()
34
+
35
+ if (reading.busy_cores === undefined) return undefined
36
+
37
+ const ledger = core_admission.ledger_load(running_claims(options.directory), Date.now())
38
+
39
+ return machine_capacity.machine_budget(
40
+ options.available_cores ?? availableParallelism(),
41
+ reading,
42
+ ledger,
43
+ ).cores
44
+ }
45
+
46
+ const free_cores = { read }
47
+
48
+ export type { FreeCoresOptions }
49
+ export { free_cores }
@@ -1,9 +1,9 @@
1
1
  import { process_identity } from '#scripts/josh/process-identity'
2
- import { lane_ledger } from '#scripts/lane/lane-ledger'
2
+ import { lane_ledger, type GateCheckRecord } from '#scripts/lane/lane-ledger'
3
3
  import { core_admission } from './core-admission'
4
4
  import { core_budget } from './core-budget'
5
5
  import { gate_plan } from './gate-plan'
6
- import type { GateStepResult } from './gate-report'
6
+ import { gate_report, type GateStepResult } from './gate-report'
7
7
  import { machine_capacity } from './machine-capacity'
8
8
 
9
9
  // A finished gate's line in the lane ledger: its duration, its verdict, its
@@ -88,6 +88,15 @@ async function start(ledger_path: string | undefined): Promise<GateLedgerStart>
88
88
  return { ledger_path, readings, sampler: start_sampler(readings) }
89
89
  }
90
90
 
91
+ function check_records(results: ReadonlyArray<GateStepResult>): Array<GateCheckRecord> {
92
+ return results.map((result) => ({
93
+ label: result.label,
94
+ elapsed_ms: result.elapsed_ms,
95
+ is_passed: !gate_report.is_gate_step_failed(result),
96
+ is_reused: result.reused_at !== undefined,
97
+ }))
98
+ }
99
+
91
100
  // The caller measures the elapsed time before this, so the end's reading is never part of it.
92
101
  async function record(
93
102
  begun: GateLedgerStart,
@@ -101,7 +110,12 @@ async function record(
101
110
  const unit_ms = results.find((result) => result.label === gate_plan.UNIT_LABEL)?.elapsed_ms
102
111
  const external_cores = peak([...begun.readings, await read_external_cores()])
103
112
 
104
- await lane_ledger.record_gate(begun.ledger_path, { ...outcome, unit_ms, external_cores })
113
+ await lane_ledger.record_gate(begun.ledger_path, {
114
+ ...outcome,
115
+ unit_ms,
116
+ external_cores,
117
+ checks: check_records(results),
118
+ })
105
119
  }
106
120
 
107
121
  const gate_ledger = {
@@ -51,6 +51,7 @@ const TYPE_CHECK_LABEL = 'check'
51
51
  const UNIT_LABEL = 'test:unit'
52
52
  const BEHAVIOR_LABEL = 'behavior'
53
53
  const METRICS_LABEL = 'metrics'
54
+ const EXPORTS_LABEL = 'exports'
54
55
 
55
56
  // The checks whose warnings must survive the green record: eslint (the `lint` step) and svelte-check
56
57
  // (the `check` step) both exit 0 while reporting per-tree warnings a developer needs to see again, so
@@ -106,7 +107,7 @@ const STATIC_CHECKS: ReadonlyArray<GateCheck> = [
106
107
  // program and walks it for about 5s, and it is over well before lint and the type check. A
107
108
  // reservation would only take a place from the four-core CI runner's fan-out and a worker from the
108
109
  // unit suite for the whole gate.
109
- { label: 'exports', target: 'exports:unused', reserved_cores: 0 },
110
+ { label: EXPORTS_LABEL, target: 'exports:unused', reserved_cores: 0 },
110
111
  // **The metrics ratchet is kit's own**: it holds kit's repository-wide
111
112
  // totals to the merge-base's. It is one in-process eslint pass with only `max-lines` enabled, over
112
113
  // in a few seconds, so it reserves no core for the reason the unused-member check above does not.
@@ -397,6 +398,25 @@ function resolve_gate_plan(
397
398
  }
398
399
  }
399
400
 
401
+ // The unit cap bounded by the cores free of load from outside the core budget's ledger
402
+ // (`free-cores.ts`), so the run count's share is never wider than what is free. A plan with no unit
403
+ // step carries no cap, and an unread machine leaves the cap as resolved (`unit_worker_share.cap_to_free`).
404
+ function bound_by_free(
405
+ plan: GatePlan,
406
+ available_cores: number,
407
+ free_cores: number | undefined,
408
+ ): GatePlan {
409
+ if (!has_unit_check(plan.checks)) return plan
410
+
411
+ const unit_worker_cap = unit_worker_share.cap_to_free(
412
+ plan.unit_worker_cap,
413
+ free_cores,
414
+ available_cores,
415
+ )
416
+
417
+ return { ...plan, unit_worker_cap }
418
+ }
419
+
400
420
  // Printed once per run, so a gate that was slow on someone else's machine can be read without
401
421
  // re-deriving the plan from their core count.
402
422
  //
@@ -440,6 +460,7 @@ function format_gate_plan(
440
460
  }
441
461
 
442
462
  const gate_plan = {
463
+ EXPORTS_LABEL,
443
464
  GATE_CHECKS,
444
465
  LINT_LABEL,
445
466
  MEASURED_CORES,
@@ -449,6 +470,7 @@ const gate_plan = {
449
470
  TYPE_CHECK_LABEL,
450
471
  UNIT_LABEL,
451
472
  WARNING_CHECKER_LABELS,
473
+ bound_by_free,
452
474
  check_memory,
453
475
  check_weight,
454
476
  direct_unit_memory,
@@ -28,6 +28,9 @@ interface GateStepResult extends BufferedProcessResult {
28
28
  command: string
29
29
  // Set when the check started past the machine-wide budget at the wait cap.
30
30
  budget_note?: string | undefined
31
+ // Set when the check was not started because its inputs match the record taken at this time
32
+ // (`check-reuse.ts`).
33
+ reused_at?: string | undefined
31
34
  }
32
35
 
33
36
  function is_gate_step_failed(result: GateStepResult): boolean {
@@ -98,6 +101,12 @@ function is_unverified_skip(result: GateStepResult): boolean {
98
101
  )
99
102
  }
100
103
 
104
+ // Whether a passing check said nothing a green record would hide — the condition every record the
105
+ // gate writes is withheld on (`verification-gate.ts` → `record_green_gate`, `check-reuse.ts`).
106
+ function has_nothing_to_say(result: GateStepResult): boolean {
107
+ return !is_unverified_skip(result) && !has_checker_warning(result)
108
+ }
109
+
101
110
  // **A check the verdict counts as skipped said nothing but that it checked nothing.** `is_skip_notice`
102
111
  // asks whether a check carries a skip notice, which is the right question for printing its body; it is
103
112
  // the wrong one for a count, twice over. A check can skip half of
@@ -154,8 +163,12 @@ function format_seconds(elapsed_ms: number): string {
154
163
  // and the log exists precisely to be read when the console's copy has been elided.
155
164
  function gate_step_header(result: GateStepResult): string {
156
165
  const icon = is_gate_step_failed(result) ? FAIL_ICON : PASS_ICON
166
+ const timing =
167
+ result.reused_at === undefined
168
+ ? format_seconds(result.elapsed_ms)
169
+ : `reused — inputs unchanged since it passed at ${result.reused_at}`
157
170
 
158
- return `${icon} ${result.label} (pnpm ${result.command}) ${format_seconds(result.elapsed_ms)}`
171
+ return `${icon} ${result.label} (pnpm ${result.command}) ${timing}`
159
172
  }
160
173
 
161
174
  function print_gate_step(result: GateStepResult, is_verbose: boolean): void {
@@ -267,8 +280,8 @@ const gate_report = {
267
280
  format_budget_notes,
268
281
  format_failure_actions,
269
282
  has_checker_warning,
283
+ has_nothing_to_say,
270
284
  is_gate_step_failed,
271
- is_unverified_skip,
272
285
  report_gate_steps,
273
286
  }
274
287