@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
@@ -2,11 +2,13 @@ import {
2
2
  ENV_FILE_NAME,
3
3
  cost_transcript,
4
4
  json_value,
5
+ lane_paths,
6
+ pr_raw_schema,
5
7
  process_identity,
6
8
  process_owner_schema,
7
9
  run_carry,
8
10
  time_instant
9
- } from "./chunk-3C4XRYG6.js";
11
+ } from "./chunk-APUO6U4J.js";
10
12
  import {
11
13
  PLATFORM_TEMP_ROOT,
12
14
  PROBE_TIMEOUT_MS,
@@ -100,1014 +102,438 @@ var COMMAND_PHASES = /* @__PURE__ */ new Map([
100
102
  ["josh followup", MERGE_PHASE]
101
103
  ]);
102
104
 
103
- // scripts/josh/josh-command-types.ts
104
- var OPTIONAL_ENV_FILE_FLAGS = [`--env-file-if-exists=${ENV_FILE_NAME}`];
105
- var GATE_COMMAND2 = "gate";
106
- var ESLINT_CACHE_FILE = ".eslintcache";
107
- var TS_BUILD_INFO_FILE = ".tsbuildinfo";
108
- var CSPELL_CACHE_FILE = ".cspellcache";
109
- var ESLINT_EDIT_CACHE_FILE = ".eslintcache.edit";
110
- var ESLINT_RELATED_CACHE_FILE = ".eslintcache.related";
111
- var ROOTED_JSON_PORTABILITY = "rooted-json";
112
- var ESLINT_CACHE_SPEC = {
113
- cache_file: ESLINT_CACHE_FILE,
114
- portability: ROOTED_JSON_PORTABILITY
115
- };
116
- var TS_CACHE_SPEC = {
117
- cache_file: TS_BUILD_INFO_FILE,
118
- portability: "raw"
119
- };
120
- var CSPELL_CACHE_SPEC = {
121
- cache_file: CSPELL_CACHE_FILE,
122
- portability: "raw"
123
- };
124
- var GATE_CACHE_SPECS = [
125
- ESLINT_CACHE_SPEC,
126
- TS_CACHE_SPEC,
127
- CSPELL_CACHE_SPEC
128
- ];
129
- var GATE_CACHE_FILES = GATE_CACHE_SPECS.map((spec) => spec.cache_file);
130
- var IGNORED_CACHE_FILES = [
131
- ...GATE_CACHE_FILES,
132
- ESLINT_EDIT_CACHE_FILE,
133
- ESLINT_RELATED_CACHE_FILE
134
- ];
135
- var CONTENT_CACHE_FLAGS = ["--cache", "--cache-strategy", "content"];
136
- var CACHE_LOCATION_FLAG = "--cache-location";
137
- function content_cache_flags(cache_file) {
138
- return [...CONTENT_CACHE_FLAGS, CACHE_LOCATION_FLAG, cache_file];
139
- }
140
- var ESLINT_CACHE_FLAGS = content_cache_flags(ESLINT_CACHE_FILE);
141
- var ESLINT_EDIT_CACHE_FLAGS = content_cache_flags(ESLINT_EDIT_CACHE_FILE);
142
- var ESLINT_RELATED_CACHE_FLAGS = content_cache_flags(ESLINT_RELATED_CACHE_FILE);
143
- var CSPELL_CACHE_FLAGS = content_cache_flags(CSPELL_CACHE_FILE);
105
+ // scripts/gate/core-budget.ts
106
+ import { randomUUID } from "node:crypto";
107
+ import { readdirSync as readdirSync2 } from "node:fs";
108
+ import { availableParallelism as availableParallelism3 } from "node:os";
109
+ import path3 from "node:path";
144
110
 
145
- // scripts/josh/josh-commands-backlog.ts
146
- var BACKLOG_COMMANDS = {
147
- // Started from a terminal: the `backlogrun` keyword's launcher, and the board.
148
- backlogrun: {
149
- script: "scripts/backlog/backlogrun-cli.ts",
150
- description: "Start a backlogrun in the background and show its board; with one already running, only show the board, or replace it when it is idle and the invocation differs; --stop stops it",
151
- category: "AI tools",
152
- reference: [
153
- "[<n>...] [--only] [--max <n>] [--idle <minutes>] [--agent claude|codex] | --stop",
154
- "developer",
155
- ["files", "network", "processes"]
156
- ]
157
- },
158
- "backlog:next": {
159
- script: "scripts/backlog/backlog-next.ts",
160
- description: "Order the whole opted-in backlog: auto-ok issues and the descendants of auto-ok epics, transitively through nested epics",
161
- category: "AI tools",
162
- reference: ["[--exclude <n>[,<n>...]] [--repo <owner/repo>]", "automation", ["network"]]
163
- },
164
- "backlog:offer": {
165
- script: "scripts/backlog/backlog-offer-cli.ts",
166
- description: "Collapse a backlogrun loop-head event into one call: read backlog:next, ask backlog:budget, return the verdict and any issues to start",
167
- category: "AI tools",
168
- reference: ["[options]", "automation", ["files", "network", "processes"]]
169
- },
170
- "backlog:drive": {
171
- script: "scripts/backlog/backlog-drive-cli.ts",
172
- description: "Drive backlogrun through offer, launch and merge; restore this run\u2019s lanes and report when complete",
173
- category: "AI tools",
174
- reference: ["--owner <pid> [options]", "automation", ["network"]]
175
- },
176
- "backlog:plan": {
177
- script: "scripts/backlog/backlog-plan-cli.ts",
178
- description: "Print the whole backlog as a plan: ready now, waiting on what, waiting on a person, out of scope",
179
- category: "AI tools",
180
- reference: [
181
- "[issue...] [--only] [--waves] [--exclude <n>[,<n>...]]",
182
- "automation",
183
- ["network"]
184
- ]
185
- },
186
- "backlog:stalled": {
187
- script: "scripts/backlog/backlog-stalled-cli.ts",
188
- // A stall sends one Telegram warning. The `Stop` hook calls the detector
189
- // directly, so this flag costs only the manual command its in-process dispatch.
190
- tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
191
- description: "Report whether ready backlog work is sitting undispatched with a free lane and no recent dispatch",
192
- category: "AI tools",
193
- reference: ["", "automation", ["network"]]
194
- },
195
- "backlog:budget": {
196
- script: "scripts/backlog/backlog-budget-cli.ts",
197
- description: "Say whether a backlogrun may start more work, keep watching, or finish",
198
- category: "AI tools",
199
- reference: ["[options]", "automation", ["none"]]
111
+ // scripts/test/unit-worker-share.ts
112
+ import { readdirSync } from "node:fs";
113
+ import { availableParallelism } from "node:os";
114
+ import path from "node:path";
115
+ var RUN_PREFIX = "josh-unit-running-";
116
+ var WORKER_FLAG = "--maxWorkers";
117
+ var WORKER_FLAGS = [WORKER_FLAG, "--max-workers"];
118
+ var MIN_WORKERS = 1;
119
+ var SOLO_RUNS = 1;
120
+ function marker_path(pid = process.pid) {
121
+ return stamp_file.stamp_path(RUN_PREFIX, String(pid));
122
+ }
123
+ function is_running(marker) {
124
+ return process_identity.is_same_process(marker.pid, marker.process_start) !== false;
125
+ }
126
+ function read_marker(source) {
127
+ const raw = stamp_file.read_stamp_text(source);
128
+ if (raw === void 0) return void 0;
129
+ const parsed = process_owner_schema.safeParse(json_value.parse_or_undefined(raw));
130
+ return parsed.success ? parsed.data : void 0;
131
+ }
132
+ function marker_files(directory = PLATFORM_TEMP_ROOT) {
133
+ try {
134
+ return readdirSync(directory).filter((name) => name.startsWith(RUN_PREFIX));
135
+ } catch {
136
+ return [];
200
137
  }
201
- };
202
-
203
- // scripts/josh/josh-commands-document.ts
204
- var DOCUMENT_COMMANDS = {
205
- "doc:section": {
206
- script: "scripts/document/document-section-cli.ts",
207
- description: 'Print one section of a markdown document, for a `file.md` \u2192 "Heading" reference',
208
- category: "AI tools",
209
- reference: ["<file> <heading>", "automation", ["none"]],
210
- is_bundled: true
211
- },
212
- "read:set": {
213
- script: "scripts/document/read-set-cli.ts",
214
- description: "Print what an entry point reads before it starts, and what that read costs",
215
- category: "AI tools",
216
- reference: ["[<entry>] [--json]", "automation", ["none"]]
217
- },
218
- "doc:read": {
219
- script: "scripts/document/document-read-cli.ts",
220
- description: "Read a whole document safely: print it, or point at the Read tool when over the Bash cap",
221
- category: "AI tools",
222
- reference: ["<file>", "automation", ["none"]]
223
- },
224
- "read:files": {
225
- script: "scripts/document/read-files-cli.ts",
226
- description: "Read several files in one call so edit targets fold into one turn",
227
- category: "AI tools",
228
- reference: ["<path> [<path> ...]", "automation", ["none"]]
229
- },
230
- "edit:files": {
231
- script: "scripts/document/edit-files-cli.ts",
232
- description: "Apply several content-addressed edits from a plan in one call",
233
- category: "AI tools",
234
- reference: ["<plan-path | ->", "automation", ["none"]]
138
+ }
139
+ function sweep_marker(source) {
140
+ try {
141
+ stamp_file.remove_stamp(source);
142
+ } catch {
235
143
  }
236
- };
237
-
238
- // scripts/josh/josh-commands-guard.ts
239
- var GUARD_COMMANDS = {
240
- "investigation:guard": {
241
- script: "scripts/delegation/investigation-guard.ts",
242
- description: "Claude Code hook: refuse a read once the unedited-read threshold is reached again (reads the tool call on stdin)",
243
- category: "AI tools",
244
- reference: ["", "automation", ["none"]]
245
- },
246
- "duplicate-read:guard": {
247
- script: "scripts/delegation/duplicate-read-guard.ts",
248
- description: "Claude Code hook: refuse a second whole-file read of a path whose content has not changed since the run last read it (reads the tool call on stdin)",
249
- category: "AI tools",
250
- reference: ["", "automation", ["none"]]
251
- },
252
- "rule:guard": {
253
- script: "scripts/rules/rule-guard.ts",
254
- description: "Claude Code hook: deliver a trigger-delivered rule at the call that binds it (reads the tool call on stdin)",
255
- category: "AI tools",
256
- reference: ["", "automation", ["none"]]
144
+ }
145
+ function is_marker_live(source) {
146
+ const marker = read_marker(source);
147
+ if (marker === void 0) return false;
148
+ if (is_running(marker)) return true;
149
+ sweep_marker(source);
150
+ return false;
151
+ }
152
+ function live_run_count(directory = PLATFORM_TEMP_ROOT) {
153
+ return marker_files(directory).filter((name) => is_marker_live(path.join(directory, name))).length;
154
+ }
155
+ var NESTED_KEY = "JOSH_UNIT_RUN_MARKED";
156
+ var NESTED_VALUE = "1";
157
+ function is_nested_run() {
158
+ return process.env[NESTED_KEY] === NESTED_VALUE;
159
+ }
160
+ function resolve_unit_workers(available_cores, runs2) {
161
+ if (runs2 <= SOLO_RUNS) return void 0;
162
+ return Math.max(MIN_WORKERS, Math.floor(available_cores / runs2));
163
+ }
164
+ function cap_to_free(workers, free_cores, available_cores) {
165
+ if (free_cores === void 0 || free_cores >= (workers ?? available_cores)) return workers;
166
+ return Math.max(MIN_WORKERS, Math.floor(free_cores));
167
+ }
168
+ function current_share(available_cores = availableParallelism(), live_runs = live_run_count(), free_cores) {
169
+ const own_run2 = is_nested_run() ? 0 : SOLO_RUNS;
170
+ const share = resolve_unit_workers(available_cores, live_runs + own_run2);
171
+ return cap_to_free(share, free_cores, available_cores);
172
+ }
173
+ function is_worker_flag(argument) {
174
+ return WORKER_FLAGS.some((flag) => argument === flag || argument.startsWith(`${flag}=`));
175
+ }
176
+ function has_worker_flag(vitest_arguments) {
177
+ return vitest_arguments.some((argument) => is_worker_flag(argument));
178
+ }
179
+ function worker_arguments(vitest_arguments, share) {
180
+ if (share === void 0 || has_worker_flag(vitest_arguments)) return [];
181
+ return [`${WORKER_FLAG}=${String(share)}`];
182
+ }
183
+ function mark_run_started(target2) {
184
+ try {
185
+ stamp_file.write_stamp(target2, process_identity.own_fields());
186
+ } catch {
187
+ }
188
+ }
189
+ async function with_run_marker(run) {
190
+ if (is_nested_run()) return await run();
191
+ const target2 = marker_path();
192
+ mark_run_started(target2);
193
+ process.env[NESTED_KEY] = NESTED_VALUE;
194
+ try {
195
+ return await run();
196
+ } finally {
197
+ process.env[NESTED_KEY] = "";
198
+ stamp_file.remove_stamp(target2);
257
199
  }
200
+ }
201
+ var unit_worker_share = {
202
+ MIN_WORKERS,
203
+ NESTED_KEY,
204
+ NESTED_VALUE,
205
+ RUN_PREFIX,
206
+ SOLO_RUNS,
207
+ cap_to_free,
208
+ current_share,
209
+ has_worker_flag,
210
+ is_nested_run,
211
+ live_run_count,
212
+ marker_path,
213
+ read_marker,
214
+ resolve_unit_workers,
215
+ with_run_marker,
216
+ worker_arguments
258
217
  };
259
218
 
260
- // scripts/josh/josh-commands-issue.ts
261
- var ISSUES_WITH_REPO = "<issue...> [--repo <owner/repo>]";
262
- var ISSUE_COMMANDS = {
263
- "issue:ack": {
264
- script: "scripts/issue/issue-ack-cli.ts",
265
- description: "Record that the run read a person's update to its issue, printing the update it acknowledges",
266
- category: "AI tools",
267
- reference: ["<issue>", "automation", ["network"]]
268
- },
269
- "issue:read": {
270
- script: "scripts/issue/issue-read-cli.ts",
271
- description: "Print each issue's title, body and every comment on it, in one call",
272
- category: "AI tools",
273
- reference: ["<issue...>", "automation", ["network"]]
274
- },
275
- "issue:state": {
276
- script: "scripts/issue/issue-state-cli.ts",
277
- description: "Print each issue's state and labels, in the spelling the documents compare against",
278
- category: "AI tools",
279
- reference: [ISSUES_WITH_REPO, "automation", ["network"]]
280
- },
281
- "issue:scout": {
282
- script: "scripts/issue/issue-scout-cli.ts",
283
- description: "Before filing: say whether an issue like this exists and which epic it belongs to",
284
- category: "AI tools",
285
- reference: ["<title> [--body <summary> | --body-file <path>]", "automation", ["network"]]
286
- },
287
- "defect:rate": {
288
- script: "scripts/issue/defect-rate-cli.ts",
289
- description: "Print the defect rate of merged work: defects filed per enhancement completed",
290
- category: "AI tools",
291
- reference: ["[--days <n>]", "automation", ["network"]]
292
- },
293
- "issue:fold": {
294
- script: "scripts/issue/issue-fold-cli.ts",
295
- description: "Before a second filing: say whether findings from this session fold into one issue",
296
- category: "AI tools",
297
- reference: ["<title>... [--not-separable] [--json]", "automation", ["none"]]
298
- },
299
- "issue:fold-existing": {
300
- script: "scripts/issue/issue-fold-existing-cli.ts",
301
- description: "Assess whether a complete draft can join an unstarted issue",
302
- category: "AI tools",
303
- reference: ["<assessment.json> [--json]", "automation", ["none"]]
304
- },
305
- "issue:cite": {
306
- script: "scripts/issue/issue-cite-cli.ts",
307
- description: "Print the paste-ready number-link citation line for each issue, in one call",
308
- category: "AI tools",
309
- reference: [ISSUES_WITH_REPO, "automation", ["network"]]
310
- },
311
- "issue:comment": {
312
- script: "scripts/issue/issue-comment-cli.ts",
313
- description: "Post one comment to an issue from a file, so no shell expands the body",
314
- category: "AI tools",
315
- reference: ["<issue> --body <text> | --body-file <path>", "automation", ["network"]]
316
- },
317
- "issue:file": {
318
- script: "scripts/issue/issue-file-cli.ts",
319
- description: "File an Issue with every filing step: lint, Origin, fold, WIP cap, duplicate scout, labels, then epic:bundle",
320
- category: "AI tools",
321
- reference: [
322
- "<title> --body-file <path> --depth <0|1|2> [--route <route>] [--label <name>] [--repo <owner/repo>] [--distinct <N,\u2026>] [--over-cap] [--no-auto-ok] [--release]",
323
- "automation",
324
- ["network"]
325
- ]
326
- },
327
- "issue:release": {
328
- script: "scripts/issue/issue-release-cli.ts",
329
- description: "Link an Issue to the repository's open release Issue as its blocker (or, with --wait, as waiting on it), filing one when none is open",
330
- category: "AI tools",
331
- reference: ["<issue> [--wait]", "automation", ["network"]]
219
+ // scripts/gate/core-budget.ts
220
+ import { z as z2 } from "zod";
221
+
222
+ // scripts/gate/core-admission.ts
223
+ var MS_PER_SECOND = 1e3;
224
+ var RAMP_UP_MS = 3e4;
225
+ function compare_claim(left, right) {
226
+ const by_time = left.reservation.claimed_at - right.reservation.claimed_at;
227
+ return by_time === 0 ? left.key.localeCompare(right.key) : by_time;
228
+ }
229
+ function add_load(totals, reservation) {
230
+ totals.cores += reservation.weight;
231
+ totals.memory_mb += reservation.memory_mb ?? 0;
232
+ }
233
+ function exceeds(totals, budget) {
234
+ return totals.cores > budget.cores || totals.memory_mb > budget.memory_mb;
235
+ }
236
+ function admitted_keys(reservations, budget, memory_budget = Infinity) {
237
+ const limit = { cores: budget, memory_mb: memory_budget };
238
+ const totals = { cores: 0, memory_mb: 0 };
239
+ const admitted = /* @__PURE__ */ new Set();
240
+ for (const [index, entry] of reservations.toSorted(compare_claim).entries()) {
241
+ add_load(totals, entry.reservation);
242
+ if (index !== 0 && exceeds(totals, limit)) break;
243
+ admitted.add(entry.key);
332
244
  }
245
+ return admitted;
246
+ }
247
+ function is_admitted(key, reservations, budget) {
248
+ return admitted_keys(reservations, budget.cores, budget.memory_mb).has(key);
249
+ }
250
+ function sum_load(reservations) {
251
+ const load = { cores: 0, memory_mb: 0 };
252
+ for (const entry of reservations) add_load(load, entry.reservation);
253
+ return load;
254
+ }
255
+ function is_ramped(reservation, now) {
256
+ const { admitted_at } = reservation;
257
+ return admitted_at !== void 0 && now - admitted_at >= RAMP_UP_MS;
258
+ }
259
+ function ledger_load(reservations, now) {
260
+ const admitted = reservations.filter((entry) => entry.reservation.admitted_at !== void 0);
261
+ const ramped = admitted.filter((entry) => is_ramped(entry.reservation, now));
262
+ return { cores: sum_load(admitted).cores, memory_mb: sum_load(ramped).memory_mb };
263
+ }
264
+ function total_weight(reservations) {
265
+ return sum_load(reservations).cores;
266
+ }
267
+ function admitted_load(reservations, budget) {
268
+ const admitted = admitted_keys(reservations, budget);
269
+ return total_weight(reservations.filter((entry) => admitted.has(entry.key)));
270
+ }
271
+ function describe_memory(ledger, budget) {
272
+ if (!Number.isFinite(budget.memory_mb)) return "";
273
+ return ` and ${String(Math.round(ledger.memory_mb))} MB of memory against ${String(Math.round(budget.memory_mb))} MB`;
274
+ }
275
+ function describe_overflow(admission) {
276
+ const { overflow } = admission;
277
+ if (overflow === void 0) return void 0;
278
+ const { ledger, budget } = overflow;
279
+ const waited = String(Math.round(overflow.waited_ms / MS_PER_SECOND));
280
+ const cores = `the ledger held ${String(ledger.cores)} cores against a budget of ${String(budget.cores)}`;
281
+ return `started past the core budget after ${waited}s \u2014 ${cores}${describe_memory(ledger, budget)}`;
282
+ }
283
+ var core_admission = {
284
+ RAMP_UP_MS,
285
+ admitted_keys,
286
+ admitted_load,
287
+ describe_overflow,
288
+ is_admitted,
289
+ ledger_load,
290
+ sum_load
333
291
  };
334
292
 
335
- // scripts/josh/josh-commands-lane.ts
336
- var LANE_SCRIPT = "scripts/lane/lane-cli.ts";
337
- var LANE_ARGUMENTS = { script: LANE_SCRIPT, tsx_arguments: OPTIONAL_ENV_FILE_FLAGS };
338
- var ISSUE_WITH_OPTIONS = "<issue> [options]";
339
- var MEASURE_ARGUMENTS = {
340
- script: "scripts/lane/lane-measure-cli.ts",
341
- tsx_arguments: OPTIONAL_ENV_FILE_FLAGS
293
+ // scripts/gate/core-wait.ts
294
+ import path2 from "node:path";
295
+ var WAIT_REASONS = ["cores", "memory", "pressure"];
296
+ var REASON_TEXT = {
297
+ cores: "not enough free cores",
298
+ memory: "not enough free memory",
299
+ pressure: "memory under pressure"
342
300
  };
343
- var LANE_COMMANDS = {
344
- "lane:open": {
345
- ...LANE_ARGUMENTS,
346
- description: "Open a lane: a linked work tree with its own branch and its own port seed",
347
- category: "AI tools",
348
- reference: [ISSUE_WITH_OPTIONS, "automation", ["files", "git"]],
349
- default_script_arguments: ["open"]
350
- },
351
- "lane:close": {
352
- ...LANE_ARGUMENTS,
353
- description: "Close a lane, leaving no work tree, branch or directory behind",
354
- category: "AI tools",
355
- reference: ["<issue>", "automation", ["files", "git"]],
356
- default_script_arguments: ["close"]
357
- },
358
- "lane:list": {
359
- ...LANE_ARGUMENTS,
360
- description: "List the open lanes: which issue, which ports, and where each one is",
361
- category: "AI tools",
362
- reference: ["", "automation", ["none"]],
363
- default_script_arguments: ["list"]
364
- },
365
- "lane:prune": {
366
- ...LANE_ARGUMENTS,
367
- description: "Close every lane an interruption left without its work tree and every spent lane, then sweep unregistered leftovers under the lanes root",
368
- category: "AI tools",
369
- reference: ["", "automation", ["files", "git"]],
370
- default_script_arguments: ["prune"]
371
- },
372
- "lane:output": {
373
- ...LANE_ARGUMENTS,
374
- description: "Record, or read back, where the unit running a lane\u2019s child writes",
375
- category: "AI tools",
376
- reference: ["<issue> [path]", "automation", ["files"]],
377
- default_script_arguments: ["output"]
378
- },
379
- "lane:dispatch": {
380
- ...LANE_ARGUMENTS,
381
- description: "Start a lane\u2019s child as a detached process that outlives this session",
382
- category: "AI tools",
383
- reference: ["<issue> <prompt>", "automation", ["processes"]],
384
- default_script_arguments: ["dispatch"]
385
- },
386
- "lane:await": {
387
- ...LANE_ARGUMENTS,
388
- description: "Block until any of the named in-flight lane children completes; prints which one finished",
389
- category: "AI tools",
390
- reference: ["<issue> [<issue>...] [--owner <pid>]", "automation", ["files", "processes"]],
391
- default_script_arguments: ["await"]
392
- },
393
- "lane:launch": {
394
- script: "scripts/lane/lane-launch-cli.ts",
395
- description: "Collapse a backlogrun lane-start event into one call: open the lane, pop and re-install on the first, dispatch the child",
396
- category: "AI tools",
397
- reference: ["<issue> [--stash <message>]", "automation", ["files", "git", "processes"]]
398
- },
399
- // `.env` is read for `JOSH_LANE_ROOT`, as every lane command reads it, so the lane is found.
400
- "lane:restart": {
401
- script: "scripts/lane/lane-restart-cli.ts",
402
- tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
403
- description: "Stop a running lane child, stash its uncommitted work and re-dispatch its issue in a fresh session \u2014 not counted as an outage or a failure",
404
- category: "AI tools",
405
- reference: ["<issue>", "developer", ["files", "git", "processes", "network"]]
406
- },
407
- // A live run's lane limit: `.env` is read so the bare form reports the
408
- // `JOSH_LANE_LIMIT` a person set there when no override stands.
409
- "lane:limit": {
410
- script: "scripts/lane/lane-limit-cli.ts",
411
- tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
412
- description: "Change a live backlogrun\u2019s lane limit without stopping it, or print the limit, lanes in use and free lanes",
413
- category: "AI tools",
414
- reference: ["[<limit>] [--reset]", "developer", ["files"]]
415
- },
416
- // The lane-limit measurement: `.env` is read so `lane:stats` labels the row
417
- // with the `JOSH_LANE_LIMIT` a person set there.
418
- "lane:sample": {
419
- ...MEASURE_ARGUMENTS,
420
- description: "Record the machine load (load average, swap, free memory, working lanes) to the lane ledger \u2014 a backlogrun samples itself, this is for a reading outside one",
421
- category: "AI tools",
422
- reference: ["[--every <seconds>]", "maintainer", ["files"]],
423
- default_script_arguments: ["sample"]
424
- },
425
- "lane:stats": {
426
- ...MEASURE_ARGUMENTS,
427
- description: "Print one table row of lane throughput, gate duration and machine load over a period, the per-stage durations of a lane under it, and the wait after each ship stop reason under those",
428
- category: "AI tools",
429
- reference: ["--period <days> [--limit <lane-limit>]", "maintainer", ["none"]],
430
- default_script_arguments: ["stats"]
301
+ function wait_reason(key, reservations, budget) {
302
+ const cores_only = { cores: budget.cores, memory_mb: Infinity };
303
+ if (!core_admission.is_admitted(key, reservations, cores_only)) return "cores";
304
+ return budget.memory_pressured === true ? "pressure" : "memory";
305
+ }
306
+ function wait_line(command, reason) {
307
+ return `\u23F3 ${command ?? "a step"} waits for the core budget \u2014 ${REASON_TEXT[reason]}`;
308
+ }
309
+ function lanes_root_of(checkout) {
310
+ if (lane_paths.lane_issue_of(checkout) === void 0) return lane_paths.lane_root(checkout);
311
+ return path2.dirname(path2.resolve(checkout));
312
+ }
313
+ function where_of(checkout) {
314
+ return { checkout, lane: lane_paths.lane_issue_of(checkout), lanes_root: lanes_root_of(checkout) };
315
+ }
316
+ function place_of(marker, home_root) {
317
+ const { checkout, lane, lanes_root } = marker;
318
+ if (checkout === void 0 || lanes_root === void 0 || lanes_root === home_root) {
319
+ return { lane, elsewhere: void 0 };
431
320
  }
321
+ const name = lane === void 0 ? path2.basename(checkout) : `${path2.basename(lanes_root)}/${lane}`;
322
+ return { lane: void 0, elsewhere: name };
323
+ }
324
+ function wait_of(marker, now, home_root) {
325
+ const { command, wait_reason: reason } = marker;
326
+ if (reason === void 0 || marker.admitted_at !== void 0) return [];
327
+ return [{ command, ...place_of(marker, home_root), waited_ms: now - marker.claimed_at, reason }];
328
+ }
329
+ function waits_of(entries, now, home) {
330
+ const home_root = lanes_root_of(home);
331
+ return entries.flatMap((entry) => wait_of(entry.reservation, now, home_root)).toSorted((left, right) => right.waited_ms - left.waited_ms);
332
+ }
333
+ var core_wait = {
334
+ REASON_TEXT,
335
+ WAIT_REASONS,
336
+ wait_line,
337
+ wait_reason,
338
+ waits_of,
339
+ where_of
432
340
  };
433
341
 
434
- // scripts/josh/josh-commands-run.ts
435
- var RUN_HOLD_SCRIPT = "scripts/run/hold/run-hold-cli.ts";
436
- var RUN_COMMANDS = {
437
- "run:hold": {
438
- script: RUN_HOLD_SCRIPT,
439
- description: "Claim this working tree for a run, or say which run already holds it",
440
- category: "AI tools",
441
- reference: ["[<issue> [--fullrun | --halfrun-stop | --prrun-stop]]", "automation", ["files"]]
442
- },
443
- "run:release": {
444
- script: RUN_HOLD_SCRIPT,
445
- description: "Release this working tree's run record",
446
- category: "AI tools",
447
- reference: ["[issue|--force]", "automation", ["files"]],
448
- default_script_arguments: ["--release"]
449
- },
450
- "run:tidy": {
451
- script: "scripts/run/tidy/run-tidy-cli.ts",
452
- description: "Close merged lanes and drop stashes whose issues are all merged",
453
- category: "AI tools",
454
- reference: ["", "automation", ["git", "network", "files"]]
455
- },
456
- "run:carry": {
457
- script: "scripts/run/carry/run-carry-cli.ts",
458
- // `--stopped` sends the stop confirmation, so the Telegram credentials come from `.env` as for
459
- // `run:wake` below.
460
- tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
461
- description: "Carry one invocation\u2019s budget across its own session cuts",
462
- category: "AI tools",
463
- reference: ["<operation> [arguments...]", "automation", ["files"]]
464
- },
465
- "run:add": {
466
- script: "scripts/run/add/run-add-cli.ts",
467
- description: "Add issues to a live backlogrun, ahead of the queue unless --no-priority",
468
- category: "AI tools",
469
- reference: ["<issue...> [--no-priority]", "automation", ["network", "files"]]
470
- },
471
- "run:wake": {
472
- script: "scripts/run/wake/run-wake-cli.ts",
473
- // `.env` rather than the ambient environment:
474
- // the failure warning needs the Telegram credentials, the same
475
- // reasons `notify` and `followup` carry these flags.
476
- tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
477
- description: "Wake the next session of a cut backlogrun from outside the conversation",
478
- category: "AI tools",
479
- reference: ["[options]", "automation", ["processes", "notifications"]]
480
- },
481
- "run:cut": {
482
- script: "scripts/run/cut/run-cut-cli.ts",
483
- description: "Cut a lane child before the gate and resume a fresh process",
484
- category: "AI tools",
485
- reference: ["[--resume] <issue> [--impl] [--handoff <path>]", "automation", ["files"]]
486
- },
487
- "run:liveness": {
488
- script: "scripts/run/run-liveness-cli.ts",
489
- description: "Say whether a delegated unit is still working, or stopped without reporting",
490
- category: "AI tools",
491
- reference: ["<issue> --output <path> [options]", "automation", ["processes"]]
492
- },
493
- "run:ending": {
494
- script: "scripts/run/run-ending-cli.ts",
495
- description: "Classify how a dispatched lane child ended (merged, cut, abandoned, unreadable)",
496
- category: "AI tools",
497
- reference: ["<issue> --output <path> [--repo <owner/repo>]", "automation", ["network"]]
498
- },
499
- "run:progress": {
500
- script: "scripts/run/progress/run-progress-cli.ts",
501
- description: "Report an unattended run\u2019s progress once it has gone quiet for an interval",
502
- category: "AI tools",
503
- reference: [
504
- "[--once | --wait] [--interval <minutes>] [--hours <hours>] [--repo <owner/repo>] [--output <path>] | --mark | --path",
505
- "automation",
506
- ["files"]
507
- ]
508
- },
509
- "run:board": {
510
- script: "scripts/run/board/run-board-cli.ts",
511
- // `--every` pushes its frame as a Telegram, so the credentials come from `.env` as for `run:carry`.
512
- tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
513
- description: "Draw a live board of the running backlogrun, redrawn every second",
514
- category: "AI tools",
515
- reference: ["[--once | --chat | --every <minutes>]", "automation", ["files", "network"]]
516
- },
517
- "run:prep": {
518
- script: "scripts/run/run-prep-cli.ts",
519
- description: "Bundle a run\u2019s pre-edit reads: body, comments, state, dependency scope",
520
- category: "AI tools",
521
- reference: ["<issue>", "automation", ["network"]]
522
- },
523
- // The entry sequence a lane opened on, folded into one call: claim the tree,
524
- // read the budget, gather the issue reads and decide the pre-implementation step. `run:hold`,
525
- // `cost --cut`, `run:prep` and `run:step` were four round trips re-billing a lane's full context each.
526
- "run:entry": {
527
- script: "scripts/run/entry/run-entry-cli.ts",
528
- // A `busy` hold or an `over` budget sends the stop confirmation.
529
- tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
530
- description: "Open a run in one call: claim the tree, read the budget, bundle the reads, decide the pre-implementation step",
531
- category: "AI tools",
532
- reference: ["<issue> [--to <command>]", "automation", ["git", "network", "files"]]
533
- },
534
- "run:status": {
535
- script: "scripts/run/run-status-cli.ts",
536
- description: "Bundle a run\u2019s read-only status: issue state, cost verdict, carry counters",
537
- category: "AI tools",
538
- reference: ["<issue> [--repo <owner/repo>]", "automation", ["network"]]
539
- },
540
- "run:next": {
541
- script: "scripts/run/run-next-cli.ts",
542
- description: "Print the next step a fullrun takes, computed from the run\u2019s state",
543
- category: "AI tools",
544
- reference: ["<issue>", "automation", ["network"]]
545
- },
546
- "run:step": {
547
- script: "scripts/run/run-step-cli.ts",
548
- description: "Print the run\u2019s next single action, computed from the event stream, carry record and issue state",
549
- category: "AI tools",
550
- reference: ["[<issue>]", "automation", ["network", "files"]]
342
+ // scripts/gate/held-mark.ts
343
+ var HELD_KEY = "JOSH_CORE_RESERVED";
344
+ var HELD_VALUE = "1";
345
+ var HELD_RESERVATION_KEY = "JOSH_CORE_RESERVATION";
346
+ function is_held() {
347
+ return process.env[HELD_KEY] === HELD_VALUE;
348
+ }
349
+ function held_reservation() {
350
+ const key = process.env[HELD_RESERVATION_KEY] ?? "";
351
+ return key === "" ? void 0 : key;
352
+ }
353
+ function set_held_mark(value, reservation) {
354
+ process.env[HELD_KEY] = value;
355
+ process.env[HELD_RESERVATION_KEY] = reservation;
356
+ }
357
+ async function with_held_mark(run, reservation = "") {
358
+ const previous = process.env[HELD_KEY] ?? "";
359
+ const previous_reservation = process.env[HELD_RESERVATION_KEY] ?? "";
360
+ set_held_mark(HELD_VALUE, reservation);
361
+ try {
362
+ return await run();
363
+ } finally {
364
+ set_held_mark(previous, previous_reservation);
551
365
  }
366
+ }
367
+ var held_mark = {
368
+ HELD_KEY,
369
+ HELD_RESERVATION_KEY,
370
+ HELD_VALUE,
371
+ held_reservation,
372
+ is_held,
373
+ with_held_mark
552
374
  };
553
375
 
554
- // scripts/josh/josh-commands-split.ts
555
- var SPLIT_COMMANDS = {
556
- "split:assess": {
557
- script: "scripts/split/split-assess-cli.ts",
558
- description: "Measure the branch change size (tests excluded) and answer the split assessment size question: split | single",
559
- category: "AI tools",
560
- reference: ["[--json]", "automation", ["none"]]
376
+ // scripts/gate/machine-capacity.ts
377
+ import { readFile } from "node:fs/promises";
378
+ import { availableParallelism as availableParallelism2, cpus, totalmem } from "node:os";
379
+ import { execa } from "execa";
380
+ var SAMPLE_WINDOW_MS = 250;
381
+ var BASELINE_CORES = 2;
382
+ var BYTES_PER_MB = 1048576;
383
+ var KB_PER_MB = 1024;
384
+ var PERCENT = 100;
385
+ var MEMINFO_PATH = "/proc/meminfo";
386
+ var VMSTAT_PATH = "/proc/vmstat";
387
+ var LINUX_AVAILABLE = /^MemAvailable:\s+(?<kb>\d+) kB/mu;
388
+ var LINUX_SWAP_IN = /^pswpin (?<pages>\d+)$/mu;
389
+ var LINUX_SWAP_OUT = /^pswpout (?<pages>\d+)$/mu;
390
+ var PAGE_SIZE_ARGUMENTS = ["PAGESIZE"];
391
+ var LEVEL_NAME = "kern.memorystatus_level";
392
+ var SWAP_IN_NAME = "vm.compressor.swapper.swapins_total";
393
+ var SWAP_OUT_NAME = "vm.compressor.swapper.swapouts_total";
394
+ var COMPRESSED_NAME = "vm.compressor_input_bytes";
395
+ var PAGE_SIZE_NAME = "hw.pagesize";
396
+ var PRESSURE_NAME = "kern.memorystatus_vm_pressure_level";
397
+ var PRESSURE_WARNING = 2;
398
+ var SWAP_PRESSURE_MB = 16;
399
+ var COMPRESSION_PRESSURE_MB = 64;
400
+ var SYSCTL_ARGUMENTS = [
401
+ LEVEL_NAME,
402
+ SWAP_IN_NAME,
403
+ SWAP_OUT_NAME,
404
+ COMPRESSED_NAME,
405
+ PAGE_SIZE_NAME,
406
+ PRESSURE_NAME
407
+ ];
408
+ var SYSCTL_LINE = /^(?<name>[\w.]+): (?<value>\d+)$/gmu;
409
+ function cpu_times() {
410
+ let busy = 0;
411
+ let idle = 0;
412
+ for (const { times } of cpus()) {
413
+ busy += times.user + times.nice + times.sys + times.irq;
414
+ idle += times.idle;
415
+ }
416
+ return { busy, total: busy + idle };
417
+ }
418
+ function busy_share_between(before, after) {
419
+ const total = after.total - before.total;
420
+ if (total <= 0) return void 0;
421
+ return (after.busy - before.busy) / total;
422
+ }
423
+ function busy_cores_between(before, after, cores) {
424
+ const share = busy_share_between(before, after);
425
+ return share === void 0 ? void 0 : share * cores;
426
+ }
427
+ function sysctl_values(output) {
428
+ const values = [...output.matchAll(SYSCTL_LINE)].map(
429
+ (match) => [match.groups?.["name"] ?? "", Number(match.groups?.["value"])]
430
+ );
431
+ return new Map(values);
432
+ }
433
+ function swapped_pages(pages_in, pages_out) {
434
+ return pages_in === void 0 || pages_out === void 0 ? void 0 : pages_in + pages_out;
435
+ }
436
+ function mb_of(units, unit_bytes) {
437
+ if (units === void 0 || unit_bytes === void 0) return void 0;
438
+ return units * unit_bytes / BYTES_PER_MB;
439
+ }
440
+ function swapped_between(from, to) {
441
+ if (from === void 0 || to === void 0) return void 0;
442
+ return Math.max(0, to - from);
443
+ }
444
+ function parse_darwin_memory(output, total_bytes) {
445
+ const values = sysctl_values(output);
446
+ const level = values.get(LEVEL_NAME);
447
+ return {
448
+ available_mb: level === void 0 ? void 0 : level / PERCENT * (total_bytes / BYTES_PER_MB),
449
+ swapped_mb: mb_of(
450
+ swapped_pages(values.get(SWAP_IN_NAME), values.get(SWAP_OUT_NAME)),
451
+ values.get(PAGE_SIZE_NAME)
452
+ ),
453
+ pressure_level: values.get(PRESSURE_NAME),
454
+ compressed_mb: mb_of(values.get(COMPRESSED_NAME), 1)
455
+ };
456
+ }
457
+ function parse_linux_available(meminfo) {
458
+ const kb = LINUX_AVAILABLE.exec(meminfo)?.groups?.["kb"];
459
+ return kb === void 0 ? void 0 : Number(kb) / KB_PER_MB;
460
+ }
461
+ function pages_of(pattern, vmstat) {
462
+ const pages = pattern.exec(vmstat)?.groups?.["pages"];
463
+ return pages === void 0 ? void 0 : Number(pages);
464
+ }
465
+ function parse_linux_swapped(vmstat, page_bytes) {
466
+ const pages = swapped_pages(pages_of(LINUX_SWAP_IN, vmstat), pages_of(LINUX_SWAP_OUT, vmstat));
467
+ return mb_of(pages, page_bytes);
468
+ }
469
+ async function settled(read) {
470
+ try {
471
+ return await read();
472
+ } catch {
473
+ return void 0;
561
474
  }
475
+ }
476
+ async function read_darwin_memory() {
477
+ const { stdout } = await execa("sysctl", SYSCTL_ARGUMENTS, {
478
+ reject: false,
479
+ timeout: PROBE_TIMEOUT_MS
480
+ });
481
+ return parse_darwin_memory(stdout, totalmem());
482
+ }
483
+ async function read_page_size() {
484
+ const { stdout } = await execa("getconf", PAGE_SIZE_ARGUMENTS, { timeout: PROBE_TIMEOUT_MS });
485
+ return stdout;
486
+ }
487
+ async function read_linux_memory() {
488
+ const [meminfo, vmstat, page_size] = await Promise.all([
489
+ settled(async () => await readFile(MEMINFO_PATH, "utf8")),
490
+ settled(async () => await readFile(VMSTAT_PATH, "utf8")),
491
+ settled(read_page_size)
492
+ ]);
493
+ const page_bytes = page_size === void 0 ? void 0 : Number(page_size);
494
+ return {
495
+ available_mb: meminfo === void 0 ? void 0 : parse_linux_available(meminfo),
496
+ swapped_mb: vmstat === void 0 || page_bytes === void 0 ? void 0 : parse_linux_swapped(vmstat, page_bytes),
497
+ pressure_level: void 0
498
+ };
499
+ }
500
+ var UNREAD_MEMORY = {
501
+ available_mb: void 0,
502
+ swapped_mb: void 0,
503
+ pressure_level: void 0
562
504
  };
563
-
564
- // scripts/josh/josh-commands-ai.ts
565
- var ISSUE_WITH_OPTIONS2 = "<issue> [options]";
566
- var AI_COMMANDS = {
567
- ...ISSUE_COMMANDS,
568
- "pkg:scout": {
569
- script: "scripts/package/package-scout-cli.ts",
570
- description: "Rank package candidates by measured metrics so the Package-First tier decision is read, not judged",
571
- category: "AI tools",
572
- reference: ["<keywords> [--size <n>]", "automation", ["network"]]
573
- },
574
- "stash:pop": {
575
- script: "scripts/git/stash/stash-pop-cli.ts",
576
- description: "Pop the stash matching this message, not whichever a shared stack has on top",
577
- category: "AI tools",
578
- reference: ["<message>", "automation", ["git"]]
579
- },
580
- epic: {
581
- script: "scripts/epic/epic.ts",
582
- description: "Create an epic issue from its child issue numbers",
583
- category: "AI tools",
584
- reference: ["<title> <issue...> [--ordered]", "automation", ["network"]]
585
- },
586
- "epic:next": {
587
- script: "scripts/epic/epic-next.ts",
588
- description: "List an epic's runnable children, bundled per repository",
589
- category: "AI tools",
590
- reference: ["<epic>... [--repo <owner/repo>] [--lanes]", "automation", ["network"]]
591
- },
592
- "epic:bundle": {
593
- script: "scripts/epic/epic-bundle-cli.ts",
594
- description: "Say whether a newly filed issue belongs with ones already in the backlog",
595
- category: "AI tools",
596
- reference: ["<issue>", "automation", ["network"]]
597
- },
598
- "epic:audit": {
599
- script: "scripts/epic/epic-audit-cli.ts",
600
- description: "Audit an epic's children against each other for contradictions",
601
- category: "AI tools",
602
- reference: ["<epic>", "automation", ["network"]]
603
- },
604
- "epic:check": {
605
- script: "scripts/epic/epic-check.ts",
606
- description: "Check an epic issue against the tracking requirements",
607
- category: "AI tools",
608
- reference: ["<epic>", "automation", ["network"]]
609
- },
610
- "auto-ok:next": {
611
- script: "scripts/auto-ok/auto-ok-cli.ts",
612
- description: "Print the next opted-in issue an unattended run may pick up outside an epic",
613
- category: "AI tools",
614
- reference: ["[--exclude <n>[,<n>...]] [--label <name>]", "automation", ["network"]]
615
- },
616
- "auto-ok:judge": {
617
- script: "scripts/auto-ok/auto-ok-judge-cli.ts",
618
- description: "List the open issues backlogrun owes an auto-ok judgement, or record one (--apply / --decline)",
619
- category: "AI tools",
620
- reference: [
621
- "[--apply <n> (--lane | --solo) | --decline <n> --reason <text>]",
622
- "automation",
623
- ["network"]
624
- ]
625
- },
626
- ...BACKLOG_COMMANDS,
627
- cost: {
628
- script: "scripts/cost-runtime/cost-cli.ts",
629
- description: "Say whether the next turn of a run crosses the context-cut threshold (--cut) or a given tokens-per-request figure (--over)",
630
- category: "AI tools",
631
- reference: ["(--cut | --over <tokens-per-request>) [--path <dir>]", "automation", ["none"]]
632
- },
633
- ...DOCUMENT_COMMANDS,
634
- time: {
635
- script: "scripts/time/time-cli.ts",
636
- description: "Report where a run's wall clock went: model wait, tool execution, human wait",
637
- category: "AI tools",
638
- reference: ["[options]", "maintainer", ["none"]],
639
- // Kit-only: it measures kit's own development runs and its report modules live under the
640
- // undistributed `scripts/time/`, so it means nothing in a consumer project and is dropped from a
641
- // consumer's help. The runtime analysis hooks and guards rely on stays
642
- // distributed under `scripts/time-runtime/`.
643
- is_kit_only: true
644
- },
645
- // The tool-calls-per-round-trip density the batching guard is measured on, aggregated across the
646
- // recent lane sessions. Kit-only for the same reason `time` is: it reads
647
- // kit's own lane transcripts and means nothing in a consumer project.
648
- "time:density": {
649
- script: "scripts/time/time-density-cli.ts",
650
- description: "Report tool calls per round trip across the recent lane sessions",
651
- category: "AI tools",
652
- reference: ["[--lanes <n>] [--path <dir>]", "maintainer", ["none"]],
653
- is_kit_only: true
654
- },
655
- retrospective: {
656
- script: "scripts/retrospective/retrospective-cli.ts",
657
- description: "Aggregate a finished run's cost, review findings, observation ledger and events into one digest",
658
- category: "AI tools",
659
- reference: ["", "maintainer", ["none"]],
660
- // Kit-only: it reads kit's own development run — the transcript store, the review ledger and the
661
- // run event stream — so it means nothing in a consumer project and is dropped from a consumer's
662
- // help, through the same declaration `josh time` uses for the same reason.
663
- is_kit_only: true
664
- },
665
- "review:brief": {
666
- script: "scripts/review/review-brief-cli.ts",
667
- description: "Print the whole /code-review invocation: level, what the gate already proved, target (--level-only for the level alone)",
668
- category: "AI tools",
669
- reference: ["[--round <1|2>] | --level-only [--staged] [--json]", "automation", ["files"]]
670
- },
671
- "review:attest": {
672
- script: "scripts/review/review-attest-cli.ts",
673
- description: "Record, or verify, which checkout a /code-review actually read",
674
- category: "AI tools",
675
- reference: ["<nonce> | --check", "automation", ["files"]]
676
- },
677
- "review:round2": {
678
- script: "scripts/review/review-round2-cli.ts",
679
- description: "Say whether the second /code-review round is due, or may be skipped entirely",
680
- category: "AI tools",
681
- reference: ["[--round-1-closed] [--json]", "automation", ["none"]]
682
- },
683
- delegate: {
684
- script: "scripts/delegation/delegation-cli.ts",
685
- description: "Say whether a run step may go to a cheaper execution tier",
686
- category: "AI tools",
687
- reference: ["<step> | --list", "automation", ["none"]]
688
- },
689
- fanout: {
690
- script: "scripts/delegation/fanout-cli.ts",
691
- description: "Say whether proposed implementation units are file-disjoint, so they may run in parallel",
692
- category: "AI tools",
693
- reference: ["<unit-files> <unit-files> [<unit-files> \u2026]", "automation", ["none"]]
694
- },
695
- ...RUN_COMMANDS,
696
- "repo:party": {
697
- script: "scripts/discovery/repo-party-cli.ts",
698
- description: "Say whether a repository is first-party or third-party by owner equality (computed, not judged)",
699
- category: "AI tools",
700
- reference: ["[<owner/repo>]", "automation", ["none"]]
701
- },
702
- "run:merge": {
703
- script: "scripts/run/merge/run-merge-cli.ts",
704
- description: "Collapse a backlogrun merge event into one call: confirm the child, do the post-merge steps, offer the next child",
705
- category: "AI tools",
706
- reference: [ISSUE_WITH_OPTIONS2, "automation", ["git", "network"]]
707
- },
708
- "run:review": {
709
- script: "scripts/run/review/run-review-cli.ts",
710
- description: "Start the gate in the background and print the /code-review brief in one call so the two overlap (--join to join the gate and check its verdict)",
711
- category: "AI tools",
712
- reference: ["[--join]", "automation", ["processes", "files"]]
713
- },
714
- // The post-merge sequence a run closed on, folded into one call: commit the
715
- // observation ledger, read the completion citations and decide the release scope. `observations:flush`,
716
- // `issue:cite` and `release:scope` were three round trips re-billing the run's full context each.
717
- "run:tail": {
718
- script: "scripts/run/run-tail-cli.ts",
719
- description: "Close a run in one call: return to the default branch, commit the observation ledger, read the citations, decide the release scope",
720
- category: "AI tools",
721
- reference: ["[<issue> ...]", "automation", ["git", "network"]]
722
- },
723
- // The commit-to-report region a run ships a change on, folded into one call:
724
- // the gate, the commit/push/PR (`git -y`), the CI-wait merge (`followup`) and the report bookkeeping
725
- // (`run:tail`) were four round trips re-billing the run's full context each. It stops at the first
726
- // failed step and names it, so the run reads only the step to fix.
727
- ship: {
728
- script: "scripts/run/ship/run-ship-cli.ts",
729
- description: "Ship a change in one call: gate, commit/push/PR, the CI-wait merge and the report bookkeeping, stopping at the first failed step",
730
- category: "AI tools",
731
- reference: [
732
- '"<title> #<N>" [<follow-up-N> ...] [--cite <N>] [--review] [--detach] [--body-file <path>] [--notify-message <text> | --notify-message-file <path>] | --log <N>',
733
- "automation",
734
- ["git", "network"]
735
- ]
736
- },
737
- "run:event": {
738
- script: "scripts/run/event/run-event-cli.ts",
739
- description: "Append to, read or watch the run\u2019s append-only event stream (--append <kind> <text> | --from|--follow <n> | --watch [<n>] | --last)",
740
- category: "AI tools",
741
- reference: [
742
- "--append <kind> <text> | --from|--follow <n> | --watch [<n>] | --last",
743
- "automation",
744
- ["files"]
745
- ]
746
- },
747
- "run:report": {
748
- script: "scripts/run/run-report-cli.ts",
749
- description: "Generate the session-facing report for this invocation from the run\u2019s event stream (merges, parks, cuts) with the release tail \u2014 the same text josh notify sends",
750
- category: "AI tools",
751
- reference: ["", "automation", ["files", "network"]]
752
- },
753
- ...LANE_COMMANDS,
754
- ...GUARD_COMMANDS,
755
- "run:watcher:guard": {
756
- script: "scripts/run/run-watcher-guard-cli.ts",
757
- description: "Guard: exits non-zero when lane children are in-flight but the watcher has not pinged recently",
758
- category: "AI tools",
759
- reference: ["", "automation", ["none"]]
760
- },
761
- "run:stranded": {
762
- script: "scripts/run/run-stranded-cli.ts",
763
- // `.env` rather than the ambient environment: the strand notification needs the Telegram
764
- // credentials, the same reason `run:wake` carries these flags.
765
- tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
766
- description: "Report whether the run is stranded \u2014 owner gone, no successor holding the budget, and no supervisor watching",
767
- category: "AI tools",
768
- reference: ["", "automation", ["processes", "notifications"]]
769
- },
770
- ...SPLIT_COMMANDS,
771
- "oracle:list": {
772
- script: "scripts/rules/oracle-list-cli.ts",
773
- description: "Print the decision oracles \u2014 commands that answer a rule question mechanically",
774
- category: "AI tools",
775
- reference: ["", "automation", ["none"]]
776
- },
777
- eval: {
778
- script: "scripts/eval/eval-run.ts",
779
- description: "Run the agent rule-compliance scenarios (real Claude sessions)",
780
- category: "AI tools",
781
- reference: ["[scenario...]", "maintainer", ["processes", "network"]],
782
- // Kit-only: it replays kit's own distributed rules against real Claude sessions, so it means
783
- // nothing in a consumer project and is dropped from a consumer's help.
784
- is_kit_only: true
785
- }
786
- };
787
-
788
- // scripts/josh/josh-commands-clone.ts
789
- var CLONE_COMMANDS = {
790
- "clone:scan": {
791
- script: "scripts/clone/clone-scan-cli.ts",
792
- description: "Count code duplication across files and first-party repositories, printing each clone as file:line pairs",
793
- category: "AI tools",
794
- reference: ["", "automation", ["files"]]
795
- }
796
- };
797
-
798
- // scripts/gate/core-budget.ts
799
- import { randomUUID } from "node:crypto";
800
- import { readdirSync as readdirSync2 } from "node:fs";
801
- import { availableParallelism as availableParallelism3 } from "node:os";
802
- import path2 from "node:path";
803
-
804
- // scripts/test/unit-worker-share.ts
805
- import { readdirSync } from "node:fs";
806
- import { availableParallelism } from "node:os";
807
- import path from "node:path";
808
- var RUN_PREFIX = "josh-unit-running-";
809
- var WORKER_FLAG = "--maxWorkers";
810
- var WORKER_FLAGS = [WORKER_FLAG, "--max-workers"];
811
- var MIN_WORKERS = 1;
812
- var SOLO_RUNS = 1;
813
- function marker_path(pid = process.pid) {
814
- return stamp_file.stamp_path(RUN_PREFIX, String(pid));
505
+ async function read_memory() {
506
+ if (process.platform === "darwin") return await settled(read_darwin_memory) ?? UNREAD_MEMORY;
507
+ return process.platform === "linux" ? await read_linux_memory() : UNREAD_MEMORY;
815
508
  }
816
- function is_running(marker) {
817
- return process_identity.is_same_process(marker.pid, marker.process_start) !== false;
509
+ function is_unquoted() {
510
+ return cpus().length > 0 && cpus().length === availableParallelism2();
818
511
  }
819
- function read_marker(source) {
820
- const raw = stamp_file.read_stamp_text(source);
821
- if (raw === void 0) return void 0;
822
- const parsed = process_owner_schema.safeParse(json_value.parse_or_undefined(raw));
823
- return parsed.success ? parsed.data : void 0;
512
+ async function default_sleep(ms) {
513
+ await new Promise((resolve) => {
514
+ setTimeout(resolve, ms);
515
+ });
824
516
  }
825
- function marker_files(directory = PLATFORM_TEMP_ROOT) {
826
- try {
827
- return readdirSync(directory).filter((name) => name.startsWith(RUN_PREFIX));
828
- } catch {
829
- return [];
830
- }
517
+ function moved_over(from, to, limit) {
518
+ return (swapped_between(from, to) ?? 0) > limit;
831
519
  }
832
- function sweep_marker(source) {
833
- try {
834
- stamp_file.remove_stamp(source);
835
- } catch {
836
- }
520
+ function is_pressured(before, after) {
521
+ return (after.pressure_level ?? 0) >= PRESSURE_WARNING || moved_over(before.swapped_mb, after.swapped_mb, SWAP_PRESSURE_MB) || moved_over(before.compressed_mb, after.compressed_mb, COMPRESSION_PRESSURE_MB);
837
522
  }
838
- function is_marker_live(source) {
839
- const marker = read_marker(source);
840
- if (marker === void 0) return false;
841
- if (is_running(marker)) return true;
842
- sweep_marker(source);
843
- return false;
844
- }
845
- function live_run_count(directory = PLATFORM_TEMP_ROOT) {
846
- return marker_files(directory).filter((name) => is_marker_live(path.join(directory, name))).length;
847
- }
848
- var NESTED_KEY = "JOSH_UNIT_RUN_MARKED";
849
- var NESTED_VALUE = "1";
850
- function is_nested_run() {
851
- return process.env[NESTED_KEY] === NESTED_VALUE;
852
- }
853
- function resolve_unit_workers(available_cores, runs2) {
854
- if (runs2 <= SOLO_RUNS) return void 0;
855
- return Math.max(MIN_WORKERS, Math.floor(available_cores / runs2));
856
- }
857
- function current_share(available_cores = availableParallelism(), live_runs = live_run_count()) {
858
- const own_run2 = is_nested_run() ? 0 : SOLO_RUNS;
859
- return resolve_unit_workers(available_cores, live_runs + own_run2);
860
- }
861
- function is_worker_flag(argument) {
862
- return WORKER_FLAGS.some((flag) => argument === flag || argument.startsWith(`${flag}=`));
863
- }
864
- function worker_arguments(vitest_arguments, share) {
865
- if (share === void 0) return [];
866
- if (vitest_arguments.some((argument) => is_worker_flag(argument))) return [];
867
- return [`${WORKER_FLAG}=${String(share)}`];
868
- }
869
- function mark_run_started(target2) {
870
- try {
871
- stamp_file.write_stamp(target2, process_identity.own_fields());
872
- } catch {
873
- }
874
- }
875
- async function with_run_marker(run) {
876
- if (is_nested_run()) return await run();
877
- const target2 = marker_path();
878
- mark_run_started(target2);
879
- process.env[NESTED_KEY] = NESTED_VALUE;
880
- try {
881
- return await run();
882
- } finally {
883
- process.env[NESTED_KEY] = "";
884
- stamp_file.remove_stamp(target2);
885
- }
886
- }
887
- var unit_worker_share = {
888
- MIN_WORKERS,
889
- NESTED_KEY,
890
- NESTED_VALUE,
891
- RUN_PREFIX,
892
- SOLO_RUNS,
893
- current_share,
894
- is_nested_run,
895
- live_run_count,
896
- marker_path,
897
- read_marker,
898
- resolve_unit_workers,
899
- with_run_marker,
900
- worker_arguments
901
- };
902
-
903
- // scripts/gate/core-budget.ts
904
- import { z as z2 } from "zod";
905
-
906
- // scripts/gate/core-admission.ts
907
- var MS_PER_SECOND = 1e3;
908
- var RAMP_UP_MS = 3e4;
909
- function compare_claim(left, right) {
910
- const by_time = left.reservation.claimed_at - right.reservation.claimed_at;
911
- return by_time === 0 ? left.key.localeCompare(right.key) : by_time;
912
- }
913
- function add_load(totals, reservation) {
914
- totals.cores += reservation.weight;
915
- totals.memory_mb += reservation.memory_mb ?? 0;
916
- }
917
- function exceeds(totals, budget) {
918
- return totals.cores > budget.cores || totals.memory_mb > budget.memory_mb;
919
- }
920
- function admitted_keys(reservations, budget, memory_budget = Infinity) {
921
- const limit = { cores: budget, memory_mb: memory_budget };
922
- const totals = { cores: 0, memory_mb: 0 };
923
- const admitted = /* @__PURE__ */ new Set();
924
- for (const [index, entry] of reservations.toSorted(compare_claim).entries()) {
925
- add_load(totals, entry.reservation);
926
- if (index !== 0 && exceeds(totals, limit)) break;
927
- admitted.add(entry.key);
928
- }
929
- return admitted;
930
- }
931
- function is_admitted(key, reservations, budget) {
932
- return admitted_keys(reservations, budget.cores, budget.memory_mb).has(key);
933
- }
934
- function sum_load(reservations) {
935
- const load = { cores: 0, memory_mb: 0 };
936
- for (const entry of reservations) add_load(load, entry.reservation);
937
- return load;
938
- }
939
- function is_ramped(reservation, now) {
940
- const { admitted_at } = reservation;
941
- return admitted_at !== void 0 && now - admitted_at >= RAMP_UP_MS;
942
- }
943
- function ledger_load(reservations, now) {
944
- const admitted = reservations.filter((entry) => entry.reservation.admitted_at !== void 0);
945
- const ramped = admitted.filter((entry) => is_ramped(entry.reservation, now));
946
- return { cores: sum_load(admitted).cores, memory_mb: sum_load(ramped).memory_mb };
947
- }
948
- function total_weight(reservations) {
949
- return sum_load(reservations).cores;
950
- }
951
- function admitted_load(reservations, budget) {
952
- const admitted = admitted_keys(reservations, budget);
953
- return total_weight(reservations.filter((entry) => admitted.has(entry.key)));
954
- }
955
- function describe_memory(ledger, budget) {
956
- if (!Number.isFinite(budget.memory_mb)) return "";
957
- return ` and ${String(Math.round(ledger.memory_mb))} MB of memory against ${String(Math.round(budget.memory_mb))} MB`;
958
- }
959
- function describe_overflow(admission) {
960
- const { overflow } = admission;
961
- if (overflow === void 0) return void 0;
962
- const { ledger, budget } = overflow;
963
- const waited = String(Math.round(overflow.waited_ms / MS_PER_SECOND));
964
- const cores = `the ledger held ${String(ledger.cores)} cores against a budget of ${String(budget.cores)}`;
965
- return `started past the core budget after ${waited}s \u2014 ${cores}${describe_memory(ledger, budget)}`;
966
- }
967
- var core_admission = {
968
- RAMP_UP_MS,
969
- admitted_keys,
970
- admitted_load,
971
- describe_overflow,
972
- is_admitted,
973
- ledger_load,
974
- sum_load
975
- };
976
-
977
- // scripts/gate/machine-capacity.ts
978
- import { readFile } from "node:fs/promises";
979
- import { availableParallelism as availableParallelism2, cpus, totalmem } from "node:os";
980
- import { execa } from "execa";
981
- var SAMPLE_WINDOW_MS = 250;
982
- var BASELINE_CORES = 2;
983
- var BYTES_PER_MB = 1048576;
984
- var KB_PER_MB = 1024;
985
- var PERCENT = 100;
986
- var MEMINFO_PATH = "/proc/meminfo";
987
- var VMSTAT_PATH = "/proc/vmstat";
988
- var LINUX_AVAILABLE = /^MemAvailable:\s+(?<kb>\d+) kB/mu;
989
- var LINUX_SWAP_IN = /^pswpin (?<pages>\d+)$/mu;
990
- var LINUX_SWAP_OUT = /^pswpout (?<pages>\d+)$/mu;
991
- var PAGE_SIZE_ARGUMENTS = ["PAGESIZE"];
992
- var LEVEL_NAME = "kern.memorystatus_level";
993
- var SWAP_IN_NAME = "vm.compressor.swapper.swapins_total";
994
- var SWAP_OUT_NAME = "vm.compressor.swapper.swapouts_total";
995
- var PAGE_SIZE_NAME = "hw.pagesize";
996
- var PRESSURE_NAME = "kern.memorystatus_vm_pressure_level";
997
- var SYSCTL_ARGUMENTS = [LEVEL_NAME, SWAP_IN_NAME, SWAP_OUT_NAME, PAGE_SIZE_NAME, PRESSURE_NAME];
998
- var SYSCTL_LINE = /^(?<name>[\w.]+): (?<value>\d+)$/gmu;
999
- function cpu_times() {
1000
- let busy = 0;
1001
- let idle = 0;
1002
- for (const { times } of cpus()) {
1003
- busy += times.user + times.nice + times.sys + times.irq;
1004
- idle += times.idle;
1005
- }
1006
- return { busy, total: busy + idle };
1007
- }
1008
- function busy_share_between(before, after) {
1009
- const total = after.total - before.total;
1010
- if (total <= 0) return void 0;
1011
- return (after.busy - before.busy) / total;
1012
- }
1013
- function busy_cores_between(before, after, cores) {
1014
- const share = busy_share_between(before, after);
1015
- return share === void 0 ? void 0 : share * cores;
1016
- }
1017
- function sysctl_values(output) {
1018
- const values = [...output.matchAll(SYSCTL_LINE)].map(
1019
- (match) => [match.groups?.["name"] ?? "", Number(match.groups?.["value"])]
1020
- );
1021
- return new Map(values);
1022
- }
1023
- function swapped_pages(pages_in, pages_out) {
1024
- return pages_in === void 0 || pages_out === void 0 ? void 0 : pages_in + pages_out;
1025
- }
1026
- function swapped_mb(pages, page_bytes) {
1027
- if (pages === void 0 || page_bytes === void 0) return void 0;
1028
- return pages * page_bytes / BYTES_PER_MB;
1029
- }
1030
- function swapped_between(from, to) {
1031
- if (from === void 0 || to === void 0) return void 0;
1032
- return Math.max(0, to - from);
1033
- }
1034
- function parse_darwin_memory(output, total_bytes) {
1035
- const values = sysctl_values(output);
1036
- const level = values.get(LEVEL_NAME);
1037
- return {
1038
- available_mb: level === void 0 ? void 0 : level / PERCENT * (total_bytes / BYTES_PER_MB),
1039
- swapped_mb: swapped_mb(
1040
- swapped_pages(values.get(SWAP_IN_NAME), values.get(SWAP_OUT_NAME)),
1041
- values.get(PAGE_SIZE_NAME)
1042
- ),
1043
- pressure_level: values.get(PRESSURE_NAME)
1044
- };
1045
- }
1046
- function parse_linux_available(meminfo) {
1047
- const kb = LINUX_AVAILABLE.exec(meminfo)?.groups?.["kb"];
1048
- return kb === void 0 ? void 0 : Number(kb) / KB_PER_MB;
1049
- }
1050
- function pages_of(pattern, vmstat) {
1051
- const pages = pattern.exec(vmstat)?.groups?.["pages"];
1052
- return pages === void 0 ? void 0 : Number(pages);
1053
- }
1054
- function parse_linux_swapped(vmstat, page_bytes) {
1055
- const pages = swapped_pages(pages_of(LINUX_SWAP_IN, vmstat), pages_of(LINUX_SWAP_OUT, vmstat));
1056
- return swapped_mb(pages, page_bytes);
1057
- }
1058
- async function settled(read) {
1059
- try {
1060
- return await read();
1061
- } catch {
1062
- return void 0;
1063
- }
1064
- }
1065
- async function read_darwin_memory() {
1066
- const { stdout } = await execa("sysctl", SYSCTL_ARGUMENTS, {
1067
- reject: false,
1068
- timeout: PROBE_TIMEOUT_MS
1069
- });
1070
- return parse_darwin_memory(stdout, totalmem());
1071
- }
1072
- async function read_page_size() {
1073
- const { stdout } = await execa("getconf", PAGE_SIZE_ARGUMENTS, { timeout: PROBE_TIMEOUT_MS });
1074
- return stdout;
1075
- }
1076
- async function read_linux_memory() {
1077
- const [meminfo, vmstat, page_size] = await Promise.all([
1078
- settled(async () => await readFile(MEMINFO_PATH, "utf8")),
1079
- settled(async () => await readFile(VMSTAT_PATH, "utf8")),
1080
- settled(read_page_size)
1081
- ]);
1082
- const page_bytes = page_size === void 0 ? void 0 : Number(page_size);
1083
- return {
1084
- available_mb: meminfo === void 0 ? void 0 : parse_linux_available(meminfo),
1085
- swapped_mb: vmstat === void 0 || page_bytes === void 0 ? void 0 : parse_linux_swapped(vmstat, page_bytes),
1086
- pressure_level: void 0
1087
- };
1088
- }
1089
- var UNREAD_MEMORY = {
1090
- available_mb: void 0,
1091
- swapped_mb: void 0,
1092
- pressure_level: void 0
1093
- };
1094
- async function read_memory() {
1095
- if (process.platform === "darwin") return await settled(read_darwin_memory) ?? UNREAD_MEMORY;
1096
- return process.platform === "linux" ? await read_linux_memory() : UNREAD_MEMORY;
1097
- }
1098
- function is_unquoted() {
1099
- return cpus().length > 0 && cpus().length === availableParallelism2();
1100
- }
1101
- async function default_sleep(ms) {
1102
- await new Promise((resolve) => {
1103
- setTimeout(resolve, ms);
1104
- });
523
+ function pressured_available(before, after) {
524
+ if (after.available_mb === void 0) return void 0;
525
+ return is_pressured(before, after) ? 0 : after.available_mb;
1105
526
  }
1106
527
  async function read_machine(sleep = default_sleep) {
1107
528
  const before = cpu_times();
1108
- const [memory] = await Promise.all([read_memory(), sleep(SAMPLE_WINDOW_MS)]);
529
+ const [memory_before] = await Promise.all([read_memory(), sleep(SAMPLE_WINDOW_MS)]);
530
+ const memory_after = await read_memory();
1109
531
  const busy_cores = is_unquoted() ? busy_cores_between(before, cpu_times(), cpus().length) : void 0;
1110
- return { busy_cores, available_mb: memory.available_mb };
532
+ return {
533
+ busy_cores,
534
+ available_mb: pressured_available(memory_before, memory_after),
535
+ memory_pressured: is_pressured(memory_before, memory_after)
536
+ };
1111
537
  }
1112
538
  async function read_sample() {
1113
539
  return { cpu: cpu_times(), memory: await read_memory(), total_mb: totalmem() / BYTES_PER_MB };
@@ -1119,210 +545,965 @@ function external_cores(busy_cores, ledger_cores) {
1119
545
  function machine_budget(cores, reading, ledger) {
1120
546
  return {
1121
547
  cores: cores - external_cores(reading.busy_cores, ledger.cores),
1122
- memory_mb: reading.available_mb === void 0 ? Infinity : reading.available_mb + ledger.memory_mb
548
+ memory_mb: reading.available_mb === void 0 ? Infinity : reading.available_mb + ledger.memory_mb,
549
+ memory_pressured: reading.memory_pressured
1123
550
  };
1124
551
  }
1125
552
  var machine_capacity = {
1126
553
  BASELINE_CORES,
1127
554
  busy_cores_between,
1128
555
  busy_share_between,
556
+ COMPRESSION_PRESSURE_MB,
1129
557
  external_cores,
1130
558
  machine_budget,
1131
559
  parse_darwin_memory,
1132
560
  parse_linux_available,
1133
561
  parse_linux_swapped,
562
+ pressured_available,
1134
563
  read_machine,
1135
564
  read_memory,
1136
565
  read_sample,
566
+ SWAP_PRESSURE_MB,
1137
567
  swapped_between
1138
568
  };
1139
569
 
1140
- // scripts/gate/core-budget.ts
1141
- var RESERVED_PREFIX = "josh-core-reserved-";
1142
- var RESERVED_SUFFIX = ".json";
1143
- var POLL_INTERVAL_MS = 50;
1144
- var WAIT_CAP_MS = 12e4;
1145
- var MIN_BUDGET = 1;
1146
- var CORE_WEIGHTS = {
1147
- lint: 2,
1148
- type_check: 1,
1149
- spell_check: 1,
1150
- eslint_scan: 1
1151
- };
1152
- var MEMORY_MB = {
1153
- lint: 2400,
1154
- type_check: 1300,
1155
- spell_check: 350,
1156
- eslint_scan: 2400,
1157
- unit_worker: 350
1158
- };
1159
- var HELD_KEY = "JOSH_CORE_RESERVED";
1160
- var HELD_VALUE = "1";
1161
- var reservation_schema = process_owner_schema.extend({
1162
- weight: z2.number(),
1163
- claimed_at: z2.number(),
1164
- memory_mb: z2.number().optional(),
1165
- admitted_at: z2.number().optional()
1166
- });
1167
- function marker_name(key) {
1168
- return `${RESERVED_PREFIX}${key}${RESERVED_SUFFIX}`;
1169
- }
1170
- function is_running2(reservation) {
1171
- return process_identity.is_same_process(reservation.pid, reservation.process_start) !== false;
1172
- }
1173
- function read_reservation(source) {
1174
- const raw = stamp_file.read_stamp_text(source);
1175
- if (raw === void 0) return void 0;
1176
- return json_value.parse_with(raw, reservation_schema);
1177
- }
1178
- function sweep_marker2(source) {
1179
- try {
1180
- stamp_file.remove_stamp(source);
1181
- } catch {
1182
- }
1183
- }
1184
- function live_reservation(source) {
1185
- const reservation = read_reservation(source);
1186
- if (reservation === void 0) return void 0;
1187
- if (is_running2(reservation)) return reservation;
1188
- sweep_marker2(source);
1189
- return void 0;
1190
- }
1191
- function marker_files2(directory) {
1192
- try {
1193
- return readdirSync2(directory).filter(
1194
- (name) => name.startsWith(RESERVED_PREFIX) && name.endsWith(RESERVED_SUFFIX)
1195
- );
1196
- } catch {
1197
- return [];
1198
- }
1199
- }
1200
- function live_reservations(directory = PLATFORM_TEMP_ROOT) {
1201
- return marker_files2(directory).flatMap((name) => {
1202
- const reservation = live_reservation(path2.join(directory, name));
1203
- return reservation === void 0 ? [] : { key: name, reservation };
1204
- });
1205
- }
1206
- function normalize_budget(budget) {
1207
- return Number.isFinite(budget) ? Math.max(MIN_BUDGET, Math.floor(budget)) : MIN_BUDGET;
1208
- }
1209
- async function default_sleep2(ms) {
1210
- await new Promise((resolve) => {
1211
- setTimeout(resolve, ms);
1212
- });
1213
- }
1214
- function write_reservation(target2, fields) {
1215
- stamp_file.replace_stamp(target2, { ...process_identity.own_fields(), ...fields });
1216
- }
1217
- function resolve_timing(options) {
1218
- return {
1219
- now: options.now ?? Date.now,
1220
- sleep: options.sleep ?? default_sleep2,
1221
- poll_interval_ms: options.poll_interval_ms ?? POLL_INTERVAL_MS,
1222
- wait_cap_ms: options.wait_cap_ms ?? WAIT_CAP_MS
1223
- };
1224
- }
1225
- async function current_budget(context, reservations) {
1226
- if (reservations.length <= 1) return { cores: context.budget, memory_mb: Infinity };
1227
- const reading = await context.read_machine();
1228
- const ledger = core_admission.ledger_load(reservations, context.now());
1229
- const budget = machine_capacity.machine_budget(context.budget, reading, ledger);
1230
- return { ...budget, cores: normalize_budget(budget.cores) };
1231
- }
1232
- function admit(context, overflow) {
1233
- write_reservation(context.target, { ...context.fields, admitted_at: context.now() });
1234
- return { overflow };
1235
- }
1236
- function overflow_of(context, reservations, budget) {
1237
- return {
1238
- waited_ms: context.now() - context.fields.claimed_at,
1239
- ledger: core_admission.sum_load(reservations),
1240
- budget
1241
- };
1242
- }
1243
- async function poll_admission(context) {
1244
- const reservations = live_reservations(context.directory);
1245
- const budget = await current_budget(context, reservations);
1246
- if (core_admission.is_admitted(context.key, reservations, budget)) return admit(context);
1247
- if (context.now() >= context.deadline) {
1248
- return admit(context, overflow_of(context, reservations, budget));
1249
- }
1250
- return void 0;
1251
- }
1252
- async function await_admission(context) {
1253
- let admission = await poll_admission(context);
1254
- while (admission === void 0) {
1255
- await context.sleep(context.poll_interval_ms);
1256
- admission = await poll_admission(context);
1257
- }
1258
- return admission;
1259
- }
1260
- async function reserve(weight, options = {}) {
1261
- const timing = resolve_timing(options);
1262
- const directory = options.directory ?? PLATFORM_TEMP_ROOT;
1263
- const claimed_at = timing.now();
1264
- const name = marker_name(randomUUID());
1265
- const target2 = path2.join(directory, name);
1266
- const fields = { weight, claimed_at, memory_mb: options.memory_mb };
1267
- write_reservation(target2, fields);
1268
- const admission = await await_admission({
1269
- ...timing,
1270
- key: name,
1271
- target: target2,
1272
- directory,
1273
- budget: normalize_budget(options.budget ?? availableParallelism3()),
1274
- deadline: claimed_at + timing.wait_cap_ms,
1275
- fields,
1276
- read_machine: options.read_machine ?? machine_capacity.read_machine
1277
- });
1278
- return { target: target2, admission };
1279
- }
1280
- function release(handle) {
1281
- sweep_marker2(handle.target);
1282
- }
1283
- async function with_core_reservation(weight, run, options = {}) {
1284
- const handle = await reserve(weight, options);
1285
- try {
1286
- return await run(handle.admission);
1287
- } finally {
1288
- release(handle);
1289
- }
1290
- }
1291
- function is_held() {
1292
- return process.env[HELD_KEY] === HELD_VALUE;
1293
- }
1294
- function set_held_mark(value) {
1295
- process.env[HELD_KEY] = value;
1296
- }
1297
- async function with_held_mark(run) {
1298
- const previous = process.env[HELD_KEY] ?? "";
1299
- set_held_mark(HELD_VALUE);
1300
- try {
1301
- return await run();
1302
- } finally {
1303
- set_held_mark(previous);
570
+ // scripts/gate/core-budget.ts
571
+ var RESERVED_PREFIX = "josh-core-reserved-";
572
+ var RESERVED_SUFFIX = ".json";
573
+ var POLL_INTERVAL_MS = 50;
574
+ var WAIT_CAP_MS = 12e4;
575
+ var MIN_BUDGET = 1;
576
+ var CORE_WEIGHTS = {
577
+ lint: 2,
578
+ type_check: 1,
579
+ spell_check: 1,
580
+ eslint_scan: 1,
581
+ format: 1,
582
+ retrospective: 1,
583
+ install: 1,
584
+ e2e: 2
585
+ };
586
+ var MEMORY_MB = {
587
+ lint: 2400,
588
+ type_check: 1300,
589
+ spell_check: 350,
590
+ eslint_scan: 2400,
591
+ unit_worker: 350,
592
+ format: 300,
593
+ retrospective: 900,
594
+ install: 100
595
+ };
596
+ var reservation_schema = process_owner_schema.extend({
597
+ weight: z2.number(),
598
+ claimed_at: z2.number(),
599
+ memory_mb: z2.number().optional(),
600
+ admitted_at: z2.number().optional(),
601
+ command: z2.string().optional(),
602
+ checkout: z2.string().optional(),
603
+ lane: z2.string().optional(),
604
+ lanes_root: z2.string().optional(),
605
+ wait_reason: z2.enum(core_wait.WAIT_REASONS).optional()
606
+ });
607
+ function marker_name(key) {
608
+ return `${RESERVED_PREFIX}${key}${RESERVED_SUFFIX}`;
609
+ }
610
+ function is_running2(reservation) {
611
+ return process_identity.is_same_process(reservation.pid, reservation.process_start) !== false;
612
+ }
613
+ function read_reservation(source) {
614
+ const raw = stamp_file.read_stamp_text(source);
615
+ if (raw === void 0) return void 0;
616
+ return json_value.parse_with(raw, reservation_schema);
617
+ }
618
+ function sweep_marker2(source) {
619
+ try {
620
+ stamp_file.remove_stamp(source);
621
+ } catch {
622
+ }
623
+ }
624
+ function live_reservation(source) {
625
+ const reservation = read_reservation(source);
626
+ if (reservation === void 0) return void 0;
627
+ if (is_running2(reservation)) return reservation;
628
+ sweep_marker2(source);
629
+ return void 0;
630
+ }
631
+ function marker_files2(directory) {
632
+ try {
633
+ return readdirSync2(directory).filter(
634
+ (name) => name.startsWith(RESERVED_PREFIX) && name.endsWith(RESERVED_SUFFIX)
635
+ );
636
+ } catch {
637
+ return [];
638
+ }
639
+ }
640
+ function live_reservations(directory = PLATFORM_TEMP_ROOT) {
641
+ return marker_files2(directory).flatMap((name) => {
642
+ const reservation = live_reservation(path3.join(directory, name));
643
+ return reservation === void 0 ? [] : { key: name, reservation };
644
+ });
645
+ }
646
+ function normalize_budget(budget) {
647
+ return Number.isFinite(budget) ? Math.max(MIN_BUDGET, Math.floor(budget)) : MIN_BUDGET;
648
+ }
649
+ async function default_sleep2(ms) {
650
+ await new Promise((resolve) => {
651
+ setTimeout(resolve, ms);
652
+ });
653
+ }
654
+ function write_reservation(target2, fields) {
655
+ stamp_file.replace_stamp(target2, { ...process_identity.own_fields(), ...fields });
656
+ }
657
+ function report_wait(line) {
658
+ process.stderr.write(`${line}
659
+ `);
660
+ }
661
+ function resolve_timing(options) {
662
+ return {
663
+ now: options.now ?? Date.now,
664
+ sleep: options.sleep ?? default_sleep2,
665
+ poll_interval_ms: options.poll_interval_ms ?? POLL_INTERVAL_MS,
666
+ wait_cap_ms: options.wait_cap_ms ?? WAIT_CAP_MS
667
+ };
668
+ }
669
+ async function current_budget(context, reservations) {
670
+ if (reservations.length <= 1) return { cores: context.budget, memory_mb: Infinity };
671
+ const reading = await context.read_machine();
672
+ const ledger = core_admission.ledger_load(reservations, context.now());
673
+ const budget = machine_capacity.machine_budget(context.budget, reading, ledger);
674
+ return { ...budget, cores: normalize_budget(budget.cores) };
675
+ }
676
+ function admit(context, overflow) {
677
+ write_reservation(context.target, { ...context.fields, admitted_at: context.now() });
678
+ return { overflow };
679
+ }
680
+ function overflow_of(context, reservations, budget) {
681
+ return {
682
+ waited_ms: context.now() - context.fields.claimed_at,
683
+ ledger: core_admission.sum_load(reservations),
684
+ budget
685
+ };
686
+ }
687
+ function mark_waiting(context, reason) {
688
+ const previous = context.fields.wait_reason;
689
+ if (previous === reason) return;
690
+ context.fields = { ...context.fields, wait_reason: reason };
691
+ write_reservation(context.target, context.fields);
692
+ if (previous === void 0) context.report(core_wait.wait_line(context.fields.command, reason));
693
+ }
694
+ async function poll_admission(context) {
695
+ const reservations = live_reservations(context.directory);
696
+ const budget = await current_budget(context, reservations);
697
+ if (core_admission.is_admitted(context.key, reservations, budget)) return admit(context);
698
+ if (context.now() >= context.deadline) {
699
+ return admit(context, overflow_of(context, reservations, budget));
700
+ }
701
+ mark_waiting(context, core_wait.wait_reason(context.key, reservations, budget));
702
+ return void 0;
703
+ }
704
+ async function await_admission(context) {
705
+ let admission = await poll_admission(context);
706
+ while (admission === void 0) {
707
+ await context.sleep(context.poll_interval_ms);
708
+ admission = await poll_admission(context);
709
+ }
710
+ return admission;
711
+ }
712
+ function resolve_ports(options) {
713
+ return {
714
+ read_machine: options.read_machine ?? machine_capacity.read_machine,
715
+ report: options.report ?? report_wait
716
+ };
717
+ }
718
+ function claim_fields(weight, claimed_at, options) {
719
+ const { memory_mb, command } = options;
720
+ return { weight, claimed_at, memory_mb, command, ...core_wait.where_of(process.cwd()) };
721
+ }
722
+ async function reserve(weight, options = {}) {
723
+ const timing = resolve_timing(options);
724
+ const directory = options.directory ?? PLATFORM_TEMP_ROOT;
725
+ const claimed_at = timing.now();
726
+ const name = marker_name(randomUUID());
727
+ const target2 = path3.join(directory, name);
728
+ const fields = claim_fields(weight, claimed_at, options);
729
+ write_reservation(target2, fields);
730
+ const admission = await await_admission({
731
+ ...timing,
732
+ key: name,
733
+ target: target2,
734
+ directory,
735
+ budget: normalize_budget(options.budget ?? availableParallelism3()),
736
+ deadline: claimed_at + timing.wait_cap_ms,
737
+ fields,
738
+ ...resolve_ports(options)
739
+ });
740
+ return { target: target2, admission };
741
+ }
742
+ function budget_waits(directory = PLATFORM_TEMP_ROOT, now = Date.now(), home = process.cwd()) {
743
+ return core_wait.waits_of(live_reservations(directory), now, home);
744
+ }
745
+ function release(handle) {
746
+ sweep_marker2(handle.target);
747
+ }
748
+ async function with_core_reservation(weight, run, options = {}) {
749
+ const handle = await reserve(weight, options);
750
+ try {
751
+ return await run(handle.admission, path3.basename(handle.target));
752
+ } finally {
753
+ release(handle);
754
+ }
755
+ }
756
+ async function with_command_reservation(weight, run, options = {}) {
757
+ if (held_mark.is_held() || unit_worker_share.is_nested_run()) return await run();
758
+ return await with_core_reservation(
759
+ weight,
760
+ async (_admission, key) => await held_mark.with_held_mark(run, key),
761
+ options
762
+ );
763
+ }
764
+ var core_budget = {
765
+ CORE_WEIGHTS,
766
+ HELD_KEY: held_mark.HELD_KEY,
767
+ HELD_VALUE: held_mark.HELD_VALUE,
768
+ MEMORY_MB,
769
+ RESERVED_PREFIX,
770
+ admitted_keys: core_admission.admitted_keys,
771
+ admitted_load: core_admission.admitted_load,
772
+ budget_waits,
773
+ describe_overflow: core_admission.describe_overflow,
774
+ is_held: held_mark.is_held,
775
+ live_reservations,
776
+ release,
777
+ reserve,
778
+ with_command_reservation,
779
+ with_core_reservation,
780
+ with_held_mark: held_mark.with_held_mark
781
+ };
782
+
783
+ // scripts/josh/josh-command-types.ts
784
+ var OPTIONAL_ENV_FILE_FLAGS = [`--env-file-if-exists=${ENV_FILE_NAME}`];
785
+ var GATE_COMMAND2 = "gate";
786
+ var ESLINT_CACHE_FILE = ".eslintcache";
787
+ var TS_BUILD_INFO_FILE = ".tsbuildinfo";
788
+ var CSPELL_CACHE_FILE = ".cspellcache";
789
+ var ESLINT_EDIT_CACHE_FILE = ".eslintcache.edit";
790
+ var ESLINT_RELATED_CACHE_FILE = ".eslintcache.related";
791
+ var PRETTIER_CACHE_FILE = ".prettiercache";
792
+ var PRETTIER_RELATED_CACHE_FILE = ".prettiercache.related";
793
+ var CSPELL_COMMIT_CACHE_FILE = ".cspellcache.commit";
794
+ var ESLINT_COMMIT_CACHE_FILE = ".eslintcache.commit";
795
+ var ROOTED_JSON_PORTABILITY = "rooted-json";
796
+ var ESLINT_CACHE_SPEC = {
797
+ cache_file: ESLINT_CACHE_FILE,
798
+ portability: ROOTED_JSON_PORTABILITY
799
+ };
800
+ var TS_CACHE_SPEC = {
801
+ cache_file: TS_BUILD_INFO_FILE,
802
+ portability: "raw"
803
+ };
804
+ var CSPELL_CACHE_SPEC = {
805
+ cache_file: CSPELL_CACHE_FILE,
806
+ portability: "raw"
807
+ };
808
+ var GATE_CACHE_SPECS = [
809
+ ESLINT_CACHE_SPEC,
810
+ TS_CACHE_SPEC,
811
+ CSPELL_CACHE_SPEC
812
+ ];
813
+ var GATE_CACHE_FILES = GATE_CACHE_SPECS.map((spec) => spec.cache_file);
814
+ var IGNORED_CACHE_FILES = [
815
+ ...GATE_CACHE_FILES,
816
+ ESLINT_EDIT_CACHE_FILE,
817
+ ESLINT_RELATED_CACHE_FILE,
818
+ PRETTIER_CACHE_FILE,
819
+ PRETTIER_RELATED_CACHE_FILE,
820
+ CSPELL_COMMIT_CACHE_FILE,
821
+ ESLINT_COMMIT_CACHE_FILE
822
+ ];
823
+ var CONTENT_CACHE_FLAGS = ["--cache", "--cache-strategy", "content"];
824
+ var CACHE_LOCATION_FLAG = "--cache-location";
825
+ function content_cache_flags(cache_file) {
826
+ return [...CONTENT_CACHE_FLAGS, CACHE_LOCATION_FLAG, cache_file];
827
+ }
828
+ var ESLINT_CACHE_FLAGS = content_cache_flags(ESLINT_CACHE_FILE);
829
+ var ESLINT_EDIT_CACHE_FLAGS = content_cache_flags(ESLINT_EDIT_CACHE_FILE);
830
+ var ESLINT_RELATED_CACHE_FLAGS = content_cache_flags(ESLINT_RELATED_CACHE_FILE);
831
+ var CSPELL_CACHE_FLAGS = content_cache_flags(CSPELL_CACHE_FILE);
832
+ var PRETTIER_CACHE_FLAGS = content_cache_flags(PRETTIER_CACHE_FILE);
833
+ var PRETTIER_RELATED_CACHE_FLAGS = content_cache_flags(PRETTIER_RELATED_CACHE_FILE);
834
+ var CSPELL_COMMIT_CACHE_FLAGS = content_cache_flags(CSPELL_COMMIT_CACHE_FILE);
835
+ var ESLINT_COMMIT_CACHE_FLAGS = content_cache_flags(ESLINT_COMMIT_CACHE_FILE);
836
+
837
+ // scripts/josh/josh-commands-backlog.ts
838
+ var BACKLOG_COMMANDS = {
839
+ // Started from a terminal: the `backlogrun` keyword's launcher, and the board.
840
+ backlogrun: {
841
+ script: "scripts/backlog/backlogrun-cli.ts",
842
+ description: "Start a backlogrun in the background and show its board; with one already running, only show the board, or replace it when it is idle and the invocation differs; --stop stops it",
843
+ category: "AI tools",
844
+ reference: [
845
+ "[<n>...] [--only] [--max <n>] [--idle <minutes>] [--agent claude|codex] | --stop",
846
+ "developer",
847
+ ["files", "network", "processes"]
848
+ ]
849
+ },
850
+ "backlog:next": {
851
+ script: "scripts/backlog/backlog-next.ts",
852
+ description: "Order the whole opted-in backlog: auto-ok issues and the descendants of auto-ok epics, transitively through nested epics",
853
+ category: "AI tools",
854
+ reference: ["[--exclude <n>[,<n>...]] [--repo <owner/repo>]", "automation", ["network"]]
855
+ },
856
+ "backlog:offer": {
857
+ script: "scripts/backlog/backlog-offer-cli.ts",
858
+ description: "Collapse a backlogrun loop-head event into one call: read backlog:next, ask backlog:budget, return the verdict and any issues to start",
859
+ category: "AI tools",
860
+ reference: ["[options]", "automation", ["files", "network", "processes"]]
861
+ },
862
+ "backlog:drive": {
863
+ script: "scripts/backlog/backlog-drive-cli.ts",
864
+ description: "Drive backlogrun through offer, launch and merge; restore this run\u2019s lanes and report when complete",
865
+ category: "AI tools",
866
+ reference: ["--owner <pid> [options]", "automation", ["network"]]
867
+ },
868
+ "backlog:plan": {
869
+ script: "scripts/backlog/backlog-plan-cli.ts",
870
+ description: "Print the whole backlog as a plan: ready now, waiting on what, waiting on a person, out of scope",
871
+ category: "AI tools",
872
+ reference: [
873
+ "[issue...] [--only] [--waves] [--exclude <n>[,<n>...]]",
874
+ "automation",
875
+ ["network"]
876
+ ]
877
+ },
878
+ "backlog:stalled": {
879
+ script: "scripts/backlog/backlog-stalled-cli.ts",
880
+ // A stall sends one Telegram warning. The `Stop` hook calls the detector
881
+ // directly, so this flag costs only the manual command its in-process dispatch.
882
+ tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
883
+ description: "Report whether ready backlog work is sitting undispatched with a free lane and no recent dispatch",
884
+ category: "AI tools",
885
+ reference: ["", "automation", ["network"]]
886
+ },
887
+ "backlog:budget": {
888
+ script: "scripts/backlog/backlog-budget-cli.ts",
889
+ description: "Say whether a backlogrun may start more work, keep watching, or finish",
890
+ category: "AI tools",
891
+ reference: ["[options]", "automation", ["none"]]
892
+ }
893
+ };
894
+
895
+ // scripts/josh/josh-commands-document.ts
896
+ var DOCUMENT_COMMANDS = {
897
+ "doc:section": {
898
+ script: "scripts/document/document-section-cli.ts",
899
+ description: 'Print one section of a markdown document, for a `file.md` \u2192 "Heading" reference',
900
+ category: "AI tools",
901
+ reference: ["<file> <heading>", "automation", ["none"]],
902
+ is_bundled: true
903
+ },
904
+ "read:set": {
905
+ script: "scripts/document/read-set-cli.ts",
906
+ description: "Print what an entry point reads before it starts, and what that read costs",
907
+ category: "AI tools",
908
+ reference: ["[<entry>] [--json]", "automation", ["none"]]
909
+ },
910
+ "doc:read": {
911
+ script: "scripts/document/document-read-cli.ts",
912
+ description: "Read a whole document safely: print it, or point at the Read tool when over the Bash cap",
913
+ category: "AI tools",
914
+ reference: ["<file>", "automation", ["none"]]
915
+ },
916
+ "read:files": {
917
+ script: "scripts/document/read-files-cli.ts",
918
+ description: "Read several files in one call so edit targets fold into one turn",
919
+ category: "AI tools",
920
+ reference: ["<path> [<path> ...]", "automation", ["none"]]
921
+ },
922
+ "edit:files": {
923
+ script: "scripts/document/edit-files-cli.ts",
924
+ description: "Apply several content-addressed edits from a plan in one call",
925
+ category: "AI tools",
926
+ reference: ["<plan-path | ->", "automation", ["none"]]
927
+ }
928
+ };
929
+
930
+ // scripts/josh/josh-commands-guard.ts
931
+ var GUARD_COMMANDS = {
932
+ "investigation:guard": {
933
+ script: "scripts/delegation/investigation-guard.ts",
934
+ description: "Claude Code hook: refuse a read once the unedited-read threshold is reached again (reads the tool call on stdin)",
935
+ category: "AI tools",
936
+ reference: ["", "automation", ["none"]]
937
+ },
938
+ "duplicate-read:guard": {
939
+ script: "scripts/delegation/duplicate-read-guard.ts",
940
+ description: "Claude Code hook: refuse a second whole-file read of a path whose content has not changed since the run last read it (reads the tool call on stdin)",
941
+ category: "AI tools",
942
+ reference: ["", "automation", ["none"]]
943
+ },
944
+ "rule:guard": {
945
+ script: "scripts/rules/rule-guard.ts",
946
+ description: "Claude Code hook: deliver a trigger-delivered rule at the call that binds it (reads the tool call on stdin)",
947
+ category: "AI tools",
948
+ reference: ["", "automation", ["none"]]
949
+ }
950
+ };
951
+
952
+ // scripts/josh/josh-commands-issue.ts
953
+ var ISSUES_WITH_REPO = "<issue...> [--repo <owner/repo>]";
954
+ var ISSUE_COMMANDS = {
955
+ "issue:ack": {
956
+ script: "scripts/issue/issue-ack-cli.ts",
957
+ description: "Record that the run read a person's update to its issue, printing the update it acknowledges",
958
+ category: "AI tools",
959
+ reference: ["<issue>", "automation", ["network"]]
960
+ },
961
+ "issue:read": {
962
+ script: "scripts/issue/issue-read-cli.ts",
963
+ description: "Print each issue's title, body and every comment on it, in one call",
964
+ category: "AI tools",
965
+ reference: ["<issue...>", "automation", ["network"]]
966
+ },
967
+ "issue:state": {
968
+ script: "scripts/issue/issue-state-cli.ts",
969
+ description: "Print each issue's state and labels, in the spelling the documents compare against",
970
+ category: "AI tools",
971
+ reference: [ISSUES_WITH_REPO, "automation", ["network"]]
972
+ },
973
+ "issue:scout": {
974
+ script: "scripts/issue/issue-scout-cli.ts",
975
+ description: "Before filing: say whether an issue like this exists and which epic it belongs to",
976
+ category: "AI tools",
977
+ reference: ["<title> [--body <summary> | --body-file <path>]", "automation", ["network"]]
978
+ },
979
+ "defect:rate": {
980
+ script: "scripts/issue/defect-rate-cli.ts",
981
+ description: "Print the defect rate of merged work: defects filed per enhancement completed",
982
+ category: "AI tools",
983
+ reference: ["[--days <n>]", "automation", ["network"]]
984
+ },
985
+ "issue:fold": {
986
+ script: "scripts/issue/issue-fold-cli.ts",
987
+ description: "Before a second filing: say whether findings from this session fold into one issue",
988
+ category: "AI tools",
989
+ reference: ["<title>... [--not-separable] [--json]", "automation", ["none"]]
990
+ },
991
+ "issue:fold-existing": {
992
+ script: "scripts/issue/issue-fold-existing-cli.ts",
993
+ description: "Assess whether a complete draft can join an unstarted issue",
994
+ category: "AI tools",
995
+ reference: ["<assessment.json> [--json]", "automation", ["none"]]
996
+ },
997
+ "issue:cite": {
998
+ script: "scripts/issue/issue-cite-cli.ts",
999
+ description: "Print the paste-ready number-link citation line for each issue, in one call",
1000
+ category: "AI tools",
1001
+ reference: [ISSUES_WITH_REPO, "automation", ["network"]]
1002
+ },
1003
+ "issue:comment": {
1004
+ script: "scripts/issue/issue-comment-cli.ts",
1005
+ description: "Post one comment to an issue from a file, so no shell expands the body",
1006
+ category: "AI tools",
1007
+ reference: ["<issue> --body <text> | --body-file <path>", "automation", ["network"]]
1008
+ },
1009
+ "issue:file": {
1010
+ script: "scripts/issue/issue-file-cli.ts",
1011
+ description: "File an Issue with every filing step: lint, Origin, fold, WIP cap, duplicate scout, labels, then epic:bundle",
1012
+ category: "AI tools",
1013
+ reference: [
1014
+ "<title> --body-file <path> --depth <0|1|2> [--route <route>] [--label <name>] [--repo <owner/repo>] [--distinct <N,\u2026>] [--over-cap] [--no-auto-ok] [--release]",
1015
+ "automation",
1016
+ ["network"]
1017
+ ]
1018
+ },
1019
+ "issue:release": {
1020
+ script: "scripts/issue/issue-release-cli.ts",
1021
+ description: "Link an Issue to the repository's open release Issue as its blocker (or, with --wait, as waiting on it), filing one when none is open",
1022
+ category: "AI tools",
1023
+ reference: ["<issue> [--wait]", "automation", ["network"]]
1024
+ }
1025
+ };
1026
+
1027
+ // scripts/josh/josh-commands-lane.ts
1028
+ var LANE_SCRIPT = "scripts/lane/lane-cli.ts";
1029
+ var LANE_ARGUMENTS = { script: LANE_SCRIPT, tsx_arguments: OPTIONAL_ENV_FILE_FLAGS };
1030
+ var ISSUE_WITH_OPTIONS = "<issue> [options]";
1031
+ var MEASURE_ARGUMENTS = {
1032
+ script: "scripts/lane/lane-measure-cli.ts",
1033
+ tsx_arguments: OPTIONAL_ENV_FILE_FLAGS
1034
+ };
1035
+ var LANE_COMMANDS = {
1036
+ "lane:open": {
1037
+ ...LANE_ARGUMENTS,
1038
+ description: "Open a lane: a linked work tree with its own branch and its own port seed",
1039
+ category: "AI tools",
1040
+ reference: [ISSUE_WITH_OPTIONS, "automation", ["files", "git"]],
1041
+ default_script_arguments: ["open"]
1042
+ },
1043
+ "lane:close": {
1044
+ ...LANE_ARGUMENTS,
1045
+ description: "Close a lane, leaving no work tree, branch or directory behind",
1046
+ category: "AI tools",
1047
+ reference: ["<issue>", "automation", ["files", "git"]],
1048
+ default_script_arguments: ["close"]
1049
+ },
1050
+ "lane:list": {
1051
+ ...LANE_ARGUMENTS,
1052
+ description: "List the open lanes: which issue, which ports, and where each one is",
1053
+ category: "AI tools",
1054
+ reference: ["", "automation", ["none"]],
1055
+ default_script_arguments: ["list"]
1056
+ },
1057
+ "lane:prune": {
1058
+ ...LANE_ARGUMENTS,
1059
+ description: "Close every lane an interruption left without its work tree and every spent lane, then sweep unregistered leftovers under the lanes root",
1060
+ category: "AI tools",
1061
+ reference: ["", "automation", ["files", "git"]],
1062
+ default_script_arguments: ["prune"]
1063
+ },
1064
+ "lane:output": {
1065
+ ...LANE_ARGUMENTS,
1066
+ description: "Record, or read back, where the unit running a lane\u2019s child writes",
1067
+ category: "AI tools",
1068
+ reference: ["<issue> [path]", "automation", ["files"]],
1069
+ default_script_arguments: ["output"]
1070
+ },
1071
+ "lane:dispatch": {
1072
+ ...LANE_ARGUMENTS,
1073
+ description: "Start a lane\u2019s child as a detached process that outlives this session",
1074
+ category: "AI tools",
1075
+ reference: ["<issue> <prompt>", "automation", ["processes"]],
1076
+ default_script_arguments: ["dispatch"]
1077
+ },
1078
+ "lane:await": {
1079
+ ...LANE_ARGUMENTS,
1080
+ description: "Block until any of the named in-flight lane children completes; prints which one finished",
1081
+ category: "AI tools",
1082
+ reference: ["<issue> [<issue>...] [--owner <pid>]", "automation", ["files", "processes"]],
1083
+ default_script_arguments: ["await"]
1084
+ },
1085
+ "lane:launch": {
1086
+ script: "scripts/lane/lane-launch-cli.ts",
1087
+ description: "Collapse a backlogrun lane-start event into one call: open the lane, pop and re-install on the first, dispatch the child",
1088
+ category: "AI tools",
1089
+ reference: ["<issue> [--stash <message>]", "automation", ["files", "git", "processes"]]
1090
+ },
1091
+ // `.env` is read for `JOSH_LANE_ROOT`, as every lane command reads it, so the lane is found.
1092
+ "lane:restart": {
1093
+ script: "scripts/lane/lane-restart-cli.ts",
1094
+ tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
1095
+ description: "Stop a running lane child, stash its uncommitted work and re-dispatch its issue in a fresh session \u2014 not counted as an outage or a failure",
1096
+ category: "AI tools",
1097
+ reference: ["<issue>", "developer", ["files", "git", "processes", "network"]]
1098
+ },
1099
+ // `.env` is read for `JOSH_LANE_ROOT`, as every lane command reads it, so the lane is found.
1100
+ "lane:withdraw": {
1101
+ script: "scripts/lane/lane-withdraw-cli.ts",
1102
+ tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
1103
+ description: "Stop a running lane child, stash its uncommitted work, close its issue as not planned and close the lane \u2014 booked withdrawn, not as an outage or a failure",
1104
+ category: "AI tools",
1105
+ reference: ["<issue> --reason <text>", "developer", ["files", "git", "processes", "network"]]
1106
+ },
1107
+ // A live run's lane limit: `.env` is read so the bare form reports the
1108
+ // `JOSH_LANE_LIMIT` a person set there when no override stands.
1109
+ "lane:limit": {
1110
+ script: "scripts/lane/lane-limit-cli.ts",
1111
+ tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
1112
+ description: "Change a live backlogrun\u2019s lane limit without stopping it, or print the limit, lanes in use and free lanes",
1113
+ category: "AI tools",
1114
+ reference: ["[<limit>] [--reset]", "developer", ["files"]]
1115
+ },
1116
+ // The lane-limit measurement: `.env` is read so `lane:stats` labels the row
1117
+ // with the `JOSH_LANE_LIMIT` a person set there.
1118
+ "lane:sample": {
1119
+ ...MEASURE_ARGUMENTS,
1120
+ description: "Record the machine load (load average, swap, free memory, working lanes) to the lane ledger \u2014 a backlogrun samples itself, this is for a reading outside one",
1121
+ category: "AI tools",
1122
+ reference: ["[--every <seconds>]", "maintainer", ["files"]],
1123
+ default_script_arguments: ["sample"]
1124
+ },
1125
+ "lane:stats": {
1126
+ ...MEASURE_ARGUMENTS,
1127
+ description: "Print one table row of lane throughput, gate duration and machine load over a period, the per-stage durations of a lane under it, the wait after each ship stop reason under those, and the duration and reuse count of each gate check last",
1128
+ category: "AI tools",
1129
+ reference: ["--period <days> [--limit <lane-limit>]", "maintainer", ["none"]],
1130
+ default_script_arguments: ["stats"]
1131
+ }
1132
+ };
1133
+
1134
+ // scripts/josh/josh-commands-run.ts
1135
+ var RUN_HOLD_SCRIPT = "scripts/run/hold/run-hold-cli.ts";
1136
+ var RUN_COMMANDS = {
1137
+ "run:hold": {
1138
+ script: RUN_HOLD_SCRIPT,
1139
+ description: "Claim this working tree for a run, or say which run already holds it",
1140
+ category: "AI tools",
1141
+ reference: ["[<issue> [--fullrun | --halfrun-stop | --prrun-stop]]", "automation", ["files"]]
1142
+ },
1143
+ "run:release": {
1144
+ script: RUN_HOLD_SCRIPT,
1145
+ description: "Release this working tree's run record",
1146
+ category: "AI tools",
1147
+ reference: ["[issue|--force]", "automation", ["files"]],
1148
+ default_script_arguments: ["--release"]
1149
+ },
1150
+ "run:tidy": {
1151
+ script: "scripts/run/tidy/run-tidy-cli.ts",
1152
+ description: "Close merged lanes and drop stashes whose issues are all merged",
1153
+ category: "AI tools",
1154
+ reference: ["", "automation", ["git", "network", "files"]]
1155
+ },
1156
+ "run:carry": {
1157
+ script: "scripts/run/carry/run-carry-cli.ts",
1158
+ // `--stopped` sends the stop confirmation, so the Telegram credentials come from `.env` as for
1159
+ // `run:wake` below.
1160
+ tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
1161
+ description: "Carry one invocation\u2019s budget across its own session cuts",
1162
+ category: "AI tools",
1163
+ reference: ["<operation> [arguments...]", "automation", ["files"]]
1164
+ },
1165
+ "run:add": {
1166
+ script: "scripts/run/add/run-add-cli.ts",
1167
+ description: "Add issues to a live backlogrun, ahead of the queue unless --no-priority",
1168
+ category: "AI tools",
1169
+ reference: ["<issue...> [--no-priority]", "automation", ["network", "files"]]
1170
+ },
1171
+ "run:wake": {
1172
+ script: "scripts/run/wake/run-wake-cli.ts",
1173
+ // `.env` rather than the ambient environment:
1174
+ // the failure warning needs the Telegram credentials, the same
1175
+ // reasons `notify` and `followup` carry these flags.
1176
+ tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
1177
+ description: "Wake the next session of a cut backlogrun from outside the conversation",
1178
+ category: "AI tools",
1179
+ reference: ["[options]", "automation", ["processes", "notifications"]]
1180
+ },
1181
+ "run:cut": {
1182
+ script: "scripts/run/cut/run-cut-cli.ts",
1183
+ description: "Cut a lane child before the gate and resume a fresh process",
1184
+ category: "AI tools",
1185
+ reference: ["[--resume] <issue> [--impl] [--handoff <path>]", "automation", ["files"]]
1186
+ },
1187
+ "run:liveness": {
1188
+ script: "scripts/run/run-liveness-cli.ts",
1189
+ description: "Say whether a delegated unit is still working, or stopped without reporting",
1190
+ category: "AI tools",
1191
+ reference: ["<issue> --output <path> [options]", "automation", ["processes"]]
1192
+ },
1193
+ "run:ending": {
1194
+ script: "scripts/run/run-ending-cli.ts",
1195
+ description: "Classify how a dispatched lane child ended (merged, cut, abandoned, unreadable)",
1196
+ category: "AI tools",
1197
+ reference: ["<issue> --output <path> [--repo <owner/repo>]", "automation", ["network"]]
1198
+ },
1199
+ "run:progress": {
1200
+ script: "scripts/run/progress/run-progress-cli.ts",
1201
+ description: "Report an unattended run\u2019s progress once it has gone quiet for an interval",
1202
+ category: "AI tools",
1203
+ reference: [
1204
+ "[--once | --wait] [--interval <minutes>] [--hours <hours>] [--repo <owner/repo>] [--output <path>] | --mark | --path",
1205
+ "automation",
1206
+ ["files"]
1207
+ ]
1208
+ },
1209
+ "run:board": {
1210
+ script: "scripts/run/board/run-board-cli.ts",
1211
+ // `--every` pushes its frame as a Telegram, so the credentials come from `.env` as for `run:carry`.
1212
+ tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
1213
+ description: "Draw a live board of the running backlogrun, redrawn every second",
1214
+ category: "AI tools",
1215
+ reference: ["[--once | --chat | --every <minutes>]", "automation", ["files", "network"]]
1216
+ },
1217
+ "run:prep": {
1218
+ script: "scripts/run/run-prep-cli.ts",
1219
+ description: "Bundle a run\u2019s pre-edit reads: body, comments, state, dependency scope",
1220
+ category: "AI tools",
1221
+ reference: ["<issue>", "automation", ["network"]]
1222
+ },
1223
+ // The entry sequence a lane opened on, folded into one call: claim the tree,
1224
+ // read the budget, gather the issue reads and decide the pre-implementation step. `run:hold`,
1225
+ // `cost --cut`, `run:prep` and `run:step` were four round trips re-billing a lane's full context each.
1226
+ "run:entry": {
1227
+ script: "scripts/run/entry/run-entry-cli.ts",
1228
+ // A `busy` hold or an `over` budget sends the stop confirmation.
1229
+ tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
1230
+ description: "Open a run in one call: claim the tree, read the budget, bundle the reads, decide the pre-implementation step",
1231
+ category: "AI tools",
1232
+ reference: ["<issue> [--to <command>]", "automation", ["git", "network", "files"]]
1233
+ },
1234
+ "run:status": {
1235
+ script: "scripts/run/run-status-cli.ts",
1236
+ description: "Bundle a run\u2019s read-only status: issue state, cost verdict, carry counters",
1237
+ category: "AI tools",
1238
+ reference: ["<issue> [--repo <owner/repo>]", "automation", ["network"]]
1239
+ },
1240
+ "run:next": {
1241
+ script: "scripts/run/run-next-cli.ts",
1242
+ description: "Print the next step a fullrun takes, computed from the run\u2019s state",
1243
+ category: "AI tools",
1244
+ reference: ["<issue>", "automation", ["network"]]
1245
+ },
1246
+ "run:step": {
1247
+ script: "scripts/run/run-step-cli.ts",
1248
+ description: "Print the run\u2019s next single action, computed from the event stream, carry record and issue state",
1249
+ category: "AI tools",
1250
+ reference: ["[<issue>]", "automation", ["network", "files"]]
1251
+ }
1252
+ };
1253
+
1254
+ // scripts/josh/josh-commands-split.ts
1255
+ var SPLIT_COMMANDS = {
1256
+ "split:assess": {
1257
+ script: "scripts/split/split-assess-cli.ts",
1258
+ description: "Measure the branch change size (tests excluded) and answer the split assessment size question: split | single",
1259
+ category: "AI tools",
1260
+ reference: ["[--json]", "automation", ["none"]]
1261
+ }
1262
+ };
1263
+
1264
+ // scripts/josh/josh-commands-ai.ts
1265
+ var ISSUE_WITH_OPTIONS2 = "<issue> [options]";
1266
+ var AI_COMMANDS = {
1267
+ ...ISSUE_COMMANDS,
1268
+ "pkg:scout": {
1269
+ script: "scripts/package/package-scout-cli.ts",
1270
+ description: "Rank package candidates by measured metrics so the Package-First tier decision is read, not judged",
1271
+ category: "AI tools",
1272
+ reference: ["<keywords> [--size <n>]", "automation", ["network"]]
1273
+ },
1274
+ "stash:pop": {
1275
+ script: "scripts/git/stash/stash-pop-cli.ts",
1276
+ description: "Pop the stash matching this message, not whichever a shared stack has on top",
1277
+ category: "AI tools",
1278
+ reference: ["<message>", "automation", ["git"]]
1279
+ },
1280
+ epic: {
1281
+ script: "scripts/epic/epic.ts",
1282
+ description: "Create an epic issue from its child issue numbers",
1283
+ category: "AI tools",
1284
+ reference: ["<title> <issue...> [--ordered]", "automation", ["network"]]
1285
+ },
1286
+ "epic:next": {
1287
+ script: "scripts/epic/epic-next.ts",
1288
+ description: "List an epic's runnable children, bundled per repository",
1289
+ category: "AI tools",
1290
+ reference: ["<epic>... [--repo <owner/repo>] [--lanes]", "automation", ["network"]]
1291
+ },
1292
+ "epic:bundle": {
1293
+ script: "scripts/epic/epic-bundle-cli.ts",
1294
+ description: "Say whether a newly filed issue belongs with ones already in the backlog",
1295
+ category: "AI tools",
1296
+ reference: ["<issue>", "automation", ["network"]]
1297
+ },
1298
+ "epic:audit": {
1299
+ script: "scripts/epic/epic-audit-cli.ts",
1300
+ description: "Audit an epic's children against each other for contradictions",
1301
+ category: "AI tools",
1302
+ reference: ["<epic>", "automation", ["network"]]
1303
+ },
1304
+ "epic:check": {
1305
+ script: "scripts/epic/epic-check.ts",
1306
+ description: "Check an epic issue against the tracking requirements",
1307
+ category: "AI tools",
1308
+ reference: ["<epic>", "automation", ["network"]]
1309
+ },
1310
+ "auto-ok:next": {
1311
+ script: "scripts/auto-ok/auto-ok-cli.ts",
1312
+ description: "Print the next opted-in issue an unattended run may pick up outside an epic",
1313
+ category: "AI tools",
1314
+ reference: ["[--exclude <n>[,<n>...]] [--label <name>]", "automation", ["network"]]
1315
+ },
1316
+ "auto-ok:judge": {
1317
+ script: "scripts/auto-ok/auto-ok-judge-cli.ts",
1318
+ description: "List the open issues backlogrun owes an auto-ok judgement, or record one (--apply / --decline)",
1319
+ category: "AI tools",
1320
+ reference: [
1321
+ "[--apply <n> (--lane | --solo) | --decline <n> --reason <text>]",
1322
+ "automation",
1323
+ ["network"]
1324
+ ]
1325
+ },
1326
+ ...BACKLOG_COMMANDS,
1327
+ cost: {
1328
+ script: "scripts/cost-runtime/cost-cli.ts",
1329
+ description: "Say whether the next turn of a run crosses the context-cut threshold (--cut) or a given tokens-per-request figure (--over)",
1330
+ category: "AI tools",
1331
+ reference: ["(--cut | --over <tokens-per-request>) [--path <dir>]", "automation", ["none"]]
1332
+ },
1333
+ "cost:audit": {
1334
+ script: "scripts/cost-audit/cost-audit-cli.ts",
1335
+ description: "Report AI cost over a period by session kind, tool category and guard, with cost per merge and regressions against the period before",
1336
+ category: "AI tools",
1337
+ reference: ["[--period <days>]", "maintainer", ["none"]],
1338
+ // Kit-only: it reads kit's own transcript store, like `josh retrospective`, which carries its
1339
+ // summary line.
1340
+ is_kit_only: true
1341
+ },
1342
+ ...DOCUMENT_COMMANDS,
1343
+ time: {
1344
+ script: "scripts/time/time-cli.ts",
1345
+ description: "Report where a run's wall clock went: model wait, tool execution, human wait",
1346
+ category: "AI tools",
1347
+ reference: ["[options]", "maintainer", ["none"]],
1348
+ // Kit-only: it measures kit's own development runs and its report modules live under the
1349
+ // undistributed `scripts/time/`, so it means nothing in a consumer project and is dropped from a
1350
+ // consumer's help. The runtime analysis hooks and guards rely on stays
1351
+ // distributed under `scripts/time-runtime/`.
1352
+ is_kit_only: true
1353
+ },
1354
+ // The tool-calls-per-round-trip density the batching guard is measured on, aggregated across the
1355
+ // recent lane sessions. Kit-only for the same reason `time` is: it reads
1356
+ // kit's own lane transcripts and means nothing in a consumer project.
1357
+ "time:density": {
1358
+ script: "scripts/time/time-density-cli.ts",
1359
+ description: "Report tool calls per round trip across the recent lane sessions",
1360
+ category: "AI tools",
1361
+ reference: ["[--lanes <n>] [--path <dir>]", "maintainer", ["none"]],
1362
+ is_kit_only: true
1363
+ },
1364
+ retrospective: {
1365
+ script: "scripts/retrospective/retrospective-cli.ts",
1366
+ description: "Aggregate a finished run's cost, review findings, observation ledger and events into one digest",
1367
+ category: "AI tools",
1368
+ reference: ["", "maintainer", ["none"]],
1369
+ core_weight: core_budget.CORE_WEIGHTS.retrospective,
1370
+ memory_mb: core_budget.MEMORY_MB.retrospective,
1371
+ // Kit-only: it reads kit's own development run — the transcript store, the review ledger and the
1372
+ // run event stream — so it means nothing in a consumer project and is dropped from a consumer's
1373
+ // help, through the same declaration `josh time` uses for the same reason.
1374
+ is_kit_only: true
1375
+ },
1376
+ "review:brief": {
1377
+ script: "scripts/review/review-brief-cli.ts",
1378
+ description: "Print the whole /code-review invocation: level, what the gate already proved, target (--level-only for the level alone)",
1379
+ category: "AI tools",
1380
+ reference: ["[--round <1|2>] | --level-only [--staged] [--json]", "automation", ["files"]]
1381
+ },
1382
+ "review:attest": {
1383
+ script: "scripts/review/review-attest-cli.ts",
1384
+ description: "Record, or verify, which checkout a /code-review actually read",
1385
+ category: "AI tools",
1386
+ reference: ["<nonce> | --check", "automation", ["files"]]
1387
+ },
1388
+ "review:round2": {
1389
+ script: "scripts/review/review-round2-cli.ts",
1390
+ description: "Say whether the second /code-review round is due, or may be skipped entirely",
1391
+ category: "AI tools",
1392
+ reference: ["[--round-1-closed] [--json]", "automation", ["none"]]
1393
+ },
1394
+ delegate: {
1395
+ script: "scripts/delegation/delegation-cli.ts",
1396
+ description: "Say whether a run step may go to a cheaper execution tier",
1397
+ category: "AI tools",
1398
+ reference: ["<step> | --list", "automation", ["none"]]
1399
+ },
1400
+ fanout: {
1401
+ script: "scripts/delegation/fanout-cli.ts",
1402
+ description: "Say whether proposed implementation units are file-disjoint, so they may run in parallel",
1403
+ category: "AI tools",
1404
+ reference: ["<unit-files> <unit-files> [<unit-files> \u2026]", "automation", ["none"]]
1405
+ },
1406
+ ...RUN_COMMANDS,
1407
+ "repo:party": {
1408
+ script: "scripts/discovery/repo-party-cli.ts",
1409
+ description: "Say whether a repository is first-party or third-party by owner equality (computed, not judged)",
1410
+ category: "AI tools",
1411
+ reference: ["[<owner/repo>]", "automation", ["none"]]
1412
+ },
1413
+ "run:merge": {
1414
+ script: "scripts/run/merge/run-merge-cli.ts",
1415
+ description: "Collapse a backlogrun merge event into one call: confirm the child, do the post-merge steps, offer the next child",
1416
+ category: "AI tools",
1417
+ reference: [ISSUE_WITH_OPTIONS2, "automation", ["git", "network"]]
1418
+ },
1419
+ "run:review": {
1420
+ script: "scripts/run/review/run-review-cli.ts",
1421
+ description: "Start the gate in the background and print the /code-review brief in one call so the two overlap (--join to join the gate and check its verdict)",
1422
+ category: "AI tools",
1423
+ reference: ["[--join]", "automation", ["processes", "files"]]
1424
+ },
1425
+ // The post-merge sequence a run closed on, folded into one call: commit the
1426
+ // observation ledger, read the completion citations and decide the release scope. `observations:flush`,
1427
+ // `issue:cite` and `release:scope` were three round trips re-billing the run's full context each.
1428
+ "run:tail": {
1429
+ script: "scripts/run/run-tail-cli.ts",
1430
+ description: "Close a run in one call: return to the default branch, commit the observation ledger, read the citations, decide the release scope",
1431
+ category: "AI tools",
1432
+ reference: ["[<issue> ...]", "automation", ["git", "network"]]
1433
+ },
1434
+ // The commit-to-report region a run ships a change on, folded into one call:
1435
+ // the gate, the commit/push/PR (`git -y`), the CI-wait merge (`followup`) and the report bookkeeping
1436
+ // (`run:tail`) were four round trips re-billing the run's full context each. It stops at the first
1437
+ // failed step and names it, so the run reads only the step to fix.
1438
+ ship: {
1439
+ script: "scripts/run/ship/run-ship-cli.ts",
1440
+ description: "Ship a change in one call: gate, commit/push/PR, the CI-wait merge and the report bookkeeping, stopping at the first failed step",
1441
+ category: "AI tools",
1442
+ reference: [
1443
+ '"<title> #<N>" [<follow-up-N> ...] [--cite <N>] [--review] [--detach] [--body-file <path>] [--notify-message <text> | --notify-message-file <path>] | --log <N>',
1444
+ "automation",
1445
+ ["git", "network"]
1446
+ ]
1447
+ },
1448
+ "run:event": {
1449
+ script: "scripts/run/event/run-event-cli.ts",
1450
+ description: "Append to, read or watch the run\u2019s append-only event stream (--append <kind> <text> | --from|--follow <n> | --watch [<n>] | --last)",
1451
+ category: "AI tools",
1452
+ reference: [
1453
+ "--append <kind> <text> | --from|--follow <n> | --watch [<n>] | --last",
1454
+ "automation",
1455
+ ["files"]
1456
+ ]
1457
+ },
1458
+ "run:report": {
1459
+ script: "scripts/run/run-report-cli.ts",
1460
+ description: "Generate the session-facing report for this invocation from the run\u2019s event stream (merges, parks, cuts) with the release tail \u2014 the same text josh notify sends",
1461
+ category: "AI tools",
1462
+ reference: ["", "automation", ["files", "network"]]
1463
+ },
1464
+ ...LANE_COMMANDS,
1465
+ ...GUARD_COMMANDS,
1466
+ "run:watcher:guard": {
1467
+ script: "scripts/run/run-watcher-guard-cli.ts",
1468
+ description: "Guard: exits non-zero when lane children are in-flight but the watcher has not pinged recently",
1469
+ category: "AI tools",
1470
+ reference: ["", "automation", ["none"]]
1471
+ },
1472
+ "run:stranded": {
1473
+ script: "scripts/run/run-stranded-cli.ts",
1474
+ // `.env` rather than the ambient environment: the strand notification needs the Telegram
1475
+ // credentials, the same reason `run:wake` carries these flags.
1476
+ tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
1477
+ description: "Report whether the run is stranded \u2014 owner gone, no successor holding the budget, and no supervisor watching",
1478
+ category: "AI tools",
1479
+ reference: ["", "automation", ["processes", "notifications"]]
1480
+ },
1481
+ ...SPLIT_COMMANDS,
1482
+ "oracle:list": {
1483
+ script: "scripts/rules/oracle-list-cli.ts",
1484
+ description: "Print the decision oracles \u2014 commands that answer a rule question mechanically",
1485
+ category: "AI tools",
1486
+ reference: ["", "automation", ["none"]]
1487
+ },
1488
+ eval: {
1489
+ script: "scripts/eval/eval-run.ts",
1490
+ description: "Run the agent rule-compliance scenarios (real Claude sessions)",
1491
+ category: "AI tools",
1492
+ reference: ["[scenario...]", "maintainer", ["processes", "network"]],
1493
+ // Kit-only: it replays kit's own distributed rules against real Claude sessions, so it means
1494
+ // nothing in a consumer project and is dropped from a consumer's help.
1495
+ is_kit_only: true
1496
+ }
1497
+ };
1498
+
1499
+ // scripts/josh/josh-commands-clone.ts
1500
+ var CLONE_COMMANDS = {
1501
+ "clone:scan": {
1502
+ script: "scripts/clone/clone-scan-cli.ts",
1503
+ description: "Count code duplication across files and first-party repositories, printing each clone as file:line pairs",
1504
+ category: "AI tools",
1505
+ reference: ["", "automation", ["files"]]
1304
1506
  }
1305
- }
1306
- async function with_command_reservation(weight, run, options = {}) {
1307
- if (is_held() || unit_worker_share.is_nested_run()) return await run();
1308
- return await with_core_reservation(weight, async () => await with_held_mark(run), options);
1309
- }
1310
- var core_budget = {
1311
- CORE_WEIGHTS,
1312
- HELD_KEY,
1313
- HELD_VALUE,
1314
- MEMORY_MB,
1315
- RESERVED_PREFIX,
1316
- admitted_keys: core_admission.admitted_keys,
1317
- admitted_load: core_admission.admitted_load,
1318
- describe_overflow: core_admission.describe_overflow,
1319
- is_held,
1320
- live_reservations,
1321
- release,
1322
- reserve,
1323
- with_command_reservation,
1324
- with_core_reservation,
1325
- with_held_mark
1326
1507
  };
1327
1508
 
1328
1509
  // scripts/gate/gate-plan.ts
@@ -1388,6 +1569,7 @@ var TYPE_CHECK_LABEL = "check";
1388
1569
  var UNIT_LABEL = "test:unit";
1389
1570
  var BEHAVIOR_LABEL = "behavior";
1390
1571
  var METRICS_LABEL = "metrics";
1572
+ var EXPORTS_LABEL = "exports";
1391
1573
  var WARNING_CHECKER_LABELS = [LINT_LABEL, TYPE_CHECK_LABEL];
1392
1574
  var STATIC_CHECKS = [
1393
1575
  {
@@ -1413,7 +1595,7 @@ var STATIC_CHECKS = [
1413
1595
  // program and walks it for about 5s, and it is over well before lint and the type check. A
1414
1596
  // reservation would only take a place from the four-core CI runner's fan-out and a worker from the
1415
1597
  // unit suite for the whole gate.
1416
- { label: "exports", target: "exports:unused", reserved_cores: 0 },
1598
+ { label: EXPORTS_LABEL, target: "exports:unused", reserved_cores: 0 },
1417
1599
  // **The metrics ratchet is kit's own**: it holds kit's repository-wide
1418
1600
  // totals to the merge-base's. It is one in-process eslint pass with only `max-lines` enabled, over
1419
1601
  // in a few seconds, so it reserves no core for the reason the unused-member check above does not.
@@ -1502,6 +1684,15 @@ function resolve_gate_plan(available_cores = availableParallelism4(), concurrent
1502
1684
  unit_worker_cap: unit_cap_for(checks, available_cores, concurrent_runs)
1503
1685
  };
1504
1686
  }
1687
+ function bound_by_free(plan, available_cores, free_cores) {
1688
+ if (!has_unit_check(plan.checks)) return plan;
1689
+ const unit_worker_cap = unit_worker_share.cap_to_free(
1690
+ plan.unit_worker_cap,
1691
+ free_cores,
1692
+ available_cores
1693
+ );
1694
+ return { ...plan, unit_worker_cap };
1695
+ }
1505
1696
  function format_machine(available_cores, concurrent_runs) {
1506
1697
  const cores = `${String(available_cores)} cores`;
1507
1698
  if (concurrent_runs <= unit_worker_share.SOLO_RUNS) return cores;
@@ -1520,6 +1711,7 @@ function format_gate_plan(plan, available_cores = availableParallelism4(), concu
1520
1711
  return `${josh_verdict.GATE_OPENING_PREFIX}${width}, ${cap} (${machine})`;
1521
1712
  }
1522
1713
  var gate_plan = {
1714
+ EXPORTS_LABEL,
1523
1715
  GATE_CHECKS,
1524
1716
  LINT_LABEL,
1525
1717
  MEASURED_CORES,
@@ -1529,6 +1721,7 @@ var gate_plan = {
1529
1721
  TYPE_CHECK_LABEL,
1530
1722
  UNIT_LABEL,
1531
1723
  WARNING_CHECKER_LABELS,
1724
+ bound_by_free,
1532
1725
  check_memory,
1533
1726
  check_weight,
1534
1727
  direct_unit_memory,
@@ -1661,7 +1854,7 @@ var DEV_COMMANDS = {
1661
1854
  },
1662
1855
  "stop:guard": {
1663
1856
  script: "scripts/hooks/stop-guard.ts",
1664
- description: "Claude Code Stop hook: deliver the three stop-time rules \u2014 hold notify/release and issue citation (reads the Stop payload on stdin)",
1857
+ description: "Claude Code Stop hook: send the hold notify, release the hold and print the issue citations itself; block on the rules that need the model (reads the Stop payload on stdin)",
1665
1858
  category: "Development",
1666
1859
  reference: ["", "automation", ["none"]]
1667
1860
  // **No `tsx_arguments`, deliberately**, the same as `pretool:guard` above: this runs at every
@@ -1795,6 +1988,12 @@ var HOOKS_COMMANDS = {
1795
1988
  category: "Git hooks",
1796
1989
  reference: ["[paths...]", "automation", ["none"]]
1797
1990
  },
1991
+ "pre-commit-lint": {
1992
+ script: "scripts/hooks/pre-commit-lint.ts",
1993
+ description: "Git hook: run cspell, prettier or eslint over staged files from a content cache",
1994
+ category: "Git hooks",
1995
+ reference: ["<cspell|prettier|eslint> [paths...]", "automation", ["files"]]
1996
+ },
1798
1997
  "pre-push-unit": {
1799
1998
  script: "scripts/hooks/pre-push-unit.ts",
1800
1999
  description: "Git hook: run unit tests, reusing a green gate recorded on the pushed tree",
@@ -2200,6 +2399,11 @@ var WORKFLOW_COMMANDS = {
2200
2399
  // A script rather than an `sh -c` chain, because the strategy has to be named rather than left to
2201
2400
  // the caller's git configuration: a bare `git pull` aborts with `Need to specify how to reconcile
2202
2401
  // divergent branches` on exactly the diverged branch the command exists for.
2402
+ //
2403
+ // **It declares no core weight, by measurement** (joshuafolkken/kit#3944): its own work is git, and
2404
+ // the catalog it may regenerate took 0.6s of CPU. The heavy processes seen beside it are commit
2405
+ // hooks — its regenerated catalog's commit runs them — which reserve themselves, and a weight here
2406
+ // would set the held mark they inherit, folding the parallel hook tools into this one place.
2203
2407
  "main:merge": {
2204
2408
  script: "scripts/git/main-merge.ts",
2205
2409
  description: "Merge origin default branch into the current branch",
@@ -2266,6 +2470,7 @@ var ALIASES = {
2266
2470
  tm: "time",
2267
2471
  tmd: "time:density",
2268
2472
  rtr: "retrospective",
2473
+ ca: "cost:audit",
2269
2474
  rvf: "review:findings",
2270
2475
  ruv: "rule:value",
2271
2476
  ev: "eval",
@@ -2275,6 +2480,7 @@ var ALIASES = {
2275
2480
  lst: "lane:stats",
2276
2481
  lli: "lane:limit",
2277
2482
  lrs: "lane:restart",
2483
+ lwd: "lane:withdraw",
2278
2484
  blr: "backlogrun"
2279
2485
  };
2280
2486
  function canonical_command(name) {
@@ -2287,7 +2493,7 @@ var BASH_SEPARATOR = ": ";
2287
2493
  var WHITESPACE_PATTERN = /\s+/u;
2288
2494
  var SEGMENT_PATTERN = /&&|\|\||;|\|/u;
2289
2495
  var PIPE_PATTERN = /(?<!\|)\|(?!\|)/u;
2290
- var CHAIN_PATTERN = /&&|\|\||;/u;
2496
+ var CHAIN_PATTERN = /&&|\|\||;|\n/u;
2291
2497
  var QUOTED_SPAN_PATTERN = /\\[\s\S]|'[^']*'|"(?:\\[\s\S]|[^"\\])*"/gu;
2292
2498
  var HEREDOC_MARKER = /(?<!<)<<-?\s*(['"]?)(\w+)\1/u;
2293
2499
  var HEREDOC_DELIMITER_GROUP = 2;
@@ -2345,6 +2551,10 @@ function without_heredoc_bodies(command) {
2345
2551
  function unquoted(command) {
2346
2552
  return without_heredoc_bodies(command).replaceAll(QUOTED_SPAN_PATTERN, " ");
2347
2553
  }
2554
+ function blanked(command) {
2555
+ if (HEREDOC_MARKER.test(command)) return void 0;
2556
+ return command.replaceAll(QUOTED_SPAN_PATTERN, (span) => " ".repeat(span.length));
2557
+ }
2348
2558
  function discarded_commands(command) {
2349
2559
  const plain = unquoted(command);
2350
2560
  if (PIPEFAIL_PATTERN.test(plain)) return [];
@@ -2381,22 +2591,35 @@ function drop_redirections(words) {
2381
2591
  }
2382
2592
  return kept;
2383
2593
  }
2384
- function josh_arguments(command) {
2385
- const text = command_words(command_segment(command)).join(" ");
2594
+ function josh_arguments_of_segment(segment) {
2595
+ const text = command_words(segment).join(" ");
2386
2596
  const match = JOSH_PATTERN.exec(text);
2387
2597
  if (match === null) return [];
2388
2598
  const words = text.slice(match[0].length).split(WHITESPACE_PATTERN).filter((word) => word !== "");
2389
2599
  return drop_redirections(words);
2390
2600
  }
2601
+ function josh_arguments(command) {
2602
+ return josh_arguments_of_segment(command_segment(command));
2603
+ }
2604
+ function josh_signature_of_segment(segment) {
2605
+ const name = josh_command_of_segment(segment);
2606
+ if (name === "") return "";
2607
+ return [name, ...josh_arguments_of_segment(segment)].join(" ");
2608
+ }
2609
+ function josh_signatures_of(command) {
2610
+ return unquoted(command).split(SEGMENT_PATTERN).map((segment) => josh_signature_of_segment(segment)).filter((signature) => signature !== "");
2611
+ }
2391
2612
  var time_shell = {
2392
2613
  JOSH_PREFIX,
2393
2614
  bash_command,
2394
2615
  bash_label,
2616
+ blanked,
2395
2617
  command_segment,
2396
2618
  discarded_commands,
2397
2619
  josh_arguments,
2398
2620
  josh_command_of,
2399
2621
  josh_commands_of,
2622
+ josh_signatures_of,
2400
2623
  leading_word,
2401
2624
  unquoted
2402
2625
  };
@@ -2497,78 +2720,6 @@ var time_background = {
2497
2720
  unread_phases
2498
2721
  };
2499
2722
 
2500
- // scripts/git/git-schemas.ts
2501
- import { z as z3 } from "zod";
2502
- var package_name_schema = z3.object({ name: z3.string().min(1) });
2503
- var rollup_item_schema = z3.looseObject({
2504
- // eslint-disable-next-line @typescript-eslint/naming-convention -- GraphQL field name from GitHub API
2505
- __typename: z3.string().optional(),
2506
- state: z3.string().optional(),
2507
- status: z3.string().optional(),
2508
- conclusion: z3.string().optional(),
2509
- name: z3.string().optional(),
2510
- context: z3.string().optional(),
2511
- description: z3.string().nullish()
2512
- });
2513
- var pr_raw_schema = z3.object({
2514
- statusCheckRollup: z3.array(rollup_item_schema).optional(),
2515
- mergeStateStatus: z3.string().optional(),
2516
- reviewDecision: z3.string().optional()
2517
- });
2518
- var pull_comment_schema = z3.object({
2519
- body: z3.string().optional(),
2520
- html_url: z3.string().optional(),
2521
- user: z3.object({ login: z3.string().optional() }).optional()
2522
- });
2523
- var ai_review_pull_comment_schema = z3.object({
2524
- body: z3.string().optional(),
2525
- url: z3.string().optional(),
2526
- author: z3.object({ login: z3.string().optional() }).optional()
2527
- });
2528
- var pr_info_schema = z3.object({
2529
- mergeable: z3.union([z3.boolean(), z3.string()]).nullable().optional(),
2530
- mergeStateStatus: z3.string().nullable().optional(),
2531
- state: z3.string().optional()
2532
- });
2533
- var epic_issue_schema = z3.object({
2534
- number: z3.number(),
2535
- body: z3.string().optional()
2536
- });
2537
- var blocking_issue_schema = z3.object({
2538
- number: z3.number(),
2539
- state: z3.string().optional(),
2540
- repository_url: z3.string().optional()
2541
- });
2542
- var blocked_by_schema = z3.object({
2543
- nodes: z3.array(blocking_issue_schema).default([]),
2544
- totalCount: z3.number().optional()
2545
- }).optional();
2546
- var epic_child_schema = z3.object({
2547
- state: z3.string().optional(),
2548
- blockedBy: blocked_by_schema
2549
- });
2550
- var rest_comment_schema = z3.looseObject({
2551
- body: z3.string().nullish(),
2552
- html_url: z3.string().optional(),
2553
- user: z3.looseObject({ login: z3.string().optional() }).nullish()
2554
- });
2555
- var issue_label_schema = z3.object({ name: z3.string() });
2556
- var epic_subject_schema = z3.object({
2557
- number: z3.number(),
2558
- state: z3.string().optional(),
2559
- labels: z3.array(issue_label_schema).optional(),
2560
- body: z3.string().optional()
2561
- });
2562
- var open_issue_schema = z3.object({
2563
- number: z3.number(),
2564
- title: z3.string(),
2565
- labels: z3.array(issue_label_schema).optional(),
2566
- createdAt: z3.string(),
2567
- blockedBy: blocked_by_schema,
2568
- // Present only on the pickup listing, which reads it for the paths each issue declares.
2569
- body: z3.string().optional()
2570
- });
2571
-
2572
2723
  // scripts/gh/git-pr-checks-parse.ts
2573
2724
  var CHECK_STATUS_PASS = "pass";
2574
2725
  var CHECK_STATUS_PENDING = "pending";
@@ -2747,7 +2898,7 @@ var PrConflictError = class extends Error {
2747
2898
 
2748
2899
  // scripts/lane/lane-ledger.ts
2749
2900
  import { closeSync, constants, openSync, writeSync } from "node:fs";
2750
- import { z as z4 } from "zod";
2901
+ import { z as z3 } from "zod";
2751
2902
  var LEDGER_PREFIX = "josh-lane-ledger-";
2752
2903
  var LEDGER_SUFFIX = ".jsonl";
2753
2904
  var LINE_SEPARATOR = "\n";
@@ -2761,54 +2912,83 @@ var KIND = {
2761
2912
  LOAD: "load",
2762
2913
  DISPATCH: "dispatch",
2763
2914
  STAGE: "stage",
2764
- STOP: "stop"
2915
+ STOP: "stop",
2916
+ OVERLAP: "overlap"
2765
2917
  };
2766
- var merge_schema = z4.object({ kind: z4.literal(KIND.MERGE), at: z4.string(), issue: z4.number() });
2767
- var gate_schema = z4.object({
2768
- kind: z4.literal(KIND.GATE),
2769
- at: z4.string(),
2770
- elapsed_ms: z4.number(),
2771
- is_passed: z4.boolean(),
2918
+ var merge_schema = z3.object({ kind: z3.literal(KIND.MERGE), at: z3.string(), issue: z3.number() });
2919
+ var check_schema = z3.object({
2920
+ label: z3.string(),
2921
+ elapsed_ms: z3.number(),
2922
+ is_passed: z3.boolean(),
2923
+ is_reused: z3.boolean().optional()
2924
+ });
2925
+ var gate_schema = z3.object({
2926
+ kind: z3.literal(KIND.GATE),
2927
+ at: z3.string(),
2928
+ elapsed_ms: z3.number(),
2929
+ is_passed: z3.boolean(),
2772
2930
  // The unit suite's own duration, present when this gate ran it — the one
2773
2931
  // record of a full vitest run `josh metrics` reads, so the suite is never timed twice.
2774
- unit_ms: z4.number().optional(),
2932
+ unit_ms: z3.number().optional(),
2775
2933
  // The whole cores busy outside this gate, beyond the machine's baseline, at its start or its end —
2776
2934
  // whichever was higher. Absent when the machine could not be read.
2777
- external_cores: z4.number().optional()
2935
+ external_cores: z3.number().optional(),
2936
+ // Each check's own duration and verdict, so `josh lane:stats` can name the check a slow gate waited
2937
+ // on. A reused check started no process: its row says so, and its zero is no timing.
2938
+ checks: z3.array(check_schema).optional()
2939
+ });
2940
+ var process_kind_schema = z3.object({
2941
+ kind: z3.string(),
2942
+ cpu_percent: z3.number(),
2943
+ rss_mb: z3.number(),
2944
+ count: z3.number()
2945
+ });
2946
+ var load_schema = z3.object({
2947
+ kind: z3.literal(KIND.LOAD),
2948
+ at: z3.string(),
2949
+ load: z3.number(),
2950
+ available_mb: z3.number().optional(),
2951
+ swapped_mb: z3.number().optional(),
2952
+ lanes: z3.number().optional(),
2953
+ // The heaviest process kinds at the instant of the sample (`lane-process-load.ts`), so the load the
2954
+ // core budget's ledger does not explain can be read back by what made it (joshuafolkken/kit#3874).
2955
+ processes: z3.array(process_kind_schema).optional()
2778
2956
  });
2779
- var load_schema = z4.object({
2780
- kind: z4.literal(KIND.LOAD),
2781
- at: z4.string(),
2782
- load: z4.number(),
2783
- available_mb: z4.number().optional(),
2784
- swapped_mb: z4.number().optional(),
2785
- lanes: z4.number().optional()
2957
+ var dispatch_schema = z3.object({
2958
+ kind: z3.literal(KIND.DISPATCH),
2959
+ at: z3.string(),
2960
+ issue: z3.number()
2786
2961
  });
2787
- var dispatch_schema = z4.object({
2788
- kind: z4.literal(KIND.DISPATCH),
2789
- at: z4.string(),
2790
- issue: z4.number()
2962
+ var stage_schema = z3.object({
2963
+ kind: z3.literal(KIND.STAGE),
2964
+ at: z3.string(),
2965
+ stage: z3.string(),
2966
+ elapsed_ms: z3.number(),
2967
+ issue: z3.number().optional()
2791
2968
  });
2792
- var stage_schema = z4.object({
2793
- kind: z4.literal(KIND.STAGE),
2794
- at: z4.string(),
2795
- stage: z4.string(),
2796
- elapsed_ms: z4.number(),
2797
- issue: z4.number().optional()
2969
+ var stop_schema = z3.object({
2970
+ kind: z3.literal(KIND.STOP),
2971
+ at: z3.string(),
2972
+ issue: z3.number(),
2973
+ reason: z3.string()
2798
2974
  });
2799
- var stop_schema = z4.object({
2800
- kind: z4.literal(KIND.STOP),
2801
- at: z4.string(),
2802
- issue: z4.number(),
2803
- reason: z4.string()
2975
+ var overlap_schema = z3.object({
2976
+ kind: z3.literal(KIND.OVERLAP),
2977
+ at: z3.string(),
2978
+ issue: z3.number(),
2979
+ lanes: z3.array(z3.number()),
2980
+ paths: z3.array(z3.string()),
2981
+ waited_ms: z3.number(),
2982
+ is_cleared: z3.boolean()
2804
2983
  });
2805
- var entry_schema = z4.discriminatedUnion("kind", [
2984
+ var entry_schema = z3.discriminatedUnion("kind", [
2806
2985
  merge_schema,
2807
2986
  gate_schema,
2808
2987
  load_schema,
2809
2988
  dispatch_schema,
2810
2989
  stage_schema,
2811
- stop_schema
2990
+ stop_schema,
2991
+ overlap_schema
2812
2992
  ]);
2813
2993
  function target_of(repository) {
2814
2994
  return stamp_file.stamp_path(LEDGER_PREFIX, repository, LEDGER_SUFFIX);
@@ -2822,19 +3002,19 @@ async function target() {
2822
3002
  return void 0;
2823
3003
  }
2824
3004
  }
2825
- function append(path4, entry) {
2826
- const descriptor = openSync(path4, APPEND_FLAGS, LEDGER_FILE_MODE);
3005
+ function append(path5, entry) {
3006
+ const descriptor = openSync(path5, APPEND_FLAGS, LEDGER_FILE_MODE);
2827
3007
  try {
2828
- if (stamp_file.is_own_regular_file(path4)) {
3008
+ if (stamp_file.is_own_regular_file(path5)) {
2829
3009
  writeSync(descriptor, `${JSON.stringify(entry)}${LINE_SEPARATOR}`);
2830
3010
  }
2831
3011
  } finally {
2832
3012
  closeSync(descriptor);
2833
3013
  }
2834
3014
  }
2835
- async function record(entry, path4) {
3015
+ async function record(entry, path5) {
2836
3016
  try {
2837
- const destination = path4 ?? await target();
3017
+ const destination = path5 ?? await target();
2838
3018
  if (destination !== void 0) append(destination, entry);
2839
3019
  } catch {
2840
3020
  }
@@ -2842,20 +3022,23 @@ async function record(entry, path4) {
2842
3022
  function now_iso() {
2843
3023
  return (/* @__PURE__ */ new Date()).toISOString();
2844
3024
  }
2845
- async function record_gate(path4, gate) {
2846
- await record({ kind: KIND.GATE, at: now_iso(), ...gate }, path4);
3025
+ async function record_gate(path5, gate) {
3026
+ await record({ kind: KIND.GATE, at: now_iso(), ...gate }, path5);
2847
3027
  }
2848
3028
  async function record_merge(issue) {
2849
3029
  await record({ kind: KIND.MERGE, at: now_iso(), issue });
2850
3030
  }
2851
- async function record_dispatch(issue, path4) {
2852
- await record({ kind: KIND.DISPATCH, at: now_iso(), issue }, path4);
3031
+ async function record_dispatch(issue, path5) {
3032
+ await record({ kind: KIND.DISPATCH, at: now_iso(), issue }, path5);
2853
3033
  }
2854
- async function record_stage(stage, path4) {
2855
- await record({ kind: KIND.STAGE, at: now_iso(), ...stage }, path4);
3034
+ async function record_stage(stage, path5) {
3035
+ await record({ kind: KIND.STAGE, at: now_iso(), ...stage }, path5);
2856
3036
  }
2857
- async function record_stop(stop, path4) {
2858
- await record({ kind: KIND.STOP, at: now_iso(), ...stop }, path4);
3037
+ async function record_stop(stop, path5) {
3038
+ await record({ kind: KIND.STOP, at: now_iso(), ...stop }, path5);
3039
+ }
3040
+ async function record_overlap(overlap, path5) {
3041
+ await record({ kind: KIND.OVERLAP, at: now_iso(), ...overlap }, path5);
2859
3042
  }
2860
3043
  function parse_line2(line) {
2861
3044
  return entry_schema.safeParse(json_value.parse_or_undefined(line)).data;
@@ -2863,8 +3046,8 @@ function parse_line2(line) {
2863
3046
  function is_present(entry) {
2864
3047
  return entry !== void 0;
2865
3048
  }
2866
- function read_entries(path4) {
2867
- const raw = stamp_file.read_stamp_text(path4);
3049
+ function read_entries(path5) {
3050
+ const raw = stamp_file.read_stamp_text(path5);
2868
3051
  if (raw === void 0) return [];
2869
3052
  return raw.split(LINE_SEPARATOR).map((line) => parse_line2(line.trim())).filter(is_present);
2870
3053
  }
@@ -2877,6 +3060,7 @@ var lane_ledger = {
2877
3060
  record_dispatch,
2878
3061
  record_gate,
2879
3062
  record_merge,
3063
+ record_overlap,
2880
3064
  record_stage,
2881
3065
  record_stop,
2882
3066
  target,
@@ -3043,9 +3227,9 @@ function read_stages(text) {
3043
3227
  var time_followup_stage = { NO_STAGES, read_stages };
3044
3228
 
3045
3229
  // scripts/time-runtime/time-reported-failure.ts
3046
- import { z as z5 } from "zod";
3047
- var TEXT_BLOCK_SCHEMA = z5.object({ text: z5.string().nullish() });
3048
- var CONTENT_SCHEMA = z5.union([z5.string(), z5.array(z5.unknown())]);
3230
+ import { z as z4 } from "zod";
3231
+ var TEXT_BLOCK_SCHEMA = z4.object({ text: z4.string().nullish() });
3232
+ var CONTENT_SCHEMA = z4.union([z4.string(), z4.array(z4.unknown())]);
3049
3233
  var LINE_SEPARATOR3 = "\n";
3050
3234
  function block_text(block) {
3051
3235
  const parsed = TEXT_BLOCK_SCHEMA.safeParse(block);
@@ -3582,38 +3766,38 @@ function check_key(josh_command, command) {
3582
3766
  var time_single_check = { NO_CHECK, check_key };
3583
3767
 
3584
3768
  // scripts/time-runtime/time-transcript-line.ts
3585
- import { z as z6 } from "zod";
3586
- var BLOCK_SCHEMA2 = z6.object({
3587
- type: z6.string().nullish(),
3588
- name: z6.string().nullish(),
3589
- id: z6.string().nullish(),
3590
- tool_use_id: z6.string().nullish(),
3591
- input: z6.unknown().nullish(),
3769
+ import { z as z5 } from "zod";
3770
+ var BLOCK_SCHEMA2 = z5.object({
3771
+ type: z5.string().nullish(),
3772
+ name: z5.string().nullish(),
3773
+ id: z5.string().nullish(),
3774
+ tool_use_id: z5.string().nullish(),
3775
+ input: z5.unknown().nullish(),
3592
3776
  // Whether the harness wrote this result back as a failure. Present on a
3593
3777
  // `tool_result` block and on nothing else, and absent even there for the tools that never report
3594
3778
  // one — which is why it is read as three answers rather than as a boolean with a default.
3595
- is_error: z6.boolean().nullish(),
3779
+ is_error: z5.boolean().nullish(),
3596
3780
  // What the call printed. Read only to recover the outcome a pipeline threw away:
3597
3781
  // it is kept as flattened text rather than as blocks, because that is
3598
3782
  // the whole of what `time-reported-failure.ts` asks of it.
3599
- content: z6.unknown().nullish()
3783
+ content: z5.unknown().nullish()
3600
3784
  });
3601
- var CONTENT_SCHEMA2 = z6.union([z6.string(), z6.array(BLOCK_SCHEMA2)]);
3602
- var MESSAGE_SCHEMA2 = z6.object({ id: z6.string().nullish(), content: CONTENT_SCHEMA2.nullish() });
3603
- var LINE_SCHEMA = z6.object({
3604
- type: z6.string().nullish(),
3605
- timestamp: z6.string().nullish(),
3785
+ var CONTENT_SCHEMA2 = z5.union([z5.string(), z5.array(BLOCK_SCHEMA2)]);
3786
+ var MESSAGE_SCHEMA2 = z5.object({ id: z5.string().nullish(), content: CONTENT_SCHEMA2.nullish() });
3787
+ var LINE_SCHEMA = z5.object({
3788
+ type: z5.string().nullish(),
3789
+ timestamp: z5.string().nullish(),
3606
3790
  // The branch the line was written on, which is where the issue number lives — `josh git` names a
3607
3791
  // branch `<N>-<slug>`. Read here rather than re-parsed by a second reader so `josh time --issue`
3608
3792
  // and `josh cost --issue` answer from the same field.
3609
- gitBranch: z6.string().nullish(),
3793
+ gitBranch: z5.string().nullish(),
3610
3794
  // The two fields outside `message` that the harness writes an end-of-task notice on.
3611
3795
  // **Both are read as `unknown` rather than as strings**: a schema
3612
3796
  // insisting on a string here would reject every line whose `content` is a list of blocks — which is
3613
3797
  // most of them — and a rejected line is dropped from the timeline entirely, so the shares would stop
3614
3798
  // reconstructing the elapsed time. The shape is checked at the read below instead.
3615
- content: z6.unknown().nullish(),
3616
- attachment: z6.unknown().nullish(),
3799
+ content: z5.unknown().nullish(),
3800
+ attachment: z5.unknown().nullish(),
3617
3801
  message: MESSAGE_SCHEMA2.nullish()
3618
3802
  });
3619
3803
  var UNKNOWN_BRANCH = "";
@@ -3724,6 +3908,7 @@ var NO_CALL = {
3724
3908
  label: "",
3725
3909
  josh_command: "",
3726
3910
  josh_commands: [],
3911
+ josh_signatures: [],
3727
3912
  check_key: time_single_check.NO_CHECK,
3728
3913
  marker: time_markers.NO_MARKER,
3729
3914
  message_id: NO_MESSAGE_ID2,
@@ -3736,6 +3921,7 @@ var UNKNOWN_CALL = {
3736
3921
  label: UNKNOWN_TOOL,
3737
3922
  josh_command: "",
3738
3923
  josh_commands: [],
3924
+ josh_signatures: [],
3739
3925
  check_key: time_single_check.NO_CHECK,
3740
3926
  marker: time_markers.NO_MARKER,
3741
3927
  message_id: NO_MESSAGE_ID2,
@@ -3749,6 +3935,7 @@ function non_bash_call(name, input, message_id) {
3749
3935
  label: name,
3750
3936
  josh_command: "",
3751
3937
  josh_commands: [],
3938
+ josh_signatures: [],
3752
3939
  check_key: time_single_check.NO_CHECK,
3753
3940
  marker: time_markers.tool_marker(name, input),
3754
3941
  message_id,
@@ -3768,6 +3955,7 @@ function bash_call(input, message_id) {
3768
3955
  label: time_shell.bash_label(command),
3769
3956
  josh_command,
3770
3957
  josh_commands: time_shell.josh_commands_of(command),
3958
+ josh_signatures: time_shell.josh_signatures_of(command),
3771
3959
  check_key: time_single_check.check_key(josh_command, command),
3772
3960
  marker: time_markers.bash_marker(command),
3773
3961
  message_id,
@@ -3890,6 +4078,7 @@ function to_spans(events, finished) {
3890
4078
  label: event.label,
3891
4079
  josh_command: event.josh_command,
3892
4080
  josh_commands: event.josh_commands,
4081
+ josh_signatures: event.josh_signatures,
3893
4082
  check_key: event.check_key,
3894
4083
  marker: event.marker,
3895
4084
  is_bundleable: event.is_bundleable,
@@ -4345,7 +4534,7 @@ var NAMED_CANDIDATE_COUNT = 3;
4345
4534
  var SEQUENCE_BEFORE_LIMIT = CONSECUTIVE_LIMIT - ONE_TURN;
4346
4535
  var RULE_POINTER = "`prompts/collaboration-workflow/turn-batching.md`";
4347
4536
  var STREAK = `${String(CONSECUTIVE_LIMIT)} single-call turns in a row`;
4348
- var REASON = `\u26D4 batching: ${STREAK}. Reissue this with the calls that do not need its result \u2014 the test is whether a call's input needs another call's result, not what kind of call it is (edits are covered exactly as reads), and it never authorizes weakening a verification gate or a review. Already batching, or nothing to pair? Reissue it unchanged; this fires once per streak. ${RULE_POINTER}`;
4537
+ var REASON = `\u26D4 batching: ${STREAK}. Reissue this with the calls that do not need its result \u2014 the test is whether a call's input needs another call's result, not what kind of call it is (edits are covered exactly as reads); it never authorizes weakening a verification gate or a review. Nothing to pair? Reissue it unchanged. ${RULE_POINTER}`;
4349
4538
  var NOTICE = `\u{1F4A1} batching: ${STREAK}; the Write proceeds. Issue the writes that do not need its result in one turn, never by weakening a gate or a review. ${RULE_POINTER}`;
4350
4539
  var LANE_NOTICE = `\u{1F4A1} batching: ${STREAK}; the call proceeds. Pair independent calls. ${RULE_POINTER}`;
4351
4540
  var WHOLE_FILE_WRITE_TOOL = "Write";
@@ -4410,7 +4599,7 @@ function describe_span(span) {
4410
4599
  }
4411
4600
  var FOLD_MINIMUM = 2;
4412
4601
  function read_targets(sequence) {
4413
- const paths = sequence.filter((span) => span.is_bundleable && !span.is_writing).map((span) => span.targets[0] ?? "").filter((path4) => path4 !== "" && time_bundle_call.has_extension(path4));
4602
+ const paths = sequence.filter((span) => span.is_bundleable && !span.is_writing).map((span) => span.targets[0] ?? "").filter((path5) => path5 !== "" && time_bundle_call.has_extension(path5));
4414
4603
  return [...new Set(paths)];
4415
4604
  }
4416
4605
  function read_fold_directive(sequence) {
@@ -4419,7 +4608,7 @@ function read_fold_directive(sequence) {
4419
4608
  return ` Fold them into one call: \`pnpm josh read:files ${paths.join(" ")}\`.`;
4420
4609
  }
4421
4610
  function write_targets(sequence) {
4422
- const paths = sequence.filter((span) => span.label === EDIT_TOOL).map((span) => span.targets[0] ?? "").filter((path4) => path4 !== "" && time_bundle_call.has_extension(path4));
4611
+ const paths = sequence.filter((span) => span.label === EDIT_TOOL).map((span) => span.targets[0] ?? "").filter((path5) => path5 !== "" && time_bundle_call.has_extension(path5));
4423
4612
  return [...new Set(paths)];
4424
4613
  }
4425
4614
  function write_fold_directive(sequence) {
@@ -4471,7 +4660,7 @@ var time_batch_guard = {
4471
4660
  };
4472
4661
 
4473
4662
  // scripts/time-runtime/time-hook-transcript.ts
4474
- import path3 from "node:path";
4663
+ import path4 from "node:path";
4475
4664
  var { TRANSCRIPT_EXTENSION } = cost_transcript;
4476
4665
  var AGENT_PREFIX = "agent-";
4477
4666
  var AGENT_ID_PATTERN = /^[\w-]+$/u;
@@ -4479,14 +4668,15 @@ function is_agent_id(value) {
4479
4668
  return typeof value === "string" && AGENT_ID_PATTERN.test(value);
4480
4669
  }
4481
4670
  function fork_path(transcript_path, agent_id) {
4482
- const directory = path3.dirname(transcript_path);
4483
- const session_id = path3.basename(transcript_path, TRANSCRIPT_EXTENSION);
4671
+ const directory = path4.dirname(transcript_path);
4672
+ const session_id = path4.basename(transcript_path, TRANSCRIPT_EXTENSION);
4484
4673
  const file_name = `${AGENT_PREFIX}${agent_id}${TRANSCRIPT_EXTENSION}`;
4485
- return path3.join(cost_transcript.unit_directory(directory, session_id), file_name);
4674
+ return path4.join(cost_transcript.unit_directory(directory, session_id), file_name);
4486
4675
  }
4487
4676
  function transcript_of(transcript_path, agent_id) {
4488
- if (!is_agent_id(agent_id)) return transcript_path;
4489
- if (!transcript_path.endsWith(TRANSCRIPT_EXTENSION)) return transcript_path;
4677
+ if (!is_agent_id(agent_id) || !transcript_path.endsWith(TRANSCRIPT_EXTENSION)) {
4678
+ return transcript_path;
4679
+ }
4490
4680
  return fork_path(transcript_path, agent_id);
4491
4681
  }
4492
4682
  var time_hook_transcript = {
@@ -4496,7 +4686,7 @@ var time_hook_transcript = {
4496
4686
 
4497
4687
  // scripts/time-runtime/time-density-hook.ts
4498
4688
  import { closeSync as closeSync2, fstatSync, openSync as openSync2, readSync } from "node:fs";
4499
- import { z as z7 } from "zod";
4689
+ import { z as z6 } from "zod";
4500
4690
 
4501
4691
  // scripts/time-runtime/time-density.ts
4502
4692
  var MIN_ROUND_TRIPS = 10;
@@ -4552,15 +4742,15 @@ var TAIL_BYTES = 262144;
4552
4742
  var FILE_START = 0;
4553
4743
  var NOTICE_PREFIX = "josh-density-notice-";
4554
4744
  var NEVER_MS = 0;
4555
- var payload_schema = z7.object({
4556
- transcript_path: z7.string().min(1).optional(),
4745
+ var payload_schema = z6.object({
4746
+ transcript_path: z6.string().min(1).optional(),
4557
4747
  // **`nullish`, not `optional`.** A payload spelling the absent agent as `null` has to read as "no
4558
4748
  // fork"; rejected, it would fail `safeParse` for the whole payload and silence the line for every
4559
4749
  // edit of the main line — the opposite of this module's stated failure direction.
4560
- agent_id: z7.string().nullish()
4750
+ agent_id: z6.string().nullish()
4561
4751
  });
4562
- var notice_schema = z7.object({ notified_at_ms: z7.number() });
4563
- var call_schema = z7.object({ tool_name: z7.string().min(1), tool_input: z7.unknown() });
4752
+ var notice_schema = z6.object({ notified_at_ms: z6.number() });
4753
+ var call_schema = z6.object({ tool_name: z6.string().min(1), tool_input: z6.unknown() });
4564
4754
  function parse_transcript_path(raw_payload) {
4565
4755
  const parsed = payload_schema.safeParse(JSON.parse(raw_payload));
4566
4756
  if (!parsed.success || parsed.data.transcript_path === void 0) return void 0;
@@ -4627,9 +4817,6 @@ export {
4627
4817
  ESLINT_EDIT_CACHE_FLAGS,
4628
4818
  time_shell,
4629
4819
  time_bundle_call,
4630
- blocking_issue_schema,
4631
- rest_comment_schema,
4632
- open_issue_schema,
4633
4820
  CHECK_STATUS_PENDING,
4634
4821
  CHECK_STATUS_FAIL,
4635
4822
  read_string,