@joshuafolkken/kit 1.1471.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 (267) hide show
  1. package/.claude/settings.json +6 -6
  2. package/.claude/skills/epic-commands/SKILL.md +2 -14
  3. package/.claude/skills/epic-commands/epic-bundle.md +9 -5
  4. package/.claude/skills/epic-commands/execution-waves.md +1 -2
  5. package/.claude/skills/verify-ui/SKILL.md +1 -2
  6. package/.claude/skills/workflow-commands/SKILL.md +7 -9
  7. package/.claude/skills/workflow-commands/background-commands.md +3 -7
  8. package/.claude/skills/workflow-commands/backlogrun-child.md +3 -10
  9. package/.claude/skills/workflow-commands/backlogrun-lanes.md +6 -15
  10. package/.claude/skills/workflow-commands/backlogrun-park.md +2 -8
  11. package/.claude/skills/workflow-commands/backlogrun-progress.md +10 -19
  12. package/.claude/skills/workflow-commands/backlogrun-recovery.md +8 -8
  13. package/.claude/skills/workflow-commands/backlogrun-steps.md +2 -10
  14. package/.claude/skills/workflow-commands/backlogrun.md +31 -50
  15. package/.claude/skills/workflow-commands/chain-rule.md +7 -6
  16. package/.claude/skills/workflow-commands/delegation.md +12 -8
  17. package/.claude/skills/workflow-commands/entry-sequence.md +1 -3
  18. package/.claude/skills/workflow-commands/followup.md +3 -6
  19. package/.claude/skills/workflow-commands/fullrun-steps.md +3 -5
  20. package/.claude/skills/workflow-commands/fullrun.md +4 -6
  21. package/.claude/skills/workflow-commands/halfrun.md +3 -7
  22. package/.claude/skills/workflow-commands/into-target.md +1 -2
  23. package/.claude/skills/workflow-commands/issue-comments.md +3 -3
  24. package/.claude/skills/workflow-commands/issue-scout.md +3 -2
  25. package/.claude/skills/workflow-commands/latest-gate.md +2 -6
  26. package/.claude/skills/workflow-commands/needs-human-review.md +2 -5
  27. package/.claude/skills/workflow-commands/observation-filing.md +0 -14
  28. package/.claude/skills/workflow-commands/observation-ledger.md +4 -9
  29. package/.claude/skills/workflow-commands/pre-gate-cut.md +11 -16
  30. package/.claude/skills/workflow-commands/prerequisite.md +1 -2
  31. package/.claude/skills/workflow-commands/progress-watcher.md +2 -7
  32. package/.claude/skills/workflow-commands/prrun.md +3 -5
  33. package/.claude/skills/workflow-commands/retrospective.md +4 -4
  34. package/.claude/skills/workflow-commands/split-assessment.md +1 -3
  35. package/.claude/skills/workflow-commands/target-repository.md +1 -2
  36. package/.claude/skills/workflow-commands/working-tree-hold.md +2 -4
  37. package/.codex/hooks.json +6 -6
  38. package/.github/actions/setup-node/action.yml +1 -1
  39. package/.github/workflows/ci.yml +1 -1
  40. package/.github/workflows/publish.yml +2 -2
  41. package/CLAUDE.md +1 -1
  42. package/cspell/index.yaml +7 -0
  43. package/dist/CLAUDE.md +1 -1
  44. package/dist/commands/document-section-cli.js +67 -34
  45. package/dist/hooks/batch-guard.js +6 -6
  46. package/dist/hooks/chunk-4KKD2UTK.js +66 -0
  47. package/dist/hooks/{chunk-J47HU5H5.js → chunk-6TS2JD6D.js} +3 -3
  48. package/dist/hooks/chunk-7DSBJVFH.js +50 -0
  49. package/dist/hooks/{chunk-YHBZAJSV.js → chunk-A65Y4YOR.js} +3 -3
  50. package/dist/hooks/{chunk-E7OIKX5Z.js → chunk-APUO6U4J.js} +85 -7
  51. package/dist/hooks/{chunk-IX6TTTI6.js → chunk-EFXE6C37.js} +8 -22
  52. package/dist/hooks/chunk-GP4IEC4O.js +20 -0
  53. package/dist/hooks/{chunk-SHGPANFB.js → chunk-JP45SOCJ.js} +3 -3
  54. package/dist/hooks/{chunk-K4EOONYM.js → chunk-LAOV7NLM.js} +1 -1
  55. package/dist/hooks/chunk-M4HGZV7U.js +83 -0
  56. package/dist/hooks/{chunk-KC5AK2GF.js → chunk-O6GDRNJB.js} +3 -3
  57. package/dist/hooks/{chunk-YYPI7SJZ.js → chunk-PF2NZL2A.js} +5 -5
  58. package/dist/hooks/{chunk-H5HVWYXK.js → chunk-PTICX4OL.js} +3 -4
  59. package/dist/hooks/{chunk-IIGFYXKC.js → chunk-RT27QRJW.js} +581 -1438
  60. package/dist/hooks/{chunk-43ZHT22P.js → chunk-SYOTDYCI.js} +1127 -1089
  61. package/dist/hooks/{chunk-AS74OU3D.js → chunk-TNUMY6NL.js} +1507 -1336
  62. package/dist/hooks/chunk-UPUZF3AN.js +279 -0
  63. package/dist/hooks/{chunk-EUMACUK2.js → chunk-WK6SMBIC.js} +1948 -806
  64. package/dist/hooks/codex-hook-adapter.js +17 -14
  65. package/dist/hooks/duplicate-read-guard.js +7 -7
  66. package/dist/hooks/format-edited.js +5 -4
  67. package/dist/hooks/inputs.json +317 -264
  68. package/dist/hooks/investigation-guard.js +8 -11
  69. package/dist/hooks/pretool-guard.js +16 -13
  70. package/dist/hooks/session-lang.js +370 -25
  71. package/dist/hooks/stop-guard.js +2576 -176
  72. package/dist/josh.js +2124 -277
  73. package/lefthook/base.yml +5 -3
  74. package/package.json +12 -10
  75. package/prompts/collaboration-workflow/file-edits.md +1 -3
  76. package/prompts/collaboration-workflow/gh-rest.md +1 -2
  77. package/prompts/collaboration-workflow/issue-citation.md +2 -4
  78. package/prompts/collaboration-workflow/issue-template.md +10 -2
  79. package/prompts/collaboration-workflow/lane-instructions.md +35 -0
  80. package/prompts/collaboration-workflow/operating-rules.md +2 -6
  81. package/prompts/collaboration-workflow/output-bounds.md +3 -3
  82. package/prompts/collaboration-workflow/overview.md +1 -1
  83. package/prompts/collaboration-workflow/principles.md +14 -8
  84. package/prompts/collaboration-workflow/report-format.md +9 -0
  85. package/prompts/collaboration-workflow/residency.md +12 -8
  86. package/prompts/collaboration-workflow/rule-delivery.md +3 -11
  87. package/prompts/collaboration-workflow/shell-body.md +6 -6
  88. package/prompts/collaboration-workflow/turn-batching.md +5 -5
  89. package/prompts/collaboration-workflow/upstream-interrupt.md +2 -2
  90. package/prompts/collaboration-workflow/wip-cap.md +6 -6
  91. package/prompts/collaboration-workflow.md +1 -0
  92. package/prompts/review.md +4 -5
  93. package/prompts/testing-guide.md +2 -2
  94. package/scripts/agent/agent-argv.ts +20 -4
  95. package/scripts/agent/agent-role-profile.ts +101 -45
  96. package/scripts/agent/agent-safe-text.ts +60 -0
  97. package/scripts/agent/claude-agent-argv.ts +14 -0
  98. package/scripts/agent/codex-agent-argv.ts +2 -0
  99. package/scripts/agent/lane-instructions.ts +144 -0
  100. package/scripts/backlog/backlog-budget.ts +4 -4
  101. package/scripts/backlog/backlog-drive-named.ts +1 -1
  102. package/scripts/backlog/backlog-drive-restore.ts +9 -1
  103. package/scripts/backlog/backlog-occupied.ts +56 -0
  104. package/scripts/backlog/backlog-ready.ts +47 -10
  105. package/scripts/delegation/duplicate-read-guard.ts +2 -2
  106. package/scripts/delegation/investigation-guard.ts +15 -65
  107. package/scripts/delegation/investigation-reads.ts +0 -1
  108. package/scripts/document/document-byte-budget.ts +6 -5
  109. package/scripts/document/document-section-cli.ts +17 -5
  110. package/scripts/document/entry-read-budget.ts +2 -2
  111. package/scripts/document/read-set-cli.ts +4 -1
  112. package/scripts/epic/epic-audit-orphans.ts +5 -8
  113. package/scripts/epic/epic-audit.ts +52 -1
  114. package/scripts/epic/epic-bundle-evidence.ts +49 -26
  115. package/scripts/epic/epic-bundle-referenced.ts +14 -3
  116. package/scripts/epic/epic-bundle.ts +17 -9
  117. package/scripts/followup/git-followup-workflow.ts +19 -1
  118. package/scripts/gate/check-inputs.ts +129 -0
  119. package/scripts/gate/check-reuse.ts +162 -0
  120. package/scripts/gate/core-budget.ts +93 -44
  121. package/scripts/gate/core-wait.ts +130 -0
  122. package/scripts/gate/free-cores.ts +49 -0
  123. package/scripts/gate/gate-ledger.ts +17 -3
  124. package/scripts/gate/gate-plan.ts +23 -1
  125. package/scripts/gate/gate-report.ts +15 -2
  126. package/scripts/gate/gate-run-steps.ts +150 -0
  127. package/scripts/gate/held-mark.ts +55 -0
  128. package/scripts/gate/machine-capacity.ts +70 -11
  129. package/scripts/gate/pre-commit-type-check.ts +14 -1
  130. package/scripts/gate/verification-gate.ts +58 -125
  131. package/scripts/gh/git-gh-issue-rest.ts +1 -0
  132. package/scripts/gh/git-gh-issue-write.ts +24 -2
  133. package/scripts/gh/git-pr.ts +13 -1
  134. package/scripts/git/main-merge-regenerate.ts +78 -0
  135. package/scripts/git/main-merge.ts +14 -5
  136. package/scripts/hooks/batch-guard.ts +5 -8
  137. package/scripts/hooks/pre-commit-lint.ts +91 -0
  138. package/scripts/hooks/pre-push-unit.ts +1 -1
  139. package/scripts/hooks/pretool-guard.ts +27 -52
  140. package/scripts/hooks/reserved-run.ts +1 -0
  141. package/scripts/hooks/run-hook.sh +10 -12
  142. package/scripts/hooks/stop-actions.ts +204 -0
  143. package/scripts/hooks/stop-guard.ts +62 -21
  144. package/scripts/init/hook-launch.ts +12 -7
  145. package/scripts/issue/expected-effect-lint.ts +59 -0
  146. package/scripts/issue/issue-cite-cli.ts +35 -15
  147. package/scripts/issue/issue-classification.ts +1 -1
  148. package/scripts/issue/issue-lint-cli.ts +5 -3
  149. package/scripts/issue/issue-scout-cli.ts +19 -14
  150. package/scripts/issue/markdown-section.ts +19 -5
  151. package/scripts/josh/command-usage.ts +110 -0
  152. package/scripts/josh/josh-command-map.ts +2 -0
  153. package/scripts/josh/josh-command-types.ts +55 -9
  154. package/scripts/josh/josh-commands-ai.ts +13 -0
  155. package/scripts/josh/josh-commands-development.ts +1 -1
  156. package/scripts/josh/josh-commands-hooks.ts +6 -0
  157. package/scripts/josh/josh-commands-lane.ts +10 -1
  158. package/scripts/josh/josh-commands-workflow.ts +5 -0
  159. package/scripts/josh/josh-logic.ts +6 -15
  160. package/scripts/josh/josh-usage-automation.ts +121 -0
  161. package/scripts/josh/josh-usage-backlog.ts +180 -0
  162. package/scripts/josh/josh-usage-developer.ts +230 -0
  163. package/scripts/josh/josh-usage-hooks.ts +38 -0
  164. package/scripts/josh/josh-usage-issue.ts +205 -0
  165. package/scripts/josh/josh-usage-review.ts +101 -0
  166. package/scripts/josh/josh-usage-run.ts +252 -0
  167. package/scripts/josh/josh-usage-session.ts +238 -0
  168. package/scripts/josh/josh-usage-workflow.ts +212 -0
  169. package/scripts/josh/josh.ts +14 -5
  170. package/scripts/josh/package-directory.ts +20 -0
  171. package/scripts/lane/lane-capacity.ts +8 -2
  172. package/scripts/lane/lane-check-stats.ts +56 -0
  173. package/scripts/lane/lane-child-invocation.ts +35 -4
  174. package/scripts/lane/lane-dispatch.ts +3 -3
  175. package/scripts/lane/lane-guard-policy.ts +2 -2
  176. package/scripts/lane/lane-handoff.ts +8 -1
  177. package/scripts/lane/lane-install.ts +12 -4
  178. package/scripts/lane/lane-ledger.ts +43 -1
  179. package/scripts/lane/lane-limit-cli.ts +8 -9
  180. package/scripts/lane/lane-measure-cli.ts +2 -0
  181. package/scripts/lane/lane-preserve.ts +29 -1
  182. package/scripts/lane/lane-registry.ts +9 -7
  183. package/scripts/lane/lane-restart.ts +5 -26
  184. package/scripts/lane/lane-session-action.ts +146 -0
  185. package/scripts/lane/lane-session-broker.ts +57 -138
  186. package/scripts/lane/lane-withdraw-cli.ts +71 -0
  187. package/scripts/lane/lane-withdraw.ts +182 -0
  188. package/scripts/lint/format.ts +4 -4
  189. package/scripts/lint/lint-parallel.ts +10 -3
  190. package/scripts/lint/lint-related-scope.ts +4 -1
  191. package/scripts/notify/telegram-test.ts +10 -5
  192. package/scripts/report/report-format-reference.ts +2 -0
  193. package/scripts/review/acceptance-map-check.ts +61 -0
  194. package/scripts/review/acceptance-map.ts +205 -0
  195. package/scripts/review/review-brief-cli.ts +1 -1
  196. package/scripts/rules/agent-model.ts +59 -0
  197. package/scripts/rules/delivered-rules.ts +11 -2
  198. package/scripts/rules/filing-cap.ts +1 -0
  199. package/scripts/rules/firing-point.ts +6 -5
  200. package/scripts/rules/guard-remedy.ts +117 -0
  201. package/scripts/rules/issue-comments.ts +67 -6
  202. package/scripts/rules/last-prompt.ts +50 -5
  203. package/scripts/rules/oracle-list-cli.ts +7 -0
  204. package/scripts/rules/piped-verification.ts +3 -1
  205. package/scripts/rules/rule-list.ts +4 -1
  206. package/scripts/rules/rule-registry-rows.ts +10 -0
  207. package/scripts/rules/rule-registry.ts +7 -4
  208. package/scripts/rules/rule-rewrite.ts +114 -0
  209. package/scripts/rules/rule-value.ts +7 -1
  210. package/scripts/rules/shell-segments.ts +15 -0
  211. package/scripts/rules/stop-decision.ts +77 -0
  212. package/scripts/rules/stop-rules.ts +147 -47
  213. package/scripts/rules/tail-commands.ts +15 -1
  214. package/scripts/run/board/run-board-cli.ts +2 -0
  215. package/scripts/run/board/run-board-closed.ts +8 -2
  216. package/scripts/run/board/run-board-github.ts +9 -1
  217. package/scripts/run/board/run-board-header.ts +4 -1
  218. package/scripts/run/board/run-board-labels.ts +2 -0
  219. package/scripts/run/board/run-board-layout.ts +1 -1
  220. package/scripts/run/board/run-board-read.ts +16 -0
  221. package/scripts/run/board/run-board-reason.ts +10 -1
  222. package/scripts/run/board/run-board-render-legend.ts +1 -0
  223. package/scripts/run/board/run-board-render-slot.ts +1 -1
  224. package/scripts/run/board/run-board-render-waits.ts +41 -0
  225. package/scripts/run/board/run-board-render.ts +6 -1
  226. package/scripts/run/board/run-board-row-yield.ts +1 -0
  227. package/scripts/run/board/run-board-state.ts +5 -0
  228. package/scripts/run/board/run-board-status.ts +72 -21
  229. package/scripts/run/board/run-board-tick.ts +5 -1
  230. package/scripts/run/cut/run-cut-cli.ts +3 -2
  231. package/scripts/run/cut/run-cut-report.ts +16 -0
  232. package/scripts/run/detached-launch.ts +23 -4
  233. package/scripts/run/entry/run-entry-cli.ts +8 -1
  234. package/scripts/run/event/run-event-render.ts +1 -0
  235. package/scripts/run/event/run-event-scope.ts +19 -1
  236. package/scripts/run/event/run-event-stream-emit.ts +13 -6
  237. package/scripts/run/event/run-event-stream.ts +4 -0
  238. package/scripts/run/hold/run-hold-session.ts +46 -0
  239. package/scripts/run/hold/run-hold.ts +7 -1
  240. package/scripts/run/launch-priority.ts +32 -0
  241. package/scripts/run/merge/run-merge-cli.ts +16 -65
  242. package/scripts/run/merge/run-merge-signals.ts +99 -0
  243. package/scripts/run/merge/run-merge-steps.ts +11 -1
  244. package/scripts/run/merge/run-merge.ts +21 -9
  245. package/scripts/run/run-liveness.ts +1 -1
  246. package/scripts/run/run-poll-hook.ts +157 -0
  247. package/scripts/run/run-step-cli.ts +22 -15
  248. package/scripts/run/run-step-section.ts +85 -0
  249. package/scripts/run/ship/run-ship-cli.ts +5 -12
  250. package/scripts/run/ship/run-ship-detach.ts +9 -0
  251. package/scripts/run/ship/run-ship-next.ts +10 -2
  252. package/scripts/run/ship/run-ship-overlap.ts +195 -0
  253. package/scripts/run/ship/run-ship-pre-detach.ts +33 -15
  254. package/scripts/run/ship/run-ship-preflight.ts +28 -2
  255. package/scripts/run/ship/run-ship-return.ts +31 -7
  256. package/scripts/run/ship/run-ship-review-steps.ts +12 -10
  257. package/scripts/run/ship/run-ship-steps.ts +1 -1
  258. package/scripts/run/ship/run-ship-stop-context.ts +206 -0
  259. package/scripts/run/ship/run-ship-sync.ts +14 -6
  260. package/scripts/test/test-e2e-guard.ts +30 -5
  261. package/scripts/test/test-unit-guard.ts +16 -1
  262. package/scripts/test/unit-worker-share.ts +26 -4
  263. package/scripts/time-runtime/time-shell.ts +29 -4
  264. package/scripts/time-runtime/time-spans.ts +1 -0
  265. package/scripts/time-runtime/time-tool-call.ts +7 -0
  266. package/templates/gitignore +6 -2
  267. package/dist/hooks/chunk-K4LMBQC2.js +0 -68
@@ -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-E7OIKX5Z.js";
11
+ } from "./chunk-APUO6U4J.js";
10
12
  import {
11
13
  PLATFORM_TEMP_ROOT,
12
14
  PROBE_TIMEOUT_MS,
@@ -100,1015 +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 || vitest_arguments.some((argument) => is_worker_flag(argument))) {
866
- return [];
867
- }
868
- return [`${WORKER_FLAG}=${String(share)}`];
869
- }
870
- function mark_run_started(target2) {
871
- try {
872
- stamp_file.write_stamp(target2, process_identity.own_fields());
873
- } catch {
874
- }
875
- }
876
- async function with_run_marker(run) {
877
- if (is_nested_run()) return await run();
878
- const target2 = marker_path();
879
- mark_run_started(target2);
880
- process.env[NESTED_KEY] = NESTED_VALUE;
881
- try {
882
- return await run();
883
- } finally {
884
- process.env[NESTED_KEY] = "";
885
- stamp_file.remove_stamp(target2);
886
- }
887
- }
888
- var unit_worker_share = {
889
- MIN_WORKERS,
890
- NESTED_KEY,
891
- NESTED_VALUE,
892
- RUN_PREFIX,
893
- SOLO_RUNS,
894
- current_share,
895
- is_nested_run,
896
- live_run_count,
897
- marker_path,
898
- read_marker,
899
- resolve_unit_workers,
900
- with_run_marker,
901
- worker_arguments
902
- };
903
-
904
- // scripts/gate/core-budget.ts
905
- import { z as z2 } from "zod";
906
-
907
- // scripts/gate/core-admission.ts
908
- var MS_PER_SECOND = 1e3;
909
- var RAMP_UP_MS = 3e4;
910
- function compare_claim(left, right) {
911
- const by_time = left.reservation.claimed_at - right.reservation.claimed_at;
912
- return by_time === 0 ? left.key.localeCompare(right.key) : by_time;
913
- }
914
- function add_load(totals, reservation) {
915
- totals.cores += reservation.weight;
916
- totals.memory_mb += reservation.memory_mb ?? 0;
917
- }
918
- function exceeds(totals, budget) {
919
- return totals.cores > budget.cores || totals.memory_mb > budget.memory_mb;
920
- }
921
- function admitted_keys(reservations, budget, memory_budget = Infinity) {
922
- const limit = { cores: budget, memory_mb: memory_budget };
923
- const totals = { cores: 0, memory_mb: 0 };
924
- const admitted = /* @__PURE__ */ new Set();
925
- for (const [index, entry] of reservations.toSorted(compare_claim).entries()) {
926
- add_load(totals, entry.reservation);
927
- if (index !== 0 && exceeds(totals, limit)) break;
928
- admitted.add(entry.key);
929
- }
930
- return admitted;
931
- }
932
- function is_admitted(key, reservations, budget) {
933
- return admitted_keys(reservations, budget.cores, budget.memory_mb).has(key);
934
- }
935
- function sum_load(reservations) {
936
- const load = { cores: 0, memory_mb: 0 };
937
- for (const entry of reservations) add_load(load, entry.reservation);
938
- return load;
939
- }
940
- function is_ramped(reservation, now) {
941
- const { admitted_at } = reservation;
942
- return admitted_at !== void 0 && now - admitted_at >= RAMP_UP_MS;
943
- }
944
- function ledger_load(reservations, now) {
945
- const admitted = reservations.filter((entry) => entry.reservation.admitted_at !== void 0);
946
- const ramped = admitted.filter((entry) => is_ramped(entry.reservation, now));
947
- return { cores: sum_load(admitted).cores, memory_mb: sum_load(ramped).memory_mb };
948
- }
949
- function total_weight(reservations) {
950
- return sum_load(reservations).cores;
951
- }
952
- function admitted_load(reservations, budget) {
953
- const admitted = admitted_keys(reservations, budget);
954
- return total_weight(reservations.filter((entry) => admitted.has(entry.key)));
955
- }
956
- function describe_memory(ledger, budget) {
957
- if (!Number.isFinite(budget.memory_mb)) return "";
958
- return ` and ${String(Math.round(ledger.memory_mb))} MB of memory against ${String(Math.round(budget.memory_mb))} MB`;
959
- }
960
- function describe_overflow(admission) {
961
- const { overflow } = admission;
962
- if (overflow === void 0) return void 0;
963
- const { ledger, budget } = overflow;
964
- const waited = String(Math.round(overflow.waited_ms / MS_PER_SECOND));
965
- const cores = `the ledger held ${String(ledger.cores)} cores against a budget of ${String(budget.cores)}`;
966
- return `started past the core budget after ${waited}s \u2014 ${cores}${describe_memory(ledger, budget)}`;
967
- }
968
- var core_admission = {
969
- RAMP_UP_MS,
970
- admitted_keys,
971
- admitted_load,
972
- describe_overflow,
973
- is_admitted,
974
- ledger_load,
975
- sum_load
976
- };
977
-
978
- // scripts/gate/machine-capacity.ts
979
- import { readFile } from "node:fs/promises";
980
- import { availableParallelism as availableParallelism2, cpus, totalmem } from "node:os";
981
- import { execa } from "execa";
982
- var SAMPLE_WINDOW_MS = 250;
983
- var BASELINE_CORES = 2;
984
- var BYTES_PER_MB = 1048576;
985
- var KB_PER_MB = 1024;
986
- var PERCENT = 100;
987
- var MEMINFO_PATH = "/proc/meminfo";
988
- var VMSTAT_PATH = "/proc/vmstat";
989
- var LINUX_AVAILABLE = /^MemAvailable:\s+(?<kb>\d+) kB/mu;
990
- var LINUX_SWAP_IN = /^pswpin (?<pages>\d+)$/mu;
991
- var LINUX_SWAP_OUT = /^pswpout (?<pages>\d+)$/mu;
992
- var PAGE_SIZE_ARGUMENTS = ["PAGESIZE"];
993
- var LEVEL_NAME = "kern.memorystatus_level";
994
- var SWAP_IN_NAME = "vm.compressor.swapper.swapins_total";
995
- var SWAP_OUT_NAME = "vm.compressor.swapper.swapouts_total";
996
- var PAGE_SIZE_NAME = "hw.pagesize";
997
- var PRESSURE_NAME = "kern.memorystatus_vm_pressure_level";
998
- var SYSCTL_ARGUMENTS = [LEVEL_NAME, SWAP_IN_NAME, SWAP_OUT_NAME, PAGE_SIZE_NAME, PRESSURE_NAME];
999
- var SYSCTL_LINE = /^(?<name>[\w.]+): (?<value>\d+)$/gmu;
1000
- function cpu_times() {
1001
- let busy = 0;
1002
- let idle = 0;
1003
- for (const { times } of cpus()) {
1004
- busy += times.user + times.nice + times.sys + times.irq;
1005
- idle += times.idle;
1006
- }
1007
- return { busy, total: busy + idle };
1008
- }
1009
- function busy_share_between(before, after) {
1010
- const total = after.total - before.total;
1011
- if (total <= 0) return void 0;
1012
- return (after.busy - before.busy) / total;
1013
- }
1014
- function busy_cores_between(before, after, cores) {
1015
- const share = busy_share_between(before, after);
1016
- return share === void 0 ? void 0 : share * cores;
1017
- }
1018
- function sysctl_values(output) {
1019
- const values = [...output.matchAll(SYSCTL_LINE)].map(
1020
- (match) => [match.groups?.["name"] ?? "", Number(match.groups?.["value"])]
1021
- );
1022
- return new Map(values);
1023
- }
1024
- function swapped_pages(pages_in, pages_out) {
1025
- return pages_in === void 0 || pages_out === void 0 ? void 0 : pages_in + pages_out;
1026
- }
1027
- function swapped_mb(pages, page_bytes) {
1028
- if (pages === void 0 || page_bytes === void 0) return void 0;
1029
- return pages * page_bytes / BYTES_PER_MB;
1030
- }
1031
- function swapped_between(from, to) {
1032
- if (from === void 0 || to === void 0) return void 0;
1033
- return Math.max(0, to - from);
1034
- }
1035
- function parse_darwin_memory(output, total_bytes) {
1036
- const values = sysctl_values(output);
1037
- const level = values.get(LEVEL_NAME);
1038
- return {
1039
- available_mb: level === void 0 ? void 0 : level / PERCENT * (total_bytes / BYTES_PER_MB),
1040
- swapped_mb: swapped_mb(
1041
- swapped_pages(values.get(SWAP_IN_NAME), values.get(SWAP_OUT_NAME)),
1042
- values.get(PAGE_SIZE_NAME)
1043
- ),
1044
- pressure_level: values.get(PRESSURE_NAME)
1045
- };
1046
- }
1047
- function parse_linux_available(meminfo) {
1048
- const kb = LINUX_AVAILABLE.exec(meminfo)?.groups?.["kb"];
1049
- return kb === void 0 ? void 0 : Number(kb) / KB_PER_MB;
1050
- }
1051
- function pages_of(pattern, vmstat) {
1052
- const pages = pattern.exec(vmstat)?.groups?.["pages"];
1053
- return pages === void 0 ? void 0 : Number(pages);
1054
- }
1055
- function parse_linux_swapped(vmstat, page_bytes) {
1056
- const pages = swapped_pages(pages_of(LINUX_SWAP_IN, vmstat), pages_of(LINUX_SWAP_OUT, vmstat));
1057
- return swapped_mb(pages, page_bytes);
1058
- }
1059
- async function settled(read) {
1060
- try {
1061
- return await read();
1062
- } catch {
1063
- return void 0;
1064
- }
1065
- }
1066
- async function read_darwin_memory() {
1067
- const { stdout } = await execa("sysctl", SYSCTL_ARGUMENTS, {
1068
- reject: false,
1069
- timeout: PROBE_TIMEOUT_MS
1070
- });
1071
- return parse_darwin_memory(stdout, totalmem());
1072
- }
1073
- async function read_page_size() {
1074
- const { stdout } = await execa("getconf", PAGE_SIZE_ARGUMENTS, { timeout: PROBE_TIMEOUT_MS });
1075
- return stdout;
1076
- }
1077
- async function read_linux_memory() {
1078
- const [meminfo, vmstat, page_size] = await Promise.all([
1079
- settled(async () => await readFile(MEMINFO_PATH, "utf8")),
1080
- settled(async () => await readFile(VMSTAT_PATH, "utf8")),
1081
- settled(read_page_size)
1082
- ]);
1083
- const page_bytes = page_size === void 0 ? void 0 : Number(page_size);
1084
- return {
1085
- available_mb: meminfo === void 0 ? void 0 : parse_linux_available(meminfo),
1086
- swapped_mb: vmstat === void 0 || page_bytes === void 0 ? void 0 : parse_linux_swapped(vmstat, page_bytes),
1087
- pressure_level: void 0
1088
- };
1089
- }
1090
- var UNREAD_MEMORY = {
1091
- available_mb: void 0,
1092
- swapped_mb: void 0,
1093
- pressure_level: void 0
1094
- };
1095
- async function read_memory() {
1096
- if (process.platform === "darwin") return await settled(read_darwin_memory) ?? UNREAD_MEMORY;
1097
- return process.platform === "linux" ? await read_linux_memory() : UNREAD_MEMORY;
1098
- }
1099
- function is_unquoted() {
1100
- return cpus().length > 0 && cpus().length === availableParallelism2();
1101
- }
1102
- async function default_sleep(ms) {
1103
- await new Promise((resolve) => {
1104
- setTimeout(resolve, ms);
1105
- });
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;
1106
526
  }
1107
527
  async function read_machine(sleep = default_sleep) {
1108
528
  const before = cpu_times();
1109
- 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();
1110
531
  const busy_cores = is_unquoted() ? busy_cores_between(before, cpu_times(), cpus().length) : void 0;
1111
- 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
+ };
1112
537
  }
1113
538
  async function read_sample() {
1114
539
  return { cpu: cpu_times(), memory: await read_memory(), total_mb: totalmem() / BYTES_PER_MB };
@@ -1120,210 +545,965 @@ function external_cores(busy_cores, ledger_cores) {
1120
545
  function machine_budget(cores, reading, ledger) {
1121
546
  return {
1122
547
  cores: cores - external_cores(reading.busy_cores, ledger.cores),
1123
- 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
1124
550
  };
1125
551
  }
1126
552
  var machine_capacity = {
1127
553
  BASELINE_CORES,
1128
554
  busy_cores_between,
1129
555
  busy_share_between,
556
+ COMPRESSION_PRESSURE_MB,
1130
557
  external_cores,
1131
558
  machine_budget,
1132
559
  parse_darwin_memory,
1133
560
  parse_linux_available,
1134
561
  parse_linux_swapped,
562
+ pressured_available,
1135
563
  read_machine,
1136
564
  read_memory,
1137
565
  read_sample,
566
+ SWAP_PRESSURE_MB,
1138
567
  swapped_between
1139
568
  };
1140
569
 
1141
- // scripts/gate/core-budget.ts
1142
- var RESERVED_PREFIX = "josh-core-reserved-";
1143
- var RESERVED_SUFFIX = ".json";
1144
- var POLL_INTERVAL_MS = 50;
1145
- var WAIT_CAP_MS = 12e4;
1146
- var MIN_BUDGET = 1;
1147
- var CORE_WEIGHTS = {
1148
- lint: 2,
1149
- type_check: 1,
1150
- spell_check: 1,
1151
- eslint_scan: 1
1152
- };
1153
- var MEMORY_MB = {
1154
- lint: 2400,
1155
- type_check: 1300,
1156
- spell_check: 350,
1157
- eslint_scan: 2400,
1158
- unit_worker: 350
1159
- };
1160
- var HELD_KEY = "JOSH_CORE_RESERVED";
1161
- var HELD_VALUE = "1";
1162
- var reservation_schema = process_owner_schema.extend({
1163
- weight: z2.number(),
1164
- claimed_at: z2.number(),
1165
- memory_mb: z2.number().optional(),
1166
- admitted_at: z2.number().optional()
1167
- });
1168
- function marker_name(key) {
1169
- return `${RESERVED_PREFIX}${key}${RESERVED_SUFFIX}`;
1170
- }
1171
- function is_running2(reservation) {
1172
- return process_identity.is_same_process(reservation.pid, reservation.process_start) !== false;
1173
- }
1174
- function read_reservation(source) {
1175
- const raw = stamp_file.read_stamp_text(source);
1176
- if (raw === void 0) return void 0;
1177
- return json_value.parse_with(raw, reservation_schema);
1178
- }
1179
- function sweep_marker2(source) {
1180
- try {
1181
- stamp_file.remove_stamp(source);
1182
- } catch {
1183
- }
1184
- }
1185
- function live_reservation(source) {
1186
- const reservation = read_reservation(source);
1187
- if (reservation === void 0) return void 0;
1188
- if (is_running2(reservation)) return reservation;
1189
- sweep_marker2(source);
1190
- return void 0;
1191
- }
1192
- function marker_files2(directory) {
1193
- try {
1194
- return readdirSync2(directory).filter(
1195
- (name) => name.startsWith(RESERVED_PREFIX) && name.endsWith(RESERVED_SUFFIX)
1196
- );
1197
- } catch {
1198
- return [];
1199
- }
1200
- }
1201
- function live_reservations(directory = PLATFORM_TEMP_ROOT) {
1202
- return marker_files2(directory).flatMap((name) => {
1203
- const reservation = live_reservation(path2.join(directory, name));
1204
- return reservation === void 0 ? [] : { key: name, reservation };
1205
- });
1206
- }
1207
- function normalize_budget(budget) {
1208
- return Number.isFinite(budget) ? Math.max(MIN_BUDGET, Math.floor(budget)) : MIN_BUDGET;
1209
- }
1210
- async function default_sleep2(ms) {
1211
- await new Promise((resolve) => {
1212
- setTimeout(resolve, ms);
1213
- });
1214
- }
1215
- function write_reservation(target2, fields) {
1216
- stamp_file.replace_stamp(target2, { ...process_identity.own_fields(), ...fields });
1217
- }
1218
- function resolve_timing(options) {
1219
- return {
1220
- now: options.now ?? Date.now,
1221
- sleep: options.sleep ?? default_sleep2,
1222
- poll_interval_ms: options.poll_interval_ms ?? POLL_INTERVAL_MS,
1223
- wait_cap_ms: options.wait_cap_ms ?? WAIT_CAP_MS
1224
- };
1225
- }
1226
- async function current_budget(context, reservations) {
1227
- if (reservations.length <= 1) return { cores: context.budget, memory_mb: Infinity };
1228
- const reading = await context.read_machine();
1229
- const ledger = core_admission.ledger_load(reservations, context.now());
1230
- const budget = machine_capacity.machine_budget(context.budget, reading, ledger);
1231
- return { ...budget, cores: normalize_budget(budget.cores) };
1232
- }
1233
- function admit(context, overflow) {
1234
- write_reservation(context.target, { ...context.fields, admitted_at: context.now() });
1235
- return { overflow };
1236
- }
1237
- function overflow_of(context, reservations, budget) {
1238
- return {
1239
- waited_ms: context.now() - context.fields.claimed_at,
1240
- ledger: core_admission.sum_load(reservations),
1241
- budget
1242
- };
1243
- }
1244
- async function poll_admission(context) {
1245
- const reservations = live_reservations(context.directory);
1246
- const budget = await current_budget(context, reservations);
1247
- if (core_admission.is_admitted(context.key, reservations, budget)) return admit(context);
1248
- if (context.now() >= context.deadline) {
1249
- return admit(context, overflow_of(context, reservations, budget));
1250
- }
1251
- return void 0;
1252
- }
1253
- async function await_admission(context) {
1254
- let admission = await poll_admission(context);
1255
- while (admission === void 0) {
1256
- await context.sleep(context.poll_interval_ms);
1257
- admission = await poll_admission(context);
1258
- }
1259
- return admission;
1260
- }
1261
- async function reserve(weight, options = {}) {
1262
- const timing = resolve_timing(options);
1263
- const directory = options.directory ?? PLATFORM_TEMP_ROOT;
1264
- const claimed_at = timing.now();
1265
- const name = marker_name(randomUUID());
1266
- const target2 = path2.join(directory, name);
1267
- const fields = { weight, claimed_at, memory_mb: options.memory_mb };
1268
- write_reservation(target2, fields);
1269
- const admission = await await_admission({
1270
- ...timing,
1271
- key: name,
1272
- target: target2,
1273
- directory,
1274
- budget: normalize_budget(options.budget ?? availableParallelism3()),
1275
- deadline: claimed_at + timing.wait_cap_ms,
1276
- fields,
1277
- read_machine: options.read_machine ?? machine_capacity.read_machine
1278
- });
1279
- return { target: target2, admission };
1280
- }
1281
- function release(handle) {
1282
- sweep_marker2(handle.target);
1283
- }
1284
- async function with_core_reservation(weight, run, options = {}) {
1285
- const handle = await reserve(weight, options);
1286
- try {
1287
- return await run(handle.admission);
1288
- } finally {
1289
- release(handle);
1290
- }
1291
- }
1292
- function is_held() {
1293
- return process.env[HELD_KEY] === HELD_VALUE;
1294
- }
1295
- function set_held_mark(value) {
1296
- process.env[HELD_KEY] = value;
1297
- }
1298
- async function with_held_mark(run) {
1299
- const previous = process.env[HELD_KEY] ?? "";
1300
- set_held_mark(HELD_VALUE);
1301
- try {
1302
- return await run();
1303
- } finally {
1304
- 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"]]
1305
1506
  }
1306
- }
1307
- async function with_command_reservation(weight, run, options = {}) {
1308
- if (is_held() || unit_worker_share.is_nested_run()) return await run();
1309
- return await with_core_reservation(weight, async () => await with_held_mark(run), options);
1310
- }
1311
- var core_budget = {
1312
- CORE_WEIGHTS,
1313
- HELD_KEY,
1314
- HELD_VALUE,
1315
- MEMORY_MB,
1316
- RESERVED_PREFIX,
1317
- admitted_keys: core_admission.admitted_keys,
1318
- admitted_load: core_admission.admitted_load,
1319
- describe_overflow: core_admission.describe_overflow,
1320
- is_held,
1321
- live_reservations,
1322
- release,
1323
- reserve,
1324
- with_command_reservation,
1325
- with_core_reservation,
1326
- with_held_mark
1327
1507
  };
1328
1508
 
1329
1509
  // scripts/gate/gate-plan.ts
@@ -1389,6 +1569,7 @@ var TYPE_CHECK_LABEL = "check";
1389
1569
  var UNIT_LABEL = "test:unit";
1390
1570
  var BEHAVIOR_LABEL = "behavior";
1391
1571
  var METRICS_LABEL = "metrics";
1572
+ var EXPORTS_LABEL = "exports";
1392
1573
  var WARNING_CHECKER_LABELS = [LINT_LABEL, TYPE_CHECK_LABEL];
1393
1574
  var STATIC_CHECKS = [
1394
1575
  {
@@ -1414,7 +1595,7 @@ var STATIC_CHECKS = [
1414
1595
  // program and walks it for about 5s, and it is over well before lint and the type check. A
1415
1596
  // reservation would only take a place from the four-core CI runner's fan-out and a worker from the
1416
1597
  // unit suite for the whole gate.
1417
- { label: "exports", target: "exports:unused", reserved_cores: 0 },
1598
+ { label: EXPORTS_LABEL, target: "exports:unused", reserved_cores: 0 },
1418
1599
  // **The metrics ratchet is kit's own**: it holds kit's repository-wide
1419
1600
  // totals to the merge-base's. It is one in-process eslint pass with only `max-lines` enabled, over
1420
1601
  // in a few seconds, so it reserves no core for the reason the unused-member check above does not.
@@ -1503,6 +1684,15 @@ function resolve_gate_plan(available_cores = availableParallelism4(), concurrent
1503
1684
  unit_worker_cap: unit_cap_for(checks, available_cores, concurrent_runs)
1504
1685
  };
1505
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
+ }
1506
1696
  function format_machine(available_cores, concurrent_runs) {
1507
1697
  const cores = `${String(available_cores)} cores`;
1508
1698
  if (concurrent_runs <= unit_worker_share.SOLO_RUNS) return cores;
@@ -1521,6 +1711,7 @@ function format_gate_plan(plan, available_cores = availableParallelism4(), concu
1521
1711
  return `${josh_verdict.GATE_OPENING_PREFIX}${width}, ${cap} (${machine})`;
1522
1712
  }
1523
1713
  var gate_plan = {
1714
+ EXPORTS_LABEL,
1524
1715
  GATE_CHECKS,
1525
1716
  LINT_LABEL,
1526
1717
  MEASURED_CORES,
@@ -1530,6 +1721,7 @@ var gate_plan = {
1530
1721
  TYPE_CHECK_LABEL,
1531
1722
  UNIT_LABEL,
1532
1723
  WARNING_CHECKER_LABELS,
1724
+ bound_by_free,
1533
1725
  check_memory,
1534
1726
  check_weight,
1535
1727
  direct_unit_memory,
@@ -1662,7 +1854,7 @@ var DEV_COMMANDS = {
1662
1854
  },
1663
1855
  "stop:guard": {
1664
1856
  script: "scripts/hooks/stop-guard.ts",
1665
- 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)",
1666
1858
  category: "Development",
1667
1859
  reference: ["", "automation", ["none"]]
1668
1860
  // **No `tsx_arguments`, deliberately**, the same as `pretool:guard` above: this runs at every
@@ -1796,6 +1988,12 @@ var HOOKS_COMMANDS = {
1796
1988
  category: "Git hooks",
1797
1989
  reference: ["[paths...]", "automation", ["none"]]
1798
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
+ },
1799
1997
  "pre-push-unit": {
1800
1998
  script: "scripts/hooks/pre-push-unit.ts",
1801
1999
  description: "Git hook: run unit tests, reusing a green gate recorded on the pushed tree",
@@ -2201,6 +2399,11 @@ var WORKFLOW_COMMANDS = {
2201
2399
  // A script rather than an `sh -c` chain, because the strategy has to be named rather than left to
2202
2400
  // the caller's git configuration: a bare `git pull` aborts with `Need to specify how to reconcile
2203
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.
2204
2407
  "main:merge": {
2205
2408
  script: "scripts/git/main-merge.ts",
2206
2409
  description: "Merge origin default branch into the current branch",
@@ -2267,6 +2470,7 @@ var ALIASES = {
2267
2470
  tm: "time",
2268
2471
  tmd: "time:density",
2269
2472
  rtr: "retrospective",
2473
+ ca: "cost:audit",
2270
2474
  rvf: "review:findings",
2271
2475
  ruv: "rule:value",
2272
2476
  ev: "eval",
@@ -2276,6 +2480,7 @@ var ALIASES = {
2276
2480
  lst: "lane:stats",
2277
2481
  lli: "lane:limit",
2278
2482
  lrs: "lane:restart",
2483
+ lwd: "lane:withdraw",
2279
2484
  blr: "backlogrun"
2280
2485
  };
2281
2486
  function canonical_command(name) {
@@ -2288,7 +2493,7 @@ var BASH_SEPARATOR = ": ";
2288
2493
  var WHITESPACE_PATTERN = /\s+/u;
2289
2494
  var SEGMENT_PATTERN = /&&|\|\||;|\|/u;
2290
2495
  var PIPE_PATTERN = /(?<!\|)\|(?!\|)/u;
2291
- var CHAIN_PATTERN = /&&|\|\||;/u;
2496
+ var CHAIN_PATTERN = /&&|\|\||;|\n/u;
2292
2497
  var QUOTED_SPAN_PATTERN = /\\[\s\S]|'[^']*'|"(?:\\[\s\S]|[^"\\])*"/gu;
2293
2498
  var HEREDOC_MARKER = /(?<!<)<<-?\s*(['"]?)(\w+)\1/u;
2294
2499
  var HEREDOC_DELIMITER_GROUP = 2;
@@ -2386,13 +2591,24 @@ function drop_redirections(words) {
2386
2591
  }
2387
2592
  return kept;
2388
2593
  }
2389
- function josh_arguments(command) {
2390
- const text = command_words(command_segment(command)).join(" ");
2594
+ function josh_arguments_of_segment(segment) {
2595
+ const text = command_words(segment).join(" ");
2391
2596
  const match = JOSH_PATTERN.exec(text);
2392
2597
  if (match === null) return [];
2393
2598
  const words = text.slice(match[0].length).split(WHITESPACE_PATTERN).filter((word) => word !== "");
2394
2599
  return drop_redirections(words);
2395
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
+ }
2396
2612
  var time_shell = {
2397
2613
  JOSH_PREFIX,
2398
2614
  bash_command,
@@ -2403,6 +2619,7 @@ var time_shell = {
2403
2619
  josh_arguments,
2404
2620
  josh_command_of,
2405
2621
  josh_commands_of,
2622
+ josh_signatures_of,
2406
2623
  leading_word,
2407
2624
  unquoted
2408
2625
  };
@@ -2503,78 +2720,6 @@ var time_background = {
2503
2720
  unread_phases
2504
2721
  };
2505
2722
 
2506
- // scripts/git/git-schemas.ts
2507
- import { z as z3 } from "zod";
2508
- var package_name_schema = z3.object({ name: z3.string().min(1) });
2509
- var rollup_item_schema = z3.looseObject({
2510
- // eslint-disable-next-line @typescript-eslint/naming-convention -- GraphQL field name from GitHub API
2511
- __typename: z3.string().optional(),
2512
- state: z3.string().optional(),
2513
- status: z3.string().optional(),
2514
- conclusion: z3.string().optional(),
2515
- name: z3.string().optional(),
2516
- context: z3.string().optional(),
2517
- description: z3.string().nullish()
2518
- });
2519
- var pr_raw_schema = z3.object({
2520
- statusCheckRollup: z3.array(rollup_item_schema).optional(),
2521
- mergeStateStatus: z3.string().optional(),
2522
- reviewDecision: z3.string().optional()
2523
- });
2524
- var pull_comment_schema = z3.object({
2525
- body: z3.string().optional(),
2526
- html_url: z3.string().optional(),
2527
- user: z3.object({ login: z3.string().optional() }).optional()
2528
- });
2529
- var ai_review_pull_comment_schema = z3.object({
2530
- body: z3.string().optional(),
2531
- url: z3.string().optional(),
2532
- author: z3.object({ login: z3.string().optional() }).optional()
2533
- });
2534
- var pr_info_schema = z3.object({
2535
- mergeable: z3.union([z3.boolean(), z3.string()]).nullable().optional(),
2536
- mergeStateStatus: z3.string().nullable().optional(),
2537
- state: z3.string().optional()
2538
- });
2539
- var epic_issue_schema = z3.object({
2540
- number: z3.number(),
2541
- body: z3.string().optional()
2542
- });
2543
- var blocking_issue_schema = z3.object({
2544
- number: z3.number(),
2545
- state: z3.string().optional(),
2546
- repository_url: z3.string().optional()
2547
- });
2548
- var blocked_by_schema = z3.object({
2549
- nodes: z3.array(blocking_issue_schema).default([]),
2550
- totalCount: z3.number().optional()
2551
- }).optional();
2552
- var epic_child_schema = z3.object({
2553
- state: z3.string().optional(),
2554
- blockedBy: blocked_by_schema
2555
- });
2556
- var rest_comment_schema = z3.looseObject({
2557
- body: z3.string().nullish(),
2558
- html_url: z3.string().optional(),
2559
- user: z3.looseObject({ login: z3.string().optional() }).nullish()
2560
- });
2561
- var issue_label_schema = z3.object({ name: z3.string() });
2562
- var epic_subject_schema = z3.object({
2563
- number: z3.number(),
2564
- state: z3.string().optional(),
2565
- labels: z3.array(issue_label_schema).optional(),
2566
- body: z3.string().optional()
2567
- });
2568
- var open_issue_schema = z3.object({
2569
- number: z3.number(),
2570
- title: z3.string(),
2571
- labels: z3.array(issue_label_schema).optional(),
2572
- createdAt: z3.string(),
2573
- blockedBy: blocked_by_schema,
2574
- // Present only on the pickup listing, which reads it for the paths each issue declares.
2575
- body: z3.string().optional()
2576
- });
2577
-
2578
2723
  // scripts/gh/git-pr-checks-parse.ts
2579
2724
  var CHECK_STATUS_PASS = "pass";
2580
2725
  var CHECK_STATUS_PENDING = "pending";
@@ -2753,7 +2898,7 @@ var PrConflictError = class extends Error {
2753
2898
 
2754
2899
  // scripts/lane/lane-ledger.ts
2755
2900
  import { closeSync, constants, openSync, writeSync } from "node:fs";
2756
- import { z as z4 } from "zod";
2901
+ import { z as z3 } from "zod";
2757
2902
  var LEDGER_PREFIX = "josh-lane-ledger-";
2758
2903
  var LEDGER_SUFFIX = ".jsonl";
2759
2904
  var LINE_SEPARATOR = "\n";
@@ -2767,63 +2912,83 @@ var KIND = {
2767
2912
  LOAD: "load",
2768
2913
  DISPATCH: "dispatch",
2769
2914
  STAGE: "stage",
2770
- STOP: "stop"
2915
+ STOP: "stop",
2916
+ OVERLAP: "overlap"
2771
2917
  };
2772
- var merge_schema = z4.object({ kind: z4.literal(KIND.MERGE), at: z4.string(), issue: z4.number() });
2773
- var gate_schema = z4.object({
2774
- kind: z4.literal(KIND.GATE),
2775
- at: z4.string(),
2776
- elapsed_ms: z4.number(),
2777
- 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(),
2778
2930
  // The unit suite's own duration, present when this gate ran it — the one
2779
2931
  // record of a full vitest run `josh metrics` reads, so the suite is never timed twice.
2780
- unit_ms: z4.number().optional(),
2932
+ unit_ms: z3.number().optional(),
2781
2933
  // The whole cores busy outside this gate, beyond the machine's baseline, at its start or its end —
2782
2934
  // whichever was higher. Absent when the machine could not be read.
2783
- 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()
2784
2939
  });
2785
- var process_kind_schema = z4.object({
2786
- kind: z4.string(),
2787
- cpu_percent: z4.number(),
2788
- rss_mb: z4.number(),
2789
- count: z4.number()
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()
2790
2945
  });
2791
- var load_schema = z4.object({
2792
- kind: z4.literal(KIND.LOAD),
2793
- at: z4.string(),
2794
- load: z4.number(),
2795
- available_mb: z4.number().optional(),
2796
- swapped_mb: z4.number().optional(),
2797
- lanes: z4.number().optional(),
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(),
2798
2953
  // The heaviest process kinds at the instant of the sample (`lane-process-load.ts`), so the load the
2799
2954
  // core budget's ledger does not explain can be read back by what made it (joshuafolkken/kit#3874).
2800
- processes: z4.array(process_kind_schema).optional()
2955
+ processes: z3.array(process_kind_schema).optional()
2801
2956
  });
2802
- var dispatch_schema = z4.object({
2803
- kind: z4.literal(KIND.DISPATCH),
2804
- at: z4.string(),
2805
- issue: z4.number()
2957
+ var dispatch_schema = z3.object({
2958
+ kind: z3.literal(KIND.DISPATCH),
2959
+ at: z3.string(),
2960
+ issue: z3.number()
2806
2961
  });
2807
- var stage_schema = z4.object({
2808
- kind: z4.literal(KIND.STAGE),
2809
- at: z4.string(),
2810
- stage: z4.string(),
2811
- elapsed_ms: z4.number(),
2812
- issue: z4.number().optional()
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()
2813
2968
  });
2814
- var stop_schema = z4.object({
2815
- kind: z4.literal(KIND.STOP),
2816
- at: z4.string(),
2817
- issue: z4.number(),
2818
- reason: z4.string()
2969
+ var stop_schema = z3.object({
2970
+ kind: z3.literal(KIND.STOP),
2971
+ at: z3.string(),
2972
+ issue: z3.number(),
2973
+ reason: z3.string()
2819
2974
  });
2820
- var entry_schema = z4.discriminatedUnion("kind", [
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()
2983
+ });
2984
+ var entry_schema = z3.discriminatedUnion("kind", [
2821
2985
  merge_schema,
2822
2986
  gate_schema,
2823
2987
  load_schema,
2824
2988
  dispatch_schema,
2825
2989
  stage_schema,
2826
- stop_schema
2990
+ stop_schema,
2991
+ overlap_schema
2827
2992
  ]);
2828
2993
  function target_of(repository) {
2829
2994
  return stamp_file.stamp_path(LEDGER_PREFIX, repository, LEDGER_SUFFIX);
@@ -2837,19 +3002,19 @@ async function target() {
2837
3002
  return void 0;
2838
3003
  }
2839
3004
  }
2840
- function append(path4, entry) {
2841
- const descriptor = openSync(path4, APPEND_FLAGS, LEDGER_FILE_MODE);
3005
+ function append(path5, entry) {
3006
+ const descriptor = openSync(path5, APPEND_FLAGS, LEDGER_FILE_MODE);
2842
3007
  try {
2843
- if (stamp_file.is_own_regular_file(path4)) {
3008
+ if (stamp_file.is_own_regular_file(path5)) {
2844
3009
  writeSync(descriptor, `${JSON.stringify(entry)}${LINE_SEPARATOR}`);
2845
3010
  }
2846
3011
  } finally {
2847
3012
  closeSync(descriptor);
2848
3013
  }
2849
3014
  }
2850
- async function record(entry, path4) {
3015
+ async function record(entry, path5) {
2851
3016
  try {
2852
- const destination = path4 ?? await target();
3017
+ const destination = path5 ?? await target();
2853
3018
  if (destination !== void 0) append(destination, entry);
2854
3019
  } catch {
2855
3020
  }
@@ -2857,20 +3022,23 @@ async function record(entry, path4) {
2857
3022
  function now_iso() {
2858
3023
  return (/* @__PURE__ */ new Date()).toISOString();
2859
3024
  }
2860
- async function record_gate(path4, gate) {
2861
- 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);
2862
3027
  }
2863
3028
  async function record_merge(issue) {
2864
3029
  await record({ kind: KIND.MERGE, at: now_iso(), issue });
2865
3030
  }
2866
- async function record_dispatch(issue, path4) {
2867
- 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);
3033
+ }
3034
+ async function record_stage(stage, path5) {
3035
+ await record({ kind: KIND.STAGE, at: now_iso(), ...stage }, path5);
2868
3036
  }
2869
- async function record_stage(stage, path4) {
2870
- await record({ kind: KIND.STAGE, at: now_iso(), ...stage }, path4);
3037
+ async function record_stop(stop, path5) {
3038
+ await record({ kind: KIND.STOP, at: now_iso(), ...stop }, path5);
2871
3039
  }
2872
- async function record_stop(stop, path4) {
2873
- await record({ kind: KIND.STOP, at: now_iso(), ...stop }, path4);
3040
+ async function record_overlap(overlap, path5) {
3041
+ await record({ kind: KIND.OVERLAP, at: now_iso(), ...overlap }, path5);
2874
3042
  }
2875
3043
  function parse_line2(line) {
2876
3044
  return entry_schema.safeParse(json_value.parse_or_undefined(line)).data;
@@ -2878,8 +3046,8 @@ function parse_line2(line) {
2878
3046
  function is_present(entry) {
2879
3047
  return entry !== void 0;
2880
3048
  }
2881
- function read_entries(path4) {
2882
- const raw = stamp_file.read_stamp_text(path4);
3049
+ function read_entries(path5) {
3050
+ const raw = stamp_file.read_stamp_text(path5);
2883
3051
  if (raw === void 0) return [];
2884
3052
  return raw.split(LINE_SEPARATOR).map((line) => parse_line2(line.trim())).filter(is_present);
2885
3053
  }
@@ -2892,6 +3060,7 @@ var lane_ledger = {
2892
3060
  record_dispatch,
2893
3061
  record_gate,
2894
3062
  record_merge,
3063
+ record_overlap,
2895
3064
  record_stage,
2896
3065
  record_stop,
2897
3066
  target,
@@ -3058,9 +3227,9 @@ function read_stages(text) {
3058
3227
  var time_followup_stage = { NO_STAGES, read_stages };
3059
3228
 
3060
3229
  // scripts/time-runtime/time-reported-failure.ts
3061
- import { z as z5 } from "zod";
3062
- var TEXT_BLOCK_SCHEMA = z5.object({ text: z5.string().nullish() });
3063
- 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())]);
3064
3233
  var LINE_SEPARATOR3 = "\n";
3065
3234
  function block_text(block) {
3066
3235
  const parsed = TEXT_BLOCK_SCHEMA.safeParse(block);
@@ -3597,38 +3766,38 @@ function check_key(josh_command, command) {
3597
3766
  var time_single_check = { NO_CHECK, check_key };
3598
3767
 
3599
3768
  // scripts/time-runtime/time-transcript-line.ts
3600
- import { z as z6 } from "zod";
3601
- var BLOCK_SCHEMA2 = z6.object({
3602
- type: z6.string().nullish(),
3603
- name: z6.string().nullish(),
3604
- id: z6.string().nullish(),
3605
- tool_use_id: z6.string().nullish(),
3606
- 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(),
3607
3776
  // Whether the harness wrote this result back as a failure. Present on a
3608
3777
  // `tool_result` block and on nothing else, and absent even there for the tools that never report
3609
3778
  // one — which is why it is read as three answers rather than as a boolean with a default.
3610
- is_error: z6.boolean().nullish(),
3779
+ is_error: z5.boolean().nullish(),
3611
3780
  // What the call printed. Read only to recover the outcome a pipeline threw away:
3612
3781
  // it is kept as flattened text rather than as blocks, because that is
3613
3782
  // the whole of what `time-reported-failure.ts` asks of it.
3614
- content: z6.unknown().nullish()
3783
+ content: z5.unknown().nullish()
3615
3784
  });
3616
- var CONTENT_SCHEMA2 = z6.union([z6.string(), z6.array(BLOCK_SCHEMA2)]);
3617
- var MESSAGE_SCHEMA2 = z6.object({ id: z6.string().nullish(), content: CONTENT_SCHEMA2.nullish() });
3618
- var LINE_SCHEMA = z6.object({
3619
- type: z6.string().nullish(),
3620
- 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(),
3621
3790
  // The branch the line was written on, which is where the issue number lives — `josh git` names a
3622
3791
  // branch `<N>-<slug>`. Read here rather than re-parsed by a second reader so `josh time --issue`
3623
3792
  // and `josh cost --issue` answer from the same field.
3624
- gitBranch: z6.string().nullish(),
3793
+ gitBranch: z5.string().nullish(),
3625
3794
  // The two fields outside `message` that the harness writes an end-of-task notice on.
3626
3795
  // **Both are read as `unknown` rather than as strings**: a schema
3627
3796
  // insisting on a string here would reject every line whose `content` is a list of blocks — which is
3628
3797
  // most of them — and a rejected line is dropped from the timeline entirely, so the shares would stop
3629
3798
  // reconstructing the elapsed time. The shape is checked at the read below instead.
3630
- content: z6.unknown().nullish(),
3631
- attachment: z6.unknown().nullish(),
3799
+ content: z5.unknown().nullish(),
3800
+ attachment: z5.unknown().nullish(),
3632
3801
  message: MESSAGE_SCHEMA2.nullish()
3633
3802
  });
3634
3803
  var UNKNOWN_BRANCH = "";
@@ -3739,6 +3908,7 @@ var NO_CALL = {
3739
3908
  label: "",
3740
3909
  josh_command: "",
3741
3910
  josh_commands: [],
3911
+ josh_signatures: [],
3742
3912
  check_key: time_single_check.NO_CHECK,
3743
3913
  marker: time_markers.NO_MARKER,
3744
3914
  message_id: NO_MESSAGE_ID2,
@@ -3751,6 +3921,7 @@ var UNKNOWN_CALL = {
3751
3921
  label: UNKNOWN_TOOL,
3752
3922
  josh_command: "",
3753
3923
  josh_commands: [],
3924
+ josh_signatures: [],
3754
3925
  check_key: time_single_check.NO_CHECK,
3755
3926
  marker: time_markers.NO_MARKER,
3756
3927
  message_id: NO_MESSAGE_ID2,
@@ -3764,6 +3935,7 @@ function non_bash_call(name, input, message_id) {
3764
3935
  label: name,
3765
3936
  josh_command: "",
3766
3937
  josh_commands: [],
3938
+ josh_signatures: [],
3767
3939
  check_key: time_single_check.NO_CHECK,
3768
3940
  marker: time_markers.tool_marker(name, input),
3769
3941
  message_id,
@@ -3783,6 +3955,7 @@ function bash_call(input, message_id) {
3783
3955
  label: time_shell.bash_label(command),
3784
3956
  josh_command,
3785
3957
  josh_commands: time_shell.josh_commands_of(command),
3958
+ josh_signatures: time_shell.josh_signatures_of(command),
3786
3959
  check_key: time_single_check.check_key(josh_command, command),
3787
3960
  marker: time_markers.bash_marker(command),
3788
3961
  message_id,
@@ -3905,6 +4078,7 @@ function to_spans(events, finished) {
3905
4078
  label: event.label,
3906
4079
  josh_command: event.josh_command,
3907
4080
  josh_commands: event.josh_commands,
4081
+ josh_signatures: event.josh_signatures,
3908
4082
  check_key: event.check_key,
3909
4083
  marker: event.marker,
3910
4084
  is_bundleable: event.is_bundleable,
@@ -4425,7 +4599,7 @@ function describe_span(span) {
4425
4599
  }
4426
4600
  var FOLD_MINIMUM = 2;
4427
4601
  function read_targets(sequence) {
4428
- 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));
4429
4603
  return [...new Set(paths)];
4430
4604
  }
4431
4605
  function read_fold_directive(sequence) {
@@ -4434,7 +4608,7 @@ function read_fold_directive(sequence) {
4434
4608
  return ` Fold them into one call: \`pnpm josh read:files ${paths.join(" ")}\`.`;
4435
4609
  }
4436
4610
  function write_targets(sequence) {
4437
- 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));
4438
4612
  return [...new Set(paths)];
4439
4613
  }
4440
4614
  function write_fold_directive(sequence) {
@@ -4486,7 +4660,7 @@ var time_batch_guard = {
4486
4660
  };
4487
4661
 
4488
4662
  // scripts/time-runtime/time-hook-transcript.ts
4489
- import path3 from "node:path";
4663
+ import path4 from "node:path";
4490
4664
  var { TRANSCRIPT_EXTENSION } = cost_transcript;
4491
4665
  var AGENT_PREFIX = "agent-";
4492
4666
  var AGENT_ID_PATTERN = /^[\w-]+$/u;
@@ -4494,10 +4668,10 @@ function is_agent_id(value) {
4494
4668
  return typeof value === "string" && AGENT_ID_PATTERN.test(value);
4495
4669
  }
4496
4670
  function fork_path(transcript_path, agent_id) {
4497
- const directory = path3.dirname(transcript_path);
4498
- 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);
4499
4673
  const file_name = `${AGENT_PREFIX}${agent_id}${TRANSCRIPT_EXTENSION}`;
4500
- 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);
4501
4675
  }
4502
4676
  function transcript_of(transcript_path, agent_id) {
4503
4677
  if (!is_agent_id(agent_id) || !transcript_path.endsWith(TRANSCRIPT_EXTENSION)) {
@@ -4512,7 +4686,7 @@ var time_hook_transcript = {
4512
4686
 
4513
4687
  // scripts/time-runtime/time-density-hook.ts
4514
4688
  import { closeSync as closeSync2, fstatSync, openSync as openSync2, readSync } from "node:fs";
4515
- import { z as z7 } from "zod";
4689
+ import { z as z6 } from "zod";
4516
4690
 
4517
4691
  // scripts/time-runtime/time-density.ts
4518
4692
  var MIN_ROUND_TRIPS = 10;
@@ -4568,15 +4742,15 @@ var TAIL_BYTES = 262144;
4568
4742
  var FILE_START = 0;
4569
4743
  var NOTICE_PREFIX = "josh-density-notice-";
4570
4744
  var NEVER_MS = 0;
4571
- var payload_schema = z7.object({
4572
- transcript_path: z7.string().min(1).optional(),
4745
+ var payload_schema = z6.object({
4746
+ transcript_path: z6.string().min(1).optional(),
4573
4747
  // **`nullish`, not `optional`.** A payload spelling the absent agent as `null` has to read as "no
4574
4748
  // fork"; rejected, it would fail `safeParse` for the whole payload and silence the line for every
4575
4749
  // edit of the main line — the opposite of this module's stated failure direction.
4576
- agent_id: z7.string().nullish()
4750
+ agent_id: z6.string().nullish()
4577
4751
  });
4578
- var notice_schema = z7.object({ notified_at_ms: z7.number() });
4579
- 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() });
4580
4754
  function parse_transcript_path(raw_payload) {
4581
4755
  const parsed = payload_schema.safeParse(JSON.parse(raw_payload));
4582
4756
  if (!parsed.success || parsed.data.transcript_path === void 0) return void 0;
@@ -4643,9 +4817,6 @@ export {
4643
4817
  ESLINT_EDIT_CACHE_FLAGS,
4644
4818
  time_shell,
4645
4819
  time_bundle_call,
4646
- blocking_issue_schema,
4647
- rest_comment_schema,
4648
- open_issue_schema,
4649
4820
  CHECK_STATUS_PENDING,
4650
4821
  CHECK_STATUS_FAIL,
4651
4822
  read_string,