@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
package/dist/josh.js CHANGED
@@ -131,20 +131,20 @@ var ABSENT_CODE = "ENOENT";
131
131
  function is_absent(error) {
132
132
  return error instanceof Error && "code" in error && error.code === ABSENT_CODE;
133
133
  }
134
- function read_optional(path12) {
134
+ function read_optional(path16) {
135
135
  try {
136
- return readFileSync(path12, "utf8");
136
+ return readFileSync(path16, "utf8");
137
137
  } catch (error) {
138
138
  if (is_absent(error)) return void 0;
139
139
  throw error;
140
140
  }
141
141
  }
142
- function read_file_or_empty(path12) {
143
- return read_optional(path12) ?? "";
142
+ function read_file_or_empty(path16) {
143
+ return read_optional(path16) ?? "";
144
144
  }
145
- function read_if_readable(path12) {
145
+ function read_if_readable(path16) {
146
146
  try {
147
- return readFileSync(path12, "utf8");
147
+ return readFileSync(path16, "utf8");
148
148
  } catch {
149
149
  return void 0;
150
150
  }
@@ -267,93 +267,6 @@ var doctor_consumer = {
267
267
  secretlint_line
268
268
  };
269
269
 
270
- // scripts/josh/josh-logic.ts
271
- import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
272
- import path11 from "node:path";
273
- import { fileURLToPath as fileURLToPath2 } from "node:url";
274
-
275
- // scripts/build/local-bin.ts
276
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
277
- import { createRequire } from "node:module";
278
- import path3 from "node:path";
279
-
280
- // scripts/lib/ancestor-directories.ts
281
- import path2 from "node:path";
282
- var MAX_DEPTH = 64;
283
- function list(start_directory) {
284
- const directories = [];
285
- let current = path2.resolve(start_directory);
286
- while (directories.length < MAX_DEPTH && !directories.includes(current)) {
287
- directories.push(current);
288
- current = path2.dirname(current);
289
- }
290
- return directories;
291
- }
292
- function nearest(start_directory, is_match) {
293
- return list(start_directory).find((directory) => is_match(directory));
294
- }
295
- var ancestor_directories = { list, nearest };
296
-
297
- // scripts/lib/schemas.ts
298
- import { z as z2 } from "zod";
299
- var overrides_snapshot_schema = z2.record(z2.string(), z2.string());
300
- var package_version_schema = z2.object({ version: z2.string() });
301
- var package_named_version_schema = z2.looseObject({ name: z2.string(), version: z2.string() });
302
- var package_with_version_schema = z2.looseObject({ version: z2.string() });
303
- var ls_global_dependency_schema = z2.looseObject({ version: z2.string() });
304
- var ls_global_dependencies_schema = z2.record(z2.string(), ls_global_dependency_schema).optional();
305
- var pnpm_ls_global_schema = z2.array(
306
- z2.looseObject({
307
- dependencies: ls_global_dependencies_schema
308
- })
309
- );
310
- var vscode_settings_schema = z2.record(z2.string(), z2.unknown());
311
- var with_scripts_schema = z2.looseObject({
312
- scripts: z2.record(z2.string(), z2.string()).optional()
313
- });
314
- var with_development_deps_schema = z2.looseObject({
315
- devDependencies: z2.record(z2.string(), z2.string()).optional()
316
- });
317
- var with_package_manager_schema = z2.looseObject({
318
- packageManager: z2.string().optional()
319
- });
320
- var with_development_engine_schema = z2.looseObject({
321
- devEngines: z2.record(z2.string(), z2.unknown()).optional()
322
- });
323
- var bin_entry_schema = z2.union([z2.string(), z2.record(z2.string(), z2.string())]).optional();
324
- var package_bin_schema = z2.looseObject({ bin: bin_entry_schema });
325
- var json_object_schema = z2.record(z2.string(), z2.unknown());
326
- var string_array_schema = z2.array(z2.string());
327
- var string_record_schema = z2.record(z2.string(), z2.string());
328
-
329
- // scripts/build/local-bin.ts
330
- var NODE_MODULES2 = "node_modules";
331
- var BIN_DIR = ".bin";
332
- var WINDOWS_PLATFORM = "win32";
333
- var WINDOWS_SHIM_SUFFIX = ".cmd";
334
- var PACKAGE_JSON = "package.json";
335
- function resolve_local_bin(project_root, bin_name) {
336
- const shim = process.platform === WINDOWS_PLATFORM ? `${bin_name}${WINDOWS_SHIM_SUFFIX}` : bin_name;
337
- return path3.join(project_root, NODE_MODULES2, BIN_DIR, shim);
338
- }
339
- function read_bin_entry(manifest_path, package_name, bin_name) {
340
- const { bin } = package_bin_schema.parse(JSON.parse(readFileSync2(manifest_path, "utf8")));
341
- if (typeof bin === "string") return bin_name === path3.basename(package_name) ? bin : void 0;
342
- return bin?.[bin_name];
343
- }
344
- function resolve_package_bin(base_directory, package_name, bin_name) {
345
- try {
346
- const resolve_from = createRequire(path3.join(base_directory, PACKAGE_JSON));
347
- const manifest_path = resolve_from.resolve(`${package_name}/${PACKAGE_JSON}`);
348
- const bin_entry = read_bin_entry(manifest_path, package_name, bin_name);
349
- if (bin_entry === void 0) return void 0;
350
- const cli_entry = path3.join(path3.dirname(manifest_path), bin_entry);
351
- return existsSync2(cli_entry) ? cli_entry : void 0;
352
- } catch {
353
- return void 0;
354
- }
355
- }
356
-
357
270
  // scripts/gate/core-budget.ts
358
271
  import { randomUUID as randomUUID3 } from "node:crypto";
359
272
  import { readdirSync as readdirSync2 } from "node:fs";
@@ -364,7 +277,7 @@ import path8 from "node:path";
364
277
  import { accessSync, constants } from "node:fs";
365
278
  import { tmpdir } from "node:os";
366
279
  var POSIX_TEMP_ROOT = "/tmp";
367
- var WINDOWS_PLATFORM2 = "win32";
280
+ var WINDOWS_PLATFORM = "win32";
368
281
  function is_writable_directory(directory) {
369
282
  try {
370
283
  accessSync(directory, constants.W_OK);
@@ -374,7 +287,7 @@ function is_writable_directory(directory) {
374
287
  }
375
288
  }
376
289
  function resolve_temporary_root(platform2, can_write = is_writable_directory) {
377
- if (platform2 === WINDOWS_PLATFORM2) return tmpdir();
290
+ if (platform2 === WINDOWS_PLATFORM) return tmpdir();
378
291
  return can_write(POSIX_TEMP_ROOT) ? POSIX_TEMP_ROOT : tmpdir();
379
292
  }
380
293
  var TEMP_ROOT_KEY = "JOSH_TEMP_ROOT";
@@ -386,9 +299,9 @@ var PLATFORM_TEMP_ROOT = temporary_root(process.env[TEMP_ROOT_KEY], process.plat
386
299
  // scripts/josh/process-identity.ts
387
300
  import { execFileSync, spawnSync } from "node:child_process";
388
301
  import { randomUUID } from "node:crypto";
389
- import { readFileSync as readFileSync3, rmSync } from "node:fs";
302
+ import { readFileSync as readFileSync2, rmSync } from "node:fs";
390
303
  import { createServer } from "node:net";
391
- import path4 from "node:path";
304
+ import path2 from "node:path";
392
305
  var PS_COMMAND = "/bin/ps";
393
306
  var FORMAT_FLAG = "-o";
394
307
  var START_TIME_FORMAT = "lstart=";
@@ -403,7 +316,7 @@ var BEACON_SUFFIX = ".sock";
403
316
  var BEACON_PREFIX = `josh-process-identity-${String(process.getuid?.() ?? "")}-`;
404
317
  var BEACON_ID_LENGTH = 36;
405
318
  var BEACON_ID_PATTERN = /^[\da-f-]+$/u;
406
- var WINDOWS_PLATFORM3 = "win32";
319
+ var WINDOWS_PLATFORM2 = "win32";
407
320
  var BEACON_LIVE_STATUS = 0;
408
321
  var BEACON_UNKNOWN_STATUS = 1;
409
322
  var BEACON_GONE_STATUS = 2;
@@ -426,7 +339,7 @@ function is_present_pid(pid) {
426
339
  }
427
340
  function read_proc_start(pid) {
428
341
  try {
429
- const stat = readFileSync3(`/proc/${String(pid)}/stat`, "utf8");
342
+ const stat = readFileSync2(`/proc/${String(pid)}/stat`, "utf8");
430
343
  const fields = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/u);
431
344
  const start = fields[PROC_START_FIELD_INDEX];
432
345
  return start === void 0 ? void 0 : `proc:${start}`;
@@ -491,7 +404,7 @@ function try_bind_beacon(bind, target) {
491
404
  }
492
405
  }
493
406
  function open_beacon(bind = bind_beacon) {
494
- const target = path4.join(PLATFORM_TEMP_ROOT, `${BEACON_PREFIX}${randomUUID()}${BEACON_SUFFIX}`);
407
+ const target = path2.join(PLATFORM_TEMP_ROOT, `${BEACON_PREFIX}${randomUUID()}${BEACON_SUFFIX}`);
495
408
  const token = `${BEACON_SCHEME}${target}`;
496
409
  const server = try_bind_beacon(bind, target);
497
410
  if (server === void 0) return void 0;
@@ -502,7 +415,7 @@ function open_beacon(bind = bind_beacon) {
502
415
  }
503
416
  function resolve_own_start(probes = START_PROBES, platform2 = process.platform) {
504
417
  const start = read_start(process.pid, probes);
505
- if (start !== void 0 || platform2 === WINDOWS_PLATFORM3) return start;
418
+ if (start !== void 0 || platform2 === WINDOWS_PLATFORM2) return start;
506
419
  return open_beacon();
507
420
  }
508
421
  function is_valid_beacon_name(name) {
@@ -513,8 +426,8 @@ function is_valid_beacon_name(name) {
513
426
  function beacon_target(token) {
514
427
  if (!token.startsWith(BEACON_SCHEME)) return void 0;
515
428
  const target = token.slice(BEACON_SCHEME.length);
516
- if (path4.dirname(target) !== PLATFORM_TEMP_ROOT) return void 0;
517
- return is_valid_beacon_name(path4.basename(target)) ? target : void 0;
429
+ if (path2.dirname(target) !== PLATFORM_TEMP_ROOT) return void 0;
430
+ return is_valid_beacon_name(path2.basename(target)) ? target : void 0;
518
431
  }
519
432
  function run_socket_probe(target) {
520
433
  return spawnSync(process.execPath, ["-e", SOCKET_PROBE_SOURCE, target], {
@@ -573,18 +486,18 @@ var process_identity = {
573
486
  };
574
487
 
575
488
  // scripts/josh/process-owner.ts
576
- import { z as z3 } from "zod";
577
- var process_owner_schema = z3.object({ pid: z3.number(), process_start: z3.string().optional() });
489
+ import { z as z2 } from "zod";
490
+ var process_owner_schema = z2.object({ pid: z2.number(), process_start: z2.string().optional() });
578
491
 
579
492
  // scripts/josh/stamp-file.ts
580
493
  import { createHash, randomUUID as randomUUID2 } from "node:crypto";
581
- import { lstatSync, readFileSync as readFileSync4, renameSync, rmSync as rmSync2, writeFileSync } from "node:fs";
582
- import path6 from "node:path";
494
+ import { lstatSync, readFileSync as readFileSync3, renameSync, rmSync as rmSync2, writeFileSync } from "node:fs";
495
+ import path4 from "node:path";
583
496
 
584
497
  // scripts/init/init-paths.ts
585
- import path5 from "node:path";
498
+ import path3 from "node:path";
586
499
  import { fileURLToPath } from "node:url";
587
- var PACKAGE_DIR = path5.join(path5.dirname(fileURLToPath(import.meta.url)), "..", "..");
500
+ var PACKAGE_DIR = path3.join(path3.dirname(fileURLToPath(import.meta.url)), "..", "..");
588
501
  var PROJECT_ROOT = process.cwd();
589
502
 
590
503
  // scripts/josh/stamp-file.ts
@@ -604,7 +517,7 @@ function account_key() {
604
517
  }
605
518
  function stamp_path(prefix, root = PACKAGE_DIR, suffix = STAMP_SUFFIX) {
606
519
  const key = digest(`${account_key()}${ACCOUNT_KEY_SEPARATOR}${root}`).slice(0, STAMP_KEY_LENGTH);
607
- return path6.join(PLATFORM_TEMP_ROOT, `${prefix}${key}${suffix}`);
520
+ return path4.join(PLATFORM_TEMP_ROOT, `${prefix}${key}${suffix}`);
608
521
  }
609
522
  function write_exclusively(target, content) {
610
523
  rmSync2(target, { force: true });
@@ -664,7 +577,7 @@ function remove_stamp(target) {
664
577
  function read_stamp_text(source) {
665
578
  try {
666
579
  if (!is_own_regular_file(source)) return void 0;
667
- return readFileSync4(source, "utf8");
580
+ return readFileSync3(source, "utf8");
668
581
  } catch {
669
582
  return void 0;
670
583
  }
@@ -685,7 +598,7 @@ var stamp_file = {
685
598
  // scripts/test/unit-worker-share.ts
686
599
  import { readdirSync } from "node:fs";
687
600
  import { availableParallelism } from "node:os";
688
- import path7 from "node:path";
601
+ import path5 from "node:path";
689
602
  var RUN_PREFIX = "josh-unit-running-";
690
603
  var WORKER_FLAG = "--maxWorkers";
691
604
  var WORKER_FLAGS = [WORKER_FLAG, "--max-workers"];
@@ -724,7 +637,7 @@ function is_marker_live(source) {
724
637
  return false;
725
638
  }
726
639
  function live_run_count(directory = PLATFORM_TEMP_ROOT) {
727
- return marker_files(directory).filter((name) => is_marker_live(path7.join(directory, name))).length;
640
+ return marker_files(directory).filter((name) => is_marker_live(path5.join(directory, name))).length;
728
641
  }
729
642
  var NESTED_KEY = "JOSH_UNIT_RUN_MARKED";
730
643
  var NESTED_VALUE = "1";
@@ -735,17 +648,23 @@ function resolve_unit_workers(available_cores, runs) {
735
648
  if (runs <= SOLO_RUNS) return void 0;
736
649
  return Math.max(MIN_WORKERS, Math.floor(available_cores / runs));
737
650
  }
738
- function current_share(available_cores = availableParallelism(), live_runs = live_run_count()) {
651
+ function cap_to_free(workers, free_cores, available_cores) {
652
+ if (free_cores === void 0 || free_cores >= (workers ?? available_cores)) return workers;
653
+ return Math.max(MIN_WORKERS, Math.floor(free_cores));
654
+ }
655
+ function current_share(available_cores = availableParallelism(), live_runs = live_run_count(), free_cores) {
739
656
  const own_run = is_nested_run() ? 0 : SOLO_RUNS;
740
- return resolve_unit_workers(available_cores, live_runs + own_run);
657
+ const share = resolve_unit_workers(available_cores, live_runs + own_run);
658
+ return cap_to_free(share, free_cores, available_cores);
741
659
  }
742
660
  function is_worker_flag(argument) {
743
661
  return WORKER_FLAGS.some((flag) => argument === flag || argument.startsWith(`${flag}=`));
744
662
  }
663
+ function has_worker_flag(vitest_arguments) {
664
+ return vitest_arguments.some((argument) => is_worker_flag(argument));
665
+ }
745
666
  function worker_arguments(vitest_arguments, share) {
746
- if (share === void 0 || vitest_arguments.some((argument) => is_worker_flag(argument))) {
747
- return [];
748
- }
667
+ if (share === void 0 || has_worker_flag(vitest_arguments)) return [];
749
668
  return [`${WORKER_FLAG}=${String(share)}`];
750
669
  }
751
670
  function mark_run_started(target) {
@@ -772,7 +691,9 @@ var unit_worker_share = {
772
691
  NESTED_VALUE,
773
692
  RUN_PREFIX,
774
693
  SOLO_RUNS,
694
+ cap_to_free,
775
695
  current_share,
696
+ has_worker_flag,
776
697
  is_nested_run,
777
698
  live_run_count,
778
699
  marker_path,
@@ -783,7 +704,7 @@ var unit_worker_share = {
783
704
  };
784
705
 
785
706
  // scripts/gate/core-budget.ts
786
- import { z as z4 } from "zod";
707
+ import { z as z3 } from "zod";
787
708
 
788
709
  // scripts/gate/core-admission.ts
789
710
  var MS_PER_SECOND = 1e3;
@@ -856,6 +777,151 @@ var core_admission = {
856
777
  sum_load
857
778
  };
858
779
 
780
+ // scripts/gate/core-wait.ts
781
+ import path7 from "node:path";
782
+
783
+ // scripts/lane/lane-paths.ts
784
+ import path6 from "node:path";
785
+
786
+ // scripts/issue/issue-number-shape.ts
787
+ var ISSUE_NUMBER_PATTERN = /^[1-9]\d*$/u;
788
+ var BAD_ISSUE_MESSAGE = "Not an issue number: ";
789
+ function is_issue_number(value) {
790
+ return ISSUE_NUMBER_PATTERN.test(value);
791
+ }
792
+ function require_issue_number(issue) {
793
+ if (!is_issue_number(issue)) throw new Error(`${BAD_ISSUE_MESSAGE}${issue}`);
794
+ }
795
+ var issue_number_shape = { ISSUE_NUMBER_PATTERN, is_issue_number, require_issue_number };
796
+
797
+ // scripts/lane/lane-paths.ts
798
+ var LANE_ROOT_KEY = "JOSH_LANE_ROOT";
799
+ var LANE_DIRECTORY_SUFFIX = "-lanes";
800
+ var LANE_BRANCH_SUFFIX = "-lane";
801
+ function default_lane_root(repository_root) {
802
+ const name = path6.basename(repository_root);
803
+ return path6.join(path6.dirname(repository_root), `.${name}${LANE_DIRECTORY_SUFFIX}`);
804
+ }
805
+ function lane_root(repository_root, environment = process.env) {
806
+ const configured = environment[LANE_ROOT_KEY]?.trim() ?? "";
807
+ return configured.length === 0 ? default_lane_root(repository_root) : path6.resolve(configured);
808
+ }
809
+ function lane_directory(root, issue) {
810
+ return path6.join(root, issue);
811
+ }
812
+ var DEFAULT_LANE_ROOT_PATTERN = new RegExp(String.raw`^\..+${LANE_DIRECTORY_SUFFIX}$`, "u");
813
+ function is_lane_root(directory, environment) {
814
+ const configured = environment[LANE_ROOT_KEY]?.trim() ?? "";
815
+ if (configured.length > 0) return path6.resolve(configured) === directory;
816
+ return DEFAULT_LANE_ROOT_PATTERN.test(path6.basename(directory));
817
+ }
818
+ function lane_issue_of(directory, environment = process.env) {
819
+ const resolved = path6.resolve(directory);
820
+ const issue = path6.basename(resolved);
821
+ if (!issue_number_shape.ISSUE_NUMBER_PATTERN.test(issue)) return void 0;
822
+ return is_lane_root(path6.dirname(resolved), environment) ? issue : void 0;
823
+ }
824
+ function lane_branch(issue) {
825
+ return `${issue}${LANE_BRANCH_SUFFIX}`;
826
+ }
827
+ var SEAT_LOCK_DIR = ".seat-locks";
828
+ function is_lane_entry(name) {
829
+ return issue_number_shape.ISSUE_NUMBER_PATTERN.test(name) || DEFAULT_LANE_ROOT_PATTERN.test(name);
830
+ }
831
+ var lane_paths = {
832
+ LANE_BRANCH_SUFFIX,
833
+ LANE_ROOT_KEY,
834
+ SEAT_LOCK_DIR,
835
+ default_lane_root,
836
+ is_lane_entry,
837
+ lane_branch,
838
+ lane_directory,
839
+ lane_issue_of,
840
+ lane_root
841
+ };
842
+
843
+ // scripts/gate/core-wait.ts
844
+ var WAIT_REASONS = ["cores", "memory", "pressure"];
845
+ var REASON_TEXT = {
846
+ cores: "not enough free cores",
847
+ memory: "not enough free memory",
848
+ pressure: "memory under pressure"
849
+ };
850
+ function wait_reason(key, reservations, budget) {
851
+ const cores_only = { cores: budget.cores, memory_mb: Infinity };
852
+ if (!core_admission.is_admitted(key, reservations, cores_only)) return "cores";
853
+ return budget.memory_pressured === true ? "pressure" : "memory";
854
+ }
855
+ function wait_line(command, reason) {
856
+ return `\u23F3 ${command ?? "a step"} waits for the core budget \u2014 ${REASON_TEXT[reason]}`;
857
+ }
858
+ function lanes_root_of(checkout) {
859
+ if (lane_paths.lane_issue_of(checkout) === void 0) return lane_paths.lane_root(checkout);
860
+ return path7.dirname(path7.resolve(checkout));
861
+ }
862
+ function where_of(checkout) {
863
+ return { checkout, lane: lane_paths.lane_issue_of(checkout), lanes_root: lanes_root_of(checkout) };
864
+ }
865
+ function place_of(marker, home_root) {
866
+ const { checkout, lane, lanes_root } = marker;
867
+ if (checkout === void 0 || lanes_root === void 0 || lanes_root === home_root) {
868
+ return { lane, elsewhere: void 0 };
869
+ }
870
+ const name = lane === void 0 ? path7.basename(checkout) : `${path7.basename(lanes_root)}/${lane}`;
871
+ return { lane: void 0, elsewhere: name };
872
+ }
873
+ function wait_of(marker, now, home_root) {
874
+ const { command, wait_reason: reason } = marker;
875
+ if (reason === void 0 || marker.admitted_at !== void 0) return [];
876
+ return [{ command, ...place_of(marker, home_root), waited_ms: now - marker.claimed_at, reason }];
877
+ }
878
+ function waits_of(entries, now, home) {
879
+ const home_root = lanes_root_of(home);
880
+ return entries.flatMap((entry) => wait_of(entry.reservation, now, home_root)).toSorted((left, right) => right.waited_ms - left.waited_ms);
881
+ }
882
+ var core_wait = {
883
+ REASON_TEXT,
884
+ WAIT_REASONS,
885
+ wait_line,
886
+ wait_reason,
887
+ waits_of,
888
+ where_of
889
+ };
890
+
891
+ // scripts/gate/held-mark.ts
892
+ var HELD_KEY = "JOSH_CORE_RESERVED";
893
+ var HELD_VALUE = "1";
894
+ var HELD_RESERVATION_KEY = "JOSH_CORE_RESERVATION";
895
+ function is_held() {
896
+ return process.env[HELD_KEY] === HELD_VALUE;
897
+ }
898
+ function held_reservation() {
899
+ const key = process.env[HELD_RESERVATION_KEY] ?? "";
900
+ return key === "" ? void 0 : key;
901
+ }
902
+ function set_held_mark(value, reservation) {
903
+ process.env[HELD_KEY] = value;
904
+ process.env[HELD_RESERVATION_KEY] = reservation;
905
+ }
906
+ async function with_held_mark(run2, reservation = "") {
907
+ const previous = process.env[HELD_KEY] ?? "";
908
+ const previous_reservation = process.env[HELD_RESERVATION_KEY] ?? "";
909
+ set_held_mark(HELD_VALUE, reservation);
910
+ try {
911
+ return await run2();
912
+ } finally {
913
+ set_held_mark(previous, previous_reservation);
914
+ }
915
+ }
916
+ var held_mark = {
917
+ HELD_KEY,
918
+ HELD_RESERVATION_KEY,
919
+ HELD_VALUE,
920
+ held_reservation,
921
+ is_held,
922
+ with_held_mark
923
+ };
924
+
859
925
  // scripts/gate/machine-capacity.ts
860
926
  import { readFile } from "node:fs/promises";
861
927
  import { availableParallelism as availableParallelism2, cpus, totalmem } from "node:os";
@@ -874,9 +940,20 @@ var PAGE_SIZE_ARGUMENTS = ["PAGESIZE"];
874
940
  var LEVEL_NAME = "kern.memorystatus_level";
875
941
  var SWAP_IN_NAME = "vm.compressor.swapper.swapins_total";
876
942
  var SWAP_OUT_NAME = "vm.compressor.swapper.swapouts_total";
943
+ var COMPRESSED_NAME = "vm.compressor_input_bytes";
877
944
  var PAGE_SIZE_NAME = "hw.pagesize";
878
945
  var PRESSURE_NAME = "kern.memorystatus_vm_pressure_level";
879
- var SYSCTL_ARGUMENTS = [LEVEL_NAME, SWAP_IN_NAME, SWAP_OUT_NAME, PAGE_SIZE_NAME, PRESSURE_NAME];
946
+ var PRESSURE_WARNING = 2;
947
+ var SWAP_PRESSURE_MB = 16;
948
+ var COMPRESSION_PRESSURE_MB = 64;
949
+ var SYSCTL_ARGUMENTS = [
950
+ LEVEL_NAME,
951
+ SWAP_IN_NAME,
952
+ SWAP_OUT_NAME,
953
+ COMPRESSED_NAME,
954
+ PAGE_SIZE_NAME,
955
+ PRESSURE_NAME
956
+ ];
880
957
  var SYSCTL_LINE = /^(?<name>[\w.]+): (?<value>\d+)$/gmu;
881
958
  function cpu_times() {
882
959
  let busy = 0;
@@ -905,9 +982,9 @@ function sysctl_values(output) {
905
982
  function swapped_pages(pages_in, pages_out) {
906
983
  return pages_in === void 0 || pages_out === void 0 ? void 0 : pages_in + pages_out;
907
984
  }
908
- function swapped_mb(pages, page_bytes) {
909
- if (pages === void 0 || page_bytes === void 0) return void 0;
910
- return pages * page_bytes / BYTES_PER_MB;
985
+ function mb_of(units, unit_bytes) {
986
+ if (units === void 0 || unit_bytes === void 0) return void 0;
987
+ return units * unit_bytes / BYTES_PER_MB;
911
988
  }
912
989
  function swapped_between(from, to) {
913
990
  if (from === void 0 || to === void 0) return void 0;
@@ -918,11 +995,12 @@ function parse_darwin_memory(output, total_bytes) {
918
995
  const level = values.get(LEVEL_NAME);
919
996
  return {
920
997
  available_mb: level === void 0 ? void 0 : level / PERCENT * (total_bytes / BYTES_PER_MB),
921
- swapped_mb: swapped_mb(
998
+ swapped_mb: mb_of(
922
999
  swapped_pages(values.get(SWAP_IN_NAME), values.get(SWAP_OUT_NAME)),
923
1000
  values.get(PAGE_SIZE_NAME)
924
1001
  ),
925
- pressure_level: values.get(PRESSURE_NAME)
1002
+ pressure_level: values.get(PRESSURE_NAME),
1003
+ compressed_mb: mb_of(values.get(COMPRESSED_NAME), 1)
926
1004
  };
927
1005
  }
928
1006
  function parse_linux_available(meminfo) {
@@ -935,7 +1013,7 @@ function pages_of(pattern, vmstat) {
935
1013
  }
936
1014
  function parse_linux_swapped(vmstat, page_bytes) {
937
1015
  const pages = swapped_pages(pages_of(LINUX_SWAP_IN, vmstat), pages_of(LINUX_SWAP_OUT, vmstat));
938
- return swapped_mb(pages, page_bytes);
1016
+ return mb_of(pages, page_bytes);
939
1017
  }
940
1018
  async function settled(read2) {
941
1019
  try {
@@ -985,11 +1063,26 @@ async function default_sleep(ms) {
985
1063
  setTimeout(resolve, ms);
986
1064
  });
987
1065
  }
1066
+ function moved_over(from, to, limit) {
1067
+ return (swapped_between(from, to) ?? 0) > limit;
1068
+ }
1069
+ function is_pressured(before, after) {
1070
+ 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);
1071
+ }
1072
+ function pressured_available(before, after) {
1073
+ if (after.available_mb === void 0) return void 0;
1074
+ return is_pressured(before, after) ? 0 : after.available_mb;
1075
+ }
988
1076
  async function read_machine(sleep = default_sleep) {
989
1077
  const before = cpu_times();
990
- const [memory] = await Promise.all([read_memory(), sleep(SAMPLE_WINDOW_MS)]);
1078
+ const [memory_before] = await Promise.all([read_memory(), sleep(SAMPLE_WINDOW_MS)]);
1079
+ const memory_after = await read_memory();
991
1080
  const busy_cores = is_unquoted() ? busy_cores_between(before, cpu_times(), cpus().length) : void 0;
992
- return { busy_cores, available_mb: memory.available_mb };
1081
+ return {
1082
+ busy_cores,
1083
+ available_mb: pressured_available(memory_before, memory_after),
1084
+ memory_pressured: is_pressured(memory_before, memory_after)
1085
+ };
993
1086
  }
994
1087
  async function read_sample() {
995
1088
  return { cpu: cpu_times(), memory: await read_memory(), total_mb: totalmem() / BYTES_PER_MB };
@@ -1001,21 +1094,25 @@ function external_cores(busy_cores, ledger_cores) {
1001
1094
  function machine_budget(cores, reading, ledger) {
1002
1095
  return {
1003
1096
  cores: cores - external_cores(reading.busy_cores, ledger.cores),
1004
- memory_mb: reading.available_mb === void 0 ? Infinity : reading.available_mb + ledger.memory_mb
1097
+ memory_mb: reading.available_mb === void 0 ? Infinity : reading.available_mb + ledger.memory_mb,
1098
+ memory_pressured: reading.memory_pressured
1005
1099
  };
1006
1100
  }
1007
1101
  var machine_capacity = {
1008
1102
  BASELINE_CORES,
1009
1103
  busy_cores_between,
1010
1104
  busy_share_between,
1105
+ COMPRESSION_PRESSURE_MB,
1011
1106
  external_cores,
1012
1107
  machine_budget,
1013
1108
  parse_darwin_memory,
1014
1109
  parse_linux_available,
1015
1110
  parse_linux_swapped,
1111
+ pressured_available,
1016
1112
  read_machine,
1017
1113
  read_memory,
1018
1114
  read_sample,
1115
+ SWAP_PRESSURE_MB,
1019
1116
  swapped_between
1020
1117
  };
1021
1118
 
@@ -1029,22 +1126,32 @@ var CORE_WEIGHTS = {
1029
1126
  lint: 2,
1030
1127
  type_check: 1,
1031
1128
  spell_check: 1,
1032
- eslint_scan: 1
1129
+ eslint_scan: 1,
1130
+ format: 1,
1131
+ retrospective: 1,
1132
+ install: 1,
1133
+ e2e: 2
1033
1134
  };
1034
1135
  var MEMORY_MB = {
1035
1136
  lint: 2400,
1036
1137
  type_check: 1300,
1037
1138
  spell_check: 350,
1038
1139
  eslint_scan: 2400,
1039
- unit_worker: 350
1140
+ unit_worker: 350,
1141
+ format: 300,
1142
+ retrospective: 900,
1143
+ install: 100
1040
1144
  };
1041
- var HELD_KEY = "JOSH_CORE_RESERVED";
1042
- var HELD_VALUE = "1";
1043
1145
  var reservation_schema = process_owner_schema.extend({
1044
- weight: z4.number(),
1045
- claimed_at: z4.number(),
1046
- memory_mb: z4.number().optional(),
1047
- admitted_at: z4.number().optional()
1146
+ weight: z3.number(),
1147
+ claimed_at: z3.number(),
1148
+ memory_mb: z3.number().optional(),
1149
+ admitted_at: z3.number().optional(),
1150
+ command: z3.string().optional(),
1151
+ checkout: z3.string().optional(),
1152
+ lane: z3.string().optional(),
1153
+ lanes_root: z3.string().optional(),
1154
+ wait_reason: z3.enum(core_wait.WAIT_REASONS).optional()
1048
1155
  });
1049
1156
  function marker_name(key) {
1050
1157
  return `${RESERVED_PREFIX}${key}${RESERVED_SUFFIX}`;
@@ -1096,6 +1203,10 @@ async function default_sleep2(ms) {
1096
1203
  function write_reservation(target, fields) {
1097
1204
  stamp_file.replace_stamp(target, { ...process_identity.own_fields(), ...fields });
1098
1205
  }
1206
+ function report_wait(line) {
1207
+ process.stderr.write(`${line}
1208
+ `);
1209
+ }
1099
1210
  function resolve_timing(options) {
1100
1211
  return {
1101
1212
  now: options.now ?? Date.now,
@@ -1122,6 +1233,13 @@ function overflow_of(context, reservations, budget) {
1122
1233
  budget
1123
1234
  };
1124
1235
  }
1236
+ function mark_waiting(context, reason) {
1237
+ const previous = context.fields.wait_reason;
1238
+ if (previous === reason) return;
1239
+ context.fields = { ...context.fields, wait_reason: reason };
1240
+ write_reservation(context.target, context.fields);
1241
+ if (previous === void 0) context.report(core_wait.wait_line(context.fields.command, reason));
1242
+ }
1125
1243
  async function poll_admission(context) {
1126
1244
  const reservations = live_reservations(context.directory);
1127
1245
  const budget = await current_budget(context, reservations);
@@ -1129,6 +1247,7 @@ async function poll_admission(context) {
1129
1247
  if (context.now() >= context.deadline) {
1130
1248
  return admit(context, overflow_of(context, reservations, budget));
1131
1249
  }
1250
+ mark_waiting(context, core_wait.wait_reason(context.key, reservations, budget));
1132
1251
  return void 0;
1133
1252
  }
1134
1253
  async function await_admission(context) {
@@ -1139,13 +1258,23 @@ async function await_admission(context) {
1139
1258
  }
1140
1259
  return admission;
1141
1260
  }
1261
+ function resolve_ports(options) {
1262
+ return {
1263
+ read_machine: options.read_machine ?? machine_capacity.read_machine,
1264
+ report: options.report ?? report_wait
1265
+ };
1266
+ }
1267
+ function claim_fields(weight, claimed_at, options) {
1268
+ const { memory_mb, command } = options;
1269
+ return { weight, claimed_at, memory_mb, command, ...core_wait.where_of(process.cwd()) };
1270
+ }
1142
1271
  async function reserve(weight, options = {}) {
1143
1272
  const timing = resolve_timing(options);
1144
1273
  const directory = options.directory ?? PLATFORM_TEMP_ROOT;
1145
1274
  const claimed_at = timing.now();
1146
1275
  const name = marker_name(randomUUID3());
1147
1276
  const target = path8.join(directory, name);
1148
- const fields = { weight, claimed_at, memory_mb: options.memory_mb };
1277
+ const fields = claim_fields(weight, claimed_at, options);
1149
1278
  write_reservation(target, fields);
1150
1279
  const admission = await await_admission({
1151
1280
  ...timing,
@@ -1155,92 +1284,134 @@ async function reserve(weight, options = {}) {
1155
1284
  budget: normalize_budget(options.budget ?? availableParallelism3()),
1156
1285
  deadline: claimed_at + timing.wait_cap_ms,
1157
1286
  fields,
1158
- read_machine: options.read_machine ?? machine_capacity.read_machine
1287
+ ...resolve_ports(options)
1159
1288
  });
1160
1289
  return { target, admission };
1161
1290
  }
1291
+ function budget_waits(directory = PLATFORM_TEMP_ROOT, now = Date.now(), home = process.cwd()) {
1292
+ return core_wait.waits_of(live_reservations(directory), now, home);
1293
+ }
1162
1294
  function release(handle) {
1163
1295
  sweep_marker2(handle.target);
1164
1296
  }
1165
1297
  async function with_core_reservation(weight, run2, options = {}) {
1166
1298
  const handle = await reserve(weight, options);
1167
1299
  try {
1168
- return await run2(handle.admission);
1300
+ return await run2(handle.admission, path8.basename(handle.target));
1169
1301
  } finally {
1170
1302
  release(handle);
1171
1303
  }
1172
1304
  }
1173
- function is_held() {
1174
- return process.env[HELD_KEY] === HELD_VALUE;
1175
- }
1176
- function set_held_mark(value) {
1177
- process.env[HELD_KEY] = value;
1178
- }
1179
- async function with_held_mark(run2) {
1180
- const previous = process.env[HELD_KEY] ?? "";
1181
- set_held_mark(HELD_VALUE);
1182
- try {
1183
- return await run2();
1184
- } finally {
1185
- set_held_mark(previous);
1186
- }
1187
- }
1188
1305
  async function with_command_reservation(weight, run2, options = {}) {
1189
- if (is_held() || unit_worker_share.is_nested_run()) return await run2();
1190
- return await with_core_reservation(weight, async () => await with_held_mark(run2), options);
1306
+ if (held_mark.is_held() || unit_worker_share.is_nested_run()) return await run2();
1307
+ return await with_core_reservation(
1308
+ weight,
1309
+ async (_admission, key) => await held_mark.with_held_mark(run2, key),
1310
+ options
1311
+ );
1191
1312
  }
1192
1313
  var core_budget = {
1193
1314
  CORE_WEIGHTS,
1194
- HELD_KEY,
1195
- HELD_VALUE,
1315
+ HELD_KEY: held_mark.HELD_KEY,
1316
+ HELD_VALUE: held_mark.HELD_VALUE,
1196
1317
  MEMORY_MB,
1197
1318
  RESERVED_PREFIX,
1198
1319
  admitted_keys: core_admission.admitted_keys,
1199
1320
  admitted_load: core_admission.admitted_load,
1321
+ budget_waits,
1200
1322
  describe_overflow: core_admission.describe_overflow,
1201
- is_held,
1323
+ is_held: held_mark.is_held,
1202
1324
  live_reservations,
1203
1325
  release,
1204
1326
  reserve,
1205
1327
  with_command_reservation,
1206
1328
  with_core_reservation,
1207
- with_held_mark
1329
+ with_held_mark: held_mark.with_held_mark
1208
1330
  };
1209
1331
 
1210
- // scripts/josh/josh-logic.ts
1211
- import { execaSync as execaSync2 } from "execa";
1212
-
1213
- // scripts/josh/command-suggest.ts
1214
- var MAX_SUGGESTION_DISTANCE = 3;
1215
- function at(values, index) {
1216
- return values[index] ?? 0;
1332
+ // ports/index.js
1333
+ import { existsSync as existsSync2 } from "node:fs";
1334
+ import path9 from "node:path";
1335
+ var PORT_SEED_KEY = "PORT_SEED";
1336
+ var LANE_SEAT_KEY = "JOSH_LANE_SEAT";
1337
+ var ENV_FILE_NAME = ".env";
1338
+ var PACKAGE_FILE_NAME = "package.json";
1339
+ var PROJECT_ENVIRONMENT_KEYS = /* @__PURE__ */ new Set([PORT_SEED_KEY, LANE_SEAT_KEY, "PLAYWRIGHT_REUSE_SERVER"]);
1340
+ var DEV_PORT_BASE = 5173;
1341
+ var PREVIEW_PORT_BASE = 4173;
1342
+ var MIN_SEED = 0;
1343
+ var SEED_LANE_MULTIPLIER = 10;
1344
+ var MAX_LANE = 9;
1345
+ var MAX_SEED = 99;
1346
+ var INTEGER_PATTERN = /^\d+$/u;
1347
+ function fail_invalid(key, raw, max) {
1348
+ throw new Error(
1349
+ `${key} must be an integer between ${MIN_SEED} and ${max}, got ${JSON.stringify(raw)}. Unset it to use the default ports (dev ${DEV_PORT_BASE}, preview ${PREVIEW_PORT_BASE}).`
1350
+ );
1217
1351
  }
1218
- function fill_distance_row(source, target, row, previous) {
1219
- const current = [row];
1220
- for (let col = 1; col <= target.length; col++) {
1221
- const cost = source[row - 1] === target[col - 1] ? 0 : 1;
1222
- const deletion = at(previous, col) + 1;
1223
- const insertion = at(current, col - 1) + 1;
1224
- const substitution = at(previous, col - 1) + cost;
1225
- current.push(Math.min(deletion, insertion, substitution));
1352
+ function read_trimmed(environment, key) {
1353
+ const raw = environment[key];
1354
+ return raw === void 0 ? "" : raw.trim();
1355
+ }
1356
+ function resolve_bounded(environment, key, max) {
1357
+ const trimmed = read_trimmed(environment, key);
1358
+ if (trimmed.length === 0) return MIN_SEED;
1359
+ if (!INTEGER_PATTERN.test(trimmed)) fail_invalid(key, trimmed, max);
1360
+ const value = Number(trimmed);
1361
+ if (value > max) fail_invalid(key, trimmed, max);
1362
+ return value;
1363
+ }
1364
+ function resolve_seed(environment = process.env) {
1365
+ return resolve_bounded(environment, PORT_SEED_KEY, MAX_SEED);
1366
+ }
1367
+ function resolve_lane(environment = process.env) {
1368
+ return resolve_bounded(environment, LANE_SEAT_KEY, MAX_LANE);
1369
+ }
1370
+ function resolve_offset(environment = process.env) {
1371
+ return resolve_seed(environment) * SEED_LANE_MULTIPLIER + resolve_lane(environment);
1372
+ }
1373
+ function offset_from(base, environment) {
1374
+ return base + resolve_offset(environment);
1375
+ }
1376
+ function resolve_development_port(environment = process.env) {
1377
+ return offset_from(DEV_PORT_BASE, environment);
1378
+ }
1379
+ function resolve_preview_port(environment = process.env) {
1380
+ return offset_from(PREVIEW_PORT_BASE, environment);
1381
+ }
1382
+ function resolve_project_directory(directory) {
1383
+ const start = path9.resolve(directory);
1384
+ let current = start;
1385
+ while (!existsSync2(path9.join(current, PACKAGE_FILE_NAME))) {
1386
+ const parent = path9.dirname(current);
1387
+ if (parent === current) return start;
1388
+ current = parent;
1226
1389
  }
1227
1390
  return current;
1228
1391
  }
1229
- function edit_distance(source, target) {
1230
- let previous = Array.from({ length: target.length + 1 }, (_unused, index) => index);
1231
- for (let row = 1; row <= source.length; row++) {
1232
- previous = fill_distance_row(source, target, row, previous);
1392
+ function discard_loaded_keys(snapshot) {
1393
+ for (const key of Object.keys(process.env)) {
1394
+ if (!PROJECT_ENVIRONMENT_KEYS.has(key) && !Object.hasOwn(snapshot, key)) {
1395
+ Reflect.deleteProperty(process.env, key);
1396
+ }
1233
1397
  }
1234
- return at(previous, target.length);
1235
1398
  }
1236
- function closest_command(input, commands) {
1237
- const within = commands.map((command) => ({ command, distance: edit_distance(input, command) })).filter((scored) => scored.distance <= MAX_SUGGESTION_DISTANCE).toSorted((left, right) => left.distance - right.distance);
1238
- return within[0]?.command;
1239
- }
1240
- var command_suggest = { closest_command, edit_distance };
1241
-
1242
- // ports/index.js
1243
- var ENV_FILE_NAME = ".env";
1399
+ function load_environment_file(directory = process.cwd()) {
1400
+ const file = path9.join(resolve_project_directory(directory), ENV_FILE_NAME);
1401
+ if (!existsSync2(file)) return false;
1402
+ const snapshot = { ...process.env };
1403
+ process.loadEnvFile(file);
1404
+ discard_loaded_keys(snapshot);
1405
+ return true;
1406
+ }
1407
+ var ports = {
1408
+ load_environment_file,
1409
+ resolve_project_directory,
1410
+ resolve_seed,
1411
+ resolve_lane,
1412
+ resolve_development_port,
1413
+ resolve_preview_port
1414
+ };
1244
1415
 
1245
1416
  // scripts/josh/josh-command-types.ts
1246
1417
  var OPTIONAL_ENV_FILE_FLAGS = [`--env-file-if-exists=${ENV_FILE_NAME}`];
@@ -1250,6 +1421,10 @@ var TS_BUILD_INFO_FILE = ".tsbuildinfo";
1250
1421
  var CSPELL_CACHE_FILE = ".cspellcache";
1251
1422
  var ESLINT_EDIT_CACHE_FILE = ".eslintcache.edit";
1252
1423
  var ESLINT_RELATED_CACHE_FILE = ".eslintcache.related";
1424
+ var PRETTIER_CACHE_FILE = ".prettiercache";
1425
+ var PRETTIER_RELATED_CACHE_FILE = ".prettiercache.related";
1426
+ var CSPELL_COMMIT_CACHE_FILE = ".cspellcache.commit";
1427
+ var ESLINT_COMMIT_CACHE_FILE = ".eslintcache.commit";
1253
1428
  var ROOTED_JSON_PORTABILITY = "rooted-json";
1254
1429
  var ESLINT_CACHE_SPEC = {
1255
1430
  cache_file: ESLINT_CACHE_FILE,
@@ -1272,7 +1447,11 @@ var GATE_CACHE_FILES = GATE_CACHE_SPECS.map((spec) => spec.cache_file);
1272
1447
  var IGNORED_CACHE_FILES = [
1273
1448
  ...GATE_CACHE_FILES,
1274
1449
  ESLINT_EDIT_CACHE_FILE,
1275
- ESLINT_RELATED_CACHE_FILE
1450
+ ESLINT_RELATED_CACHE_FILE,
1451
+ PRETTIER_CACHE_FILE,
1452
+ PRETTIER_RELATED_CACHE_FILE,
1453
+ CSPELL_COMMIT_CACHE_FILE,
1454
+ ESLINT_COMMIT_CACHE_FILE
1276
1455
  ];
1277
1456
  var CONTENT_CACHE_FLAGS = ["--cache", "--cache-strategy", "content"];
1278
1457
  var CACHE_LOCATION_FLAG = "--cache-location";
@@ -1283,6 +1462,10 @@ var ESLINT_CACHE_FLAGS = content_cache_flags(ESLINT_CACHE_FILE);
1283
1462
  var ESLINT_EDIT_CACHE_FLAGS = content_cache_flags(ESLINT_EDIT_CACHE_FILE);
1284
1463
  var ESLINT_RELATED_CACHE_FLAGS = content_cache_flags(ESLINT_RELATED_CACHE_FILE);
1285
1464
  var CSPELL_CACHE_FLAGS = content_cache_flags(CSPELL_CACHE_FILE);
1465
+ var PRETTIER_CACHE_FLAGS = content_cache_flags(PRETTIER_CACHE_FILE);
1466
+ var PRETTIER_RELATED_CACHE_FLAGS = content_cache_flags(PRETTIER_RELATED_CACHE_FILE);
1467
+ var CSPELL_COMMIT_CACHE_FLAGS = content_cache_flags(CSPELL_COMMIT_CACHE_FILE);
1468
+ var ESLINT_COMMIT_CACHE_FLAGS = content_cache_flags(ESLINT_COMMIT_CACHE_FILE);
1286
1469
 
1287
1470
  // scripts/josh/josh-commands-backlog.ts
1288
1471
  var BACKLOG_COMMANDS = {
@@ -1546,6 +1729,14 @@ var LANE_COMMANDS = {
1546
1729
  category: "AI tools",
1547
1730
  reference: ["<issue>", "developer", ["files", "git", "processes", "network"]]
1548
1731
  },
1732
+ // `.env` is read for `JOSH_LANE_ROOT`, as every lane command reads it, so the lane is found.
1733
+ "lane:withdraw": {
1734
+ script: "scripts/lane/lane-withdraw-cli.ts",
1735
+ tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
1736
+ 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",
1737
+ category: "AI tools",
1738
+ reference: ["<issue> --reason <text>", "developer", ["files", "git", "processes", "network"]]
1739
+ },
1549
1740
  // A live run's lane limit: `.env` is read so the bare form reports the
1550
1741
  // `JOSH_LANE_LIMIT` a person set there when no override stands.
1551
1742
  "lane:limit": {
@@ -1566,7 +1757,7 @@ var LANE_COMMANDS = {
1566
1757
  },
1567
1758
  "lane:stats": {
1568
1759
  ...MEASURE_ARGUMENTS,
1569
- 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",
1760
+ 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",
1570
1761
  category: "AI tools",
1571
1762
  reference: ["--period <days> [--limit <lane-limit>]", "maintainer", ["none"]],
1572
1763
  default_script_arguments: ["stats"]
@@ -1772,6 +1963,15 @@ var AI_COMMANDS = {
1772
1963
  category: "AI tools",
1773
1964
  reference: ["(--cut | --over <tokens-per-request>) [--path <dir>]", "automation", ["none"]]
1774
1965
  },
1966
+ "cost:audit": {
1967
+ script: "scripts/cost-audit/cost-audit-cli.ts",
1968
+ description: "Report AI cost over a period by session kind, tool category and guard, with cost per merge and regressions against the period before",
1969
+ category: "AI tools",
1970
+ reference: ["[--period <days>]", "maintainer", ["none"]],
1971
+ // Kit-only: it reads kit's own transcript store, like `josh retrospective`, which carries its
1972
+ // summary line.
1973
+ is_kit_only: true
1974
+ },
1775
1975
  ...DOCUMENT_COMMANDS,
1776
1976
  time: {
1777
1977
  script: "scripts/time/time-cli.ts",
@@ -1799,6 +1999,8 @@ var AI_COMMANDS = {
1799
1999
  description: "Aggregate a finished run's cost, review findings, observation ledger and events into one digest",
1800
2000
  category: "AI tools",
1801
2001
  reference: ["", "maintainer", ["none"]],
2002
+ core_weight: core_budget.CORE_WEIGHTS.retrospective,
2003
+ memory_mb: core_budget.MEMORY_MB.retrospective,
1802
2004
  // Kit-only: it reads kit's own development run — the transcript store, the review ledger and the
1803
2005
  // run event stream — so it means nothing in a consumer project and is dropped from a consumer's
1804
2006
  // help, through the same declaration `josh time` uses for the same reason.
@@ -2000,6 +2202,7 @@ var TYPE_CHECK_LABEL = "check";
2000
2202
  var UNIT_LABEL = "test:unit";
2001
2203
  var BEHAVIOR_LABEL = "behavior";
2002
2204
  var METRICS_LABEL = "metrics";
2205
+ var EXPORTS_LABEL = "exports";
2003
2206
  var WARNING_CHECKER_LABELS = [LINT_LABEL, TYPE_CHECK_LABEL];
2004
2207
  var STATIC_CHECKS = [
2005
2208
  {
@@ -2025,7 +2228,7 @@ var STATIC_CHECKS = [
2025
2228
  // program and walks it for about 5s, and it is over well before lint and the type check. A
2026
2229
  // reservation would only take a place from the four-core CI runner's fan-out and a worker from the
2027
2230
  // unit suite for the whole gate.
2028
- { label: "exports", target: "exports:unused", reserved_cores: 0 },
2231
+ { label: EXPORTS_LABEL, target: "exports:unused", reserved_cores: 0 },
2029
2232
  // **The metrics ratchet is kit's own**: it holds kit's repository-wide
2030
2233
  // totals to the merge-base's. It is one in-process eslint pass with only `max-lines` enabled, over
2031
2234
  // in a few seconds, so it reserves no core for the reason the unused-member check above does not.
@@ -2114,6 +2317,15 @@ function resolve_gate_plan(available_cores = availableParallelism4(), concurrent
2114
2317
  unit_worker_cap: unit_cap_for(checks, available_cores, concurrent_runs)
2115
2318
  };
2116
2319
  }
2320
+ function bound_by_free(plan, available_cores, free_cores) {
2321
+ if (!has_unit_check(plan.checks)) return plan;
2322
+ const unit_worker_cap = unit_worker_share.cap_to_free(
2323
+ plan.unit_worker_cap,
2324
+ free_cores,
2325
+ available_cores
2326
+ );
2327
+ return { ...plan, unit_worker_cap };
2328
+ }
2117
2329
  function format_machine(available_cores, concurrent_runs) {
2118
2330
  const cores = `${String(available_cores)} cores`;
2119
2331
  if (concurrent_runs <= unit_worker_share.SOLO_RUNS) return cores;
@@ -2132,6 +2344,7 @@ function format_gate_plan(plan, available_cores = availableParallelism4(), concu
2132
2344
  return `${josh_verdict.GATE_OPENING_PREFIX}${width}, ${cap} (${machine})`;
2133
2345
  }
2134
2346
  var gate_plan = {
2347
+ EXPORTS_LABEL,
2135
2348
  GATE_CHECKS,
2136
2349
  LINT_LABEL,
2137
2350
  MEASURED_CORES,
@@ -2141,6 +2354,7 @@ var gate_plan = {
2141
2354
  TYPE_CHECK_LABEL,
2142
2355
  UNIT_LABEL,
2143
2356
  WARNING_CHECKER_LABELS,
2357
+ bound_by_free,
2144
2358
  check_memory,
2145
2359
  check_weight,
2146
2360
  direct_unit_memory,
@@ -2273,7 +2487,7 @@ var DEV_COMMANDS = {
2273
2487
  },
2274
2488
  "stop:guard": {
2275
2489
  script: "scripts/hooks/stop-guard.ts",
2276
- description: "Claude Code Stop hook: deliver the three stop-time rules \u2014 hold notify/release and issue citation (reads the Stop payload on stdin)",
2490
+ 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)",
2277
2491
  category: "Development",
2278
2492
  reference: ["", "automation", ["none"]]
2279
2493
  // **No `tsx_arguments`, deliberately**, the same as `pretool:guard` above: this runs at every
@@ -2407,6 +2621,12 @@ var HOOKS_COMMANDS = {
2407
2621
  category: "Git hooks",
2408
2622
  reference: ["[paths...]", "automation", ["none"]]
2409
2623
  },
2624
+ "pre-commit-lint": {
2625
+ script: "scripts/hooks/pre-commit-lint.ts",
2626
+ description: "Git hook: run cspell, prettier or eslint over staged files from a content cache",
2627
+ category: "Git hooks",
2628
+ reference: ["<cspell|prettier|eslint> [paths...]", "automation", ["files"]]
2629
+ },
2410
2630
  "pre-push-unit": {
2411
2631
  script: "scripts/hooks/pre-push-unit.ts",
2412
2632
  description: "Git hook: run unit tests, reusing a green gate recorded on the pushed tree",
@@ -2812,6 +3032,11 @@ var WORKFLOW_COMMANDS = {
2812
3032
  // A script rather than an `sh -c` chain, because the strategy has to be named rather than left to
2813
3033
  // the caller's git configuration: a bare `git pull` aborts with `Need to specify how to reconcile
2814
3034
  // divergent branches` on exactly the diverged branch the command exists for.
3035
+ //
3036
+ // **It declares no core weight, by measurement** (joshuafolkken/kit#3944): its own work is git, and
3037
+ // the catalog it may regenerate took 0.6s of CPU. The heavy processes seen beside it are commit
3038
+ // hooks — its regenerated catalog's commit runs them — which reserve themselves, and a weight here
3039
+ // would set the held mark they inherit, folding the parallel hook tools into this one place.
2815
3040
  "main:merge": {
2816
3041
  script: "scripts/git/main-merge.ts",
2817
3042
  description: "Merge origin default branch into the current branch",
@@ -2887,6 +3112,7 @@ var ALIASES = {
2887
3112
  tm: "time",
2888
3113
  tmd: "time:density",
2889
3114
  rtr: "retrospective",
3115
+ ca: "cost:audit",
2890
3116
  rvf: "review:findings",
2891
3117
  ruv: "rule:value",
2892
3118
  ev: "eval",
@@ -2896,73 +3122,1685 @@ var ALIASES = {
2896
3122
  lst: "lane:stats",
2897
3123
  lli: "lane:limit",
2898
3124
  lrs: "lane:restart",
3125
+ lwd: "lane:withdraw",
2899
3126
  blr: "backlogrun"
2900
3127
  };
2901
-
2902
- // scripts/josh/josh-composite-arguments.ts
2903
- var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["sh", "bash", "zsh", "dash"]);
2904
- var SHELL_COMMAND_FLAG = "-c";
2905
- var PATH_SEPARATOR = "/";
2906
- var USAGE_ERROR_EXIT_CODE = 1;
2907
- function is_shell_interpreter(executable) {
2908
- return SHELL_INTERPRETERS.has(executable.split(PATH_SEPARATOR).at(-1) ?? executable);
2909
- }
2910
- function is_composite_shell(shell) {
2911
- const [executable, flag] = shell ?? [];
2912
- if (executable === void 0) return false;
2913
- return is_shell_interpreter(executable) && flag === SHELL_COMMAND_FLAG;
2914
- }
2915
- function format_targets(targets) {
2916
- return targets.map((target) => `josh ${target}`).join(" or ");
2917
- }
2918
- function format_rejection(cmd, targets) {
2919
- const hint = targets.length > 0 ? `pass them to ${format_targets(targets)} instead` : "it runs a fixed sequence and forwards nothing";
2920
- return `josh ${cmd} takes no extra arguments \u2014 ${hint}`;
2921
- }
2922
- function reject_extra_arguments(cmd, entry, extra) {
2923
- if (extra.length === 0 || !is_composite_shell(entry.shell)) return void 0;
2924
- return format_rejection(cmd, entry.argument_targets ?? []);
3128
+ function canonical_command(name) {
3129
+ if (!Object.hasOwn(ALIASES, name)) return name;
3130
+ return ALIASES[name] ?? name;
2925
3131
  }
2926
- var composite_arguments = { format_rejection, is_composite_shell, reject_extra_arguments };
2927
3132
 
2928
- // scripts/josh/josh-in-process.ts
2929
- import { realpathSync } from "node:fs";
2930
- import path9 from "node:path";
2931
- import { pathToFileURL } from "node:url";
2932
- var TYPESCRIPT_EXTENSION = ".ts";
2933
- var JAVASCRIPT_EXTENSION = ".js";
2934
- var BUNDLED_COMMAND_DIRECTORY = path9.join("dist", "commands");
2935
- var FAILURE_EXIT_CODE = 1;
2936
- function is_typescript_dispatcher(dispatcher_url) {
2937
- return dispatcher_url.endsWith(TYPESCRIPT_EXTENSION);
2938
- }
2939
- function bundled_script_path(package_directory, script) {
2940
- const name = `${path9.basename(script, TYPESCRIPT_EXTENSION)}${JAVASCRIPT_EXTENSION}`;
2941
- return path9.join(package_directory, BUNDLED_COMMAND_DIRECTORY, name);
2942
- }
2943
- function in_process_target(entry, package_directory, dispatcher_url) {
2944
- if (entry.script === void 0 || entry.tsx_arguments !== void 0) return void 0;
2945
- if (is_typescript_dispatcher(dispatcher_url)) return path9.join(package_directory, entry.script);
2946
- return entry.is_bundled === true ? bundled_script_path(package_directory, entry.script) : void 0;
2947
- }
2948
- function read_exit_code() {
2949
- const code = process.exitCode;
2950
- return typeof code === "number" ? code : 0;
2951
- }
2952
- function resolve_real_path(script_path) {
2953
- try {
2954
- return realpathSync(script_path);
2955
- } catch {
2956
- return script_path;
2957
- }
2958
- }
2959
- async function run_in_process(script_path, script_arguments) {
2960
- const resolved_path = resolve_real_path(script_path);
2961
- process.argv = [process.execPath, resolved_path, ...script_arguments];
2962
- try {
2963
- await import(pathToFileURL(resolved_path).href);
2964
- } catch (error) {
2965
- console.error(error);
3133
+ // scripts/josh/josh-usage-automation.ts
3134
+ var STAGED = "--staged";
3135
+ var JSON_FLAG = "--json";
3136
+ var BRANCH_DIFF = "the branch diff";
3137
+ var STAGED_DIFF = "the staged diff instead";
3138
+ var PR_NUMBER = "42";
3139
+ var DRY_RUN = "--dry-run";
3140
+ var SKIP_PUBLISH_WAIT = "--skip-publish-wait";
3141
+ var AUTOMATION_USAGE = {
3142
+ "refactor:scan": { examples: [{ arguments: "" }] },
3143
+ "time:density": {
3144
+ examples: [{ arguments: "" }, { arguments: "--lanes 5", note: "the five most recent lanes" }],
3145
+ flags: [
3146
+ {
3147
+ names: ["--lanes <n>"],
3148
+ description: "average over a different count of recent qualifying lanes (default 10)."
3149
+ },
3150
+ {
3151
+ names: ["--path <dir>"],
3152
+ description: "read another project's lane transcripts, as `josh time` does. Run it from the main checkout \u2014 a lane reads only its own transcript."
3153
+ }
3154
+ ]
3155
+ },
3156
+ "e2e:retry-check": { examples: [{ arguments: "" }] },
3157
+ "sync:scope": {
3158
+ examples: [
3159
+ { arguments: "", note: BRANCH_DIFF },
3160
+ { arguments: STAGED, note: STAGED_DIFF },
3161
+ { arguments: JSON_FLAG, note: '{"scope":"managed","reason":"..."}' }
3162
+ ],
3163
+ flags: [
3164
+ { names: [STAGED], description: "inspect the staged diff instead of the branch diff." },
3165
+ { names: [JSON_FLAG], description: 'emit `{"scope","reason"}` as JSON.' }
3166
+ ]
3167
+ },
3168
+ "dogfood:commit": { examples: [{ arguments: "~/Development/kit-test-html-start" }] },
3169
+ "fresh:run": {
3170
+ examples: [
3171
+ { arguments: "--list", note: "kit's six patterns" },
3172
+ { arguments: "kit-test-html-init", note: "one pattern, the packed checkout" },
3173
+ {
3174
+ arguments: "kit-test-html-start",
3175
+ note: "a GitHub pattern, the latest published version"
3176
+ },
3177
+ { arguments: "--spec fresh/html-init.json", note: "a scenario another toolkit wrote" }
3178
+ ],
3179
+ flags: [
3180
+ { names: ["--list"], description: "print the pattern names and run nothing." },
3181
+ {
3182
+ names: ["--spec <file.json>"],
3183
+ description: "run the scenario in this file instead of a named pattern; the path is relative to the project root."
3184
+ },
3185
+ {
3186
+ names: ["--package-version <version>"],
3187
+ description: "the published version a `package_source: published` scenario installs. Default: the registry's latest version of the package the command is run from."
3188
+ },
3189
+ {
3190
+ names: ["--log-dir <dir>"],
3191
+ description: "where the log is written. Default: `.claude/tmp/fresh-machine`."
3192
+ },
3193
+ { names: ["--rebuild"], description: "build the machine image without the layer cache." }
3194
+ ]
3195
+ },
3196
+ "auth:setup": {
3197
+ examples: [
3198
+ { arguments: "", note: "sign in and save the token" },
3199
+ { arguments: "--check", note: "ok: \u2026 saved on <date>, or missing: \u2026 and exit 1" }
3200
+ ]
3201
+ },
3202
+ "sonar:hotspots": { examples: [{ arguments: PR_NUMBER }] },
3203
+ "sonar:new-code": { examples: [{ arguments: PR_NUMBER }] },
3204
+ "ui:routes": {
3205
+ examples: [
3206
+ { arguments: "", note: BRANCH_DIFF },
3207
+ { arguments: STAGED, note: STAGED_DIFF }
3208
+ ]
3209
+ },
3210
+ propagate: {
3211
+ examples: [
3212
+ { arguments: "" },
3213
+ { arguments: DRY_RUN, note: "report targets and steps, write nothing" },
3214
+ { arguments: SKIP_PUBLISH_WAIT, note: "release already known to be published" },
3215
+ { arguments: "--target app-kit", note: "carry the release into one consumer only" }
3216
+ ],
3217
+ flags: [
3218
+ {
3219
+ names: [DRY_RUN],
3220
+ description: "report targets and steps without writing; skips the publish wait, opens no issues."
3221
+ },
3222
+ {
3223
+ names: [SKIP_PUBLISH_WAIT],
3224
+ description: "skip the registry poll for an already-published release."
3225
+ },
3226
+ {
3227
+ names: ["--target <repo>"],
3228
+ description: "process only that consumer (`app-kit` or `joshuafolkken/app-kit`); the rest are reported `skipped` and left untouched. An unknown, ambiguous or non-dependent name fails before the publish wait, writing nothing."
3229
+ }
3230
+ ]
3231
+ },
3232
+ "repo:party": {
3233
+ examples: [
3234
+ { arguments: "joshuafolkken/kit", note: "\u2192 first-party (in the kit session)" },
3235
+ { arguments: "sveltejs/kit", note: "\u2192 third-party" },
3236
+ { arguments: "", note: "no argument: the session's own repository" }
3237
+ ]
3238
+ }
3239
+ };
3240
+
3241
+ // scripts/josh/josh-usage-backlog.ts
3242
+ var EXCLUDE = "--exclude <N>";
3243
+ var BUDGET = '--answer candidates --started "$started" --active "$active"';
3244
+ var REPO = "--repo <owner/repo>";
3245
+ var JUST_MERGED = "skip the issue just merged";
3246
+ var EXCLUDE_1630 = "--exclude 1630";
3247
+ var BACKLOG_USAGE = {
3248
+ epic: {
3249
+ examples: [
3250
+ { arguments: '"Epic: split the parser work" 101 102 103' },
3251
+ { arguments: '"Epic: staged rollout" 101 102 --ordered' },
3252
+ { arguments: '"Epic: ..." 101 102 --rationale-file rationale.md' },
3253
+ { arguments: "--promote 858 101 102 103", note: "turn an existing issue into an epic" },
3254
+ { arguments: "--add 893 894", note: "track #894, declaring no order for it" },
3255
+ { arguments: "--add 893 894 --before 891", note: "#894 must finish before #891" },
3256
+ { arguments: "--add 893 894 --after 890", note: "#894 starts once #890 is done" },
3257
+ { arguments: "--add 893 894 --decision-file why.md", note: "\u2026and record why, in one call" },
3258
+ {
3259
+ arguments: "--reconcile 2184",
3260
+ note: "rewrite the body to match the recorded blocked-by, and record any order the body declared"
3261
+ }
3262
+ ],
3263
+ flags: [
3264
+ {
3265
+ names: ["--ordered"],
3266
+ description: "argument order is the dependency order; writes the arrow chain, records the matching `blocked-by` relations, and adds a `### Declared order` entry under `## Decisions`."
3267
+ },
3268
+ {
3269
+ names: ["--rationale-file <path|->"],
3270
+ description: "split rationale prose (`-` reads stdin)."
3271
+ },
3272
+ {
3273
+ names: ["--origin <owner/repo#N>"],
3274
+ description: "backlink when the split originated in another repository."
3275
+ },
3276
+ {
3277
+ names: ["--before <M>"],
3278
+ description: "with `--add`: insert before `#M`, re-pointing what `#M` was waiting on so the chain is never broken."
3279
+ },
3280
+ {
3281
+ names: ["--after <M>"],
3282
+ description: "with `--add`: start after `#M`; branches when `#M` already has a successor, extends when it is a tail."
3283
+ },
3284
+ {
3285
+ names: ["--order-before <M>", "--order-after <M>"],
3286
+ description: "with `--add`: move the task-list row only; write no `blocked-by` relation."
3287
+ },
3288
+ {
3289
+ names: ["--decision-file <path|->"],
3290
+ description: "with `--add`: record why the child was placed."
3291
+ },
3292
+ {
3293
+ names: ["--remove <E> <M> <N> [<N2> \u2026]"],
3294
+ description: "delete a declared order from the body and the relations; each consecutive pair is one link."
3295
+ }
3296
+ ]
3297
+ },
3298
+ "epic:next": {
3299
+ examples: [
3300
+ { arguments: "858" },
3301
+ { arguments: "858 --repo joshuafolkken/kit", note: "just the next child for one repository" },
3302
+ { arguments: "858 --repo joshuafolkken/kit --lanes", note: "one child per free lane there" }
3303
+ ],
3304
+ flags: [
3305
+ {
3306
+ names: [REPO],
3307
+ description: "answer for one repository; stdout carries one token (an issue number, or `wait`/`stop`/`complete`), everything else on stderr."
3308
+ },
3309
+ {
3310
+ names: ["--lanes"],
3311
+ description: "print one issue number per free lane (requires `--repo`); `JOSH_LANE_LIMIT` sets the ceiling, default 4. Prints `triage` instead while any candidate carries neither `run:solo` nor `run:lane` (the triage gate in `josh backlog:next`)."
3312
+ }
3313
+ ]
3314
+ },
3315
+ "epic:bundle": { examples: [{ arguments: "874" }] },
3316
+ "epic:audit": { examples: [{ arguments: "858" }] },
3317
+ "epic:check": { examples: [{ arguments: "700" }] },
3318
+ "auto-ok:next": {
3319
+ examples: [{ arguments: "" }, { arguments: "--exclude 906", note: JUST_MERGED }],
3320
+ flags: [
3321
+ {
3322
+ names: [EXCLUDE],
3323
+ description: "drop issues from the answer; comma-separated, repeatable."
3324
+ }
3325
+ ]
3326
+ },
3327
+ "auto-ok:judge": {
3328
+ examples: [
3329
+ { arguments: "", note: "the issues that still owe a judgement" },
3330
+ { arguments: "--apply 912 --lane", note: "opt it in, with its run label" },
3331
+ { arguments: '--decline 913 --reason "<why>"', note: "record why it stays out" }
3332
+ ],
3333
+ flags: [
3334
+ {
3335
+ names: ["--apply <N> --lane|--solo"],
3336
+ description: "checks the exclusions again against the labels as they stand, applies the run label and then `auto-ok`, and notes it on the progress board."
3337
+ },
3338
+ { names: ["--decline <N> --reason <text>"], description: "applies nothing." }
3339
+ ]
3340
+ },
3341
+ "backlog:next": {
3342
+ examples: [{ arguments: "" }, { arguments: EXCLUDE_1630, note: JUST_MERGED }],
3343
+ flags: [
3344
+ {
3345
+ names: [EXCLUDE],
3346
+ description: "drop issues from every bucket; comma-separated, repeatable."
3347
+ }
3348
+ ]
3349
+ },
3350
+ "backlog:plan": {
3351
+ examples: [
3352
+ { arguments: "" },
3353
+ { arguments: EXCLUDE_1630, note: "after #1630 merged" },
3354
+ { arguments: "--waves", note: "the run order, wave by wave" }
3355
+ ],
3356
+ flags: [
3357
+ { names: [EXCLUDE], description: "same exclusion as `backlog:next`." },
3358
+ {
3359
+ names: ["--waves"],
3360
+ description: "print the order the run takes, wave by wave, assuming each wave merges before the next. Read-only."
3361
+ }
3362
+ ]
3363
+ },
3364
+ "backlog:stalled": { examples: [{ arguments: "" }] },
3365
+ "backlog:budget": {
3366
+ examples: [
3367
+ { arguments: BUDGET },
3368
+ { arguments: '--answer exhausted --started "$started" --active "$active" --idle 60' },
3369
+ { arguments: `${BUDGET} --merged 3 --running 2 --max 5` }
3370
+ ],
3371
+ flags: [
3372
+ {
3373
+ names: ["--answer <candidates|exhausted|blocked|parked|unreadable|untriaged>"],
3374
+ description: "`backlog:next`'s answer, mapped."
3375
+ },
3376
+ {
3377
+ names: ["--started", "--active"],
3378
+ description: "when the invocation began / when it last had work (required unless the watch is off)."
3379
+ },
3380
+ {
3381
+ names: ["--idle <minutes>"],
3382
+ description: "once nothing can proceed, keep polling this long, counted from `--active` (default 480, 8 hours; `--idle 0` turns the watch off)."
3383
+ },
3384
+ {
3385
+ names: ["--max <count>"],
3386
+ description: "issues one invocation may take (default unlimited); `--merged` and `--running` count against it."
3387
+ },
3388
+ {
3389
+ names: ["--json"],
3390
+ description: 'collapse verdict and reason into `{"budget": "<verdict>", "reason": "\u2026"}`.'
3391
+ }
3392
+ ]
3393
+ },
3394
+ "backlog:offer": {
3395
+ examples: [{ arguments: '--started "$started" --active "$active" --running 2 --retries 1' }]
3396
+ },
3397
+ "backlog:drive": {
3398
+ examples: [{ arguments: '--owner "$PPID" [--max <n>] [--idle <minutes>] [--only]' }]
3399
+ }
3400
+ };
3401
+
3402
+ // scripts/josh/josh-usage-developer.ts
3403
+ var UNSEEDED = {};
3404
+ var NARROWED = { arguments: "scripts/thing.ts", note: "narrow by the given files instead" };
3405
+ var DEVELOPER_USAGE = {
3406
+ gate: {
3407
+ examples: [
3408
+ { arguments: "" },
3409
+ { arguments: "--verbose", note: "every check's output, passing ones included" },
3410
+ { arguments: "--force", note: "re-run even on a tree already recorded green" },
3411
+ { arguments: "--no-unit", note: "the static checks only (CI only)" }
3412
+ ]
3413
+ },
3414
+ lint: { examples: [{ arguments: "" }] },
3415
+ "lint:related": { examples: [{ arguments: "" }, NARROWED] },
3416
+ lines: {
3417
+ examples: [{ arguments: "scripts/hooks/format-edited-file.ts scripts/josh/josh-logic.ts" }]
3418
+ },
3419
+ bytes: {
3420
+ examples: [
3421
+ { arguments: "docs/josh-commands.md", note: "one document" },
3422
+ { arguments: "", note: "scan: every budgeted document near its ceiling" }
3423
+ ]
3424
+ },
3425
+ metrics: {
3426
+ examples: [
3427
+ { arguments: "", note: "print the totals and check them against the merge-base's" },
3428
+ { arguments: '--accept --reason "<why>"', note: "record this branch's growth and why" }
3429
+ ]
3430
+ },
3431
+ format: { examples: [{ arguments: "" }] },
3432
+ "cspell:dot": { examples: [{ arguments: "", note: "includes dotfiles" }] },
3433
+ behavior: { examples: [{ arguments: "" }] },
3434
+ "exports:unused": { examples: [{ arguments: "" }] },
3435
+ "test:unit": { examples: [{ arguments: "" }] },
3436
+ "test:related": {
3437
+ examples: [
3438
+ { arguments: "" },
3439
+ NARROWED,
3440
+ { arguments: "--silent", note: "flags are forwarded to vitest" }
3441
+ ]
3442
+ },
3443
+ "test:declared": {
3444
+ examples: [
3445
+ { arguments: "" },
3446
+ { arguments: "--match", note: "check Step 0 declarations on stdin" },
3447
+ { arguments: "--help", note: "print usage, including the --match stdin form" }
3448
+ ]
3449
+ },
3450
+ "test:e2e": { examples: [{ arguments: "" }] },
3451
+ test: { examples: [{ arguments: "" }] },
3452
+ check: { examples: [{ arguments: "" }] },
3453
+ port: {
3454
+ examples: [
3455
+ {
3456
+ arguments: "dev",
3457
+ note: `${String(ports.resolve_development_port(UNSEEDED))} with no seed set`
3458
+ },
3459
+ {
3460
+ arguments: "preview",
3461
+ note: `${String(ports.resolve_preview_port(UNSEEDED))} with no seed set`
3462
+ }
3463
+ ]
3464
+ },
3465
+ profile: { examples: [{ arguments: "" }] },
3466
+ init: {
3467
+ examples: [
3468
+ {
3469
+ arguments: "",
3470
+ note: "create/merge config files, install and format (--no-install skips both, --dry-run only prints the plan)"
3471
+ }
3472
+ ]
3473
+ },
3474
+ "registry:migrate": { examples: [{ arguments: "" }] },
3475
+ sync: { examples: [{ arguments: "", note: "overwrite managed files" }] },
3476
+ uninstall: {
3477
+ examples: [
3478
+ { arguments: "", note: "show the plan, then ask" },
3479
+ { arguments: "--yes", note: "remove without asking" }
3480
+ ]
3481
+ },
3482
+ adopt: {
3483
+ examples: [
3484
+ { arguments: "" },
3485
+ { arguments: "--dry-run", note: "report the steps without touching anything" }
3486
+ ],
3487
+ flags: [{ names: ["--dry-run"], description: "report the plan without writing." }]
3488
+ },
3489
+ git: {
3490
+ examples: [
3491
+ { arguments: "", note: "interactive" },
3492
+ { arguments: "-y", note: "unattended (no TTY required)" },
3493
+ { arguments: '-y "title"', note: "set commit message prefix" },
3494
+ { arguments: '-y "<title> #<N>"', note: "follow-up commit on the same branch" },
3495
+ {
3496
+ arguments: "-y --skip-commit --skip-push",
3497
+ note: "open the PR without committing/pushing"
3498
+ }
3499
+ ],
3500
+ flags: [
3501
+ {
3502
+ names: ["-y", "--yes"],
3503
+ description: "run non-interactively; works without a TTY (agent or CI shell)."
3504
+ },
3505
+ {
3506
+ names: ["--skip-commit", "--skip-push"],
3507
+ description: "recover/open a PR without a new commit or push."
3508
+ }
3509
+ ]
3510
+ },
3511
+ pr: { examples: [{ arguments: "", note: "open the PR for the current <N>-<slug> branch" }] },
3512
+ "review:findings": { examples: [{ arguments: "" }] },
3513
+ "main:sync": { examples: [{ arguments: "" }] },
3514
+ "main:merge": { examples: [{ arguments: "" }] },
3515
+ version: {
3516
+ examples: [
3517
+ { arguments: "" },
3518
+ {
3519
+ arguments: "--upgrade",
3520
+ note: "upgrade global + project to latest, then re-run fix-gh-packages"
3521
+ }
3522
+ ],
3523
+ flags: [
3524
+ {
3525
+ names: ["--upgrade"],
3526
+ description: "upgrade both the global install (`pnpm add -g`) and the project devDependency (`pnpm add -D` + `fix-gh-packages`); already-current or not-installed targets are skipped."
3527
+ }
3528
+ ]
3529
+ },
3530
+ ranges: { examples: [{ arguments: "" }] },
3531
+ doctor: {
3532
+ examples: [
3533
+ { arguments: "", note: "diagnose only" },
3534
+ { arguments: "--fix", note: "reclaim the global josh by removing a stale kit shim" },
3535
+ { arguments: "--ports", note: "also print the per-repository port-seed table" }
3536
+ ],
3537
+ flags: [
3538
+ {
3539
+ names: ["--fix"],
3540
+ description: "remove a shadowing binary **only if it is a kit shim** (its body references `@joshuafolkken/kit` or the removed `install-bin` script); any other binary is left and reported for manual review. Also the one-shot recovery for an old project that re-created the global shim."
3541
+ },
3542
+ { names: ["--ports"], description: "additionally print the port-seed table (see below)." }
3543
+ ]
3544
+ },
3545
+ audit: { examples: [{ arguments: "" }] },
3546
+ overrides: {
3547
+ examples: [
3548
+ { arguments: "", note: "verify overrides unchanged" },
3549
+ { arguments: "--save", note: "snapshot current merged overrides" }
3550
+ ],
3551
+ flags: [
3552
+ {
3553
+ names: ["--save"],
3554
+ description: "write the current merged overrides to `.overrides-snapshot.json` (gitignored); later runs compare against it and exit non-zero on any add, removal, or change."
3555
+ }
3556
+ ]
3557
+ },
3558
+ backlogrun: {
3559
+ examples: [
3560
+ { arguments: "", note: "start in Claude, then show the board" },
3561
+ { arguments: "3437 --only", note: "the arguments go to backlogrun as typed" },
3562
+ { arguments: "--agent codex", note: "start in Codex" },
3563
+ { arguments: "--stop", note: "stop the run in this repository" }
3564
+ ]
3565
+ },
3566
+ "lane:limit": {
3567
+ examples: [
3568
+ { arguments: "8", note: "raise or lower the live run's limit to 8" },
3569
+ { arguments: "", note: "print the limit, the lanes in use and the free lanes" },
3570
+ { arguments: "--reset", note: "clear the override; JOSH_LANE_LIMIT applies again" }
3571
+ ]
3572
+ },
3573
+ "lane:withdraw": {
3574
+ examples: [{ arguments: '3769 --reason "folded into a broader issue"' }]
3575
+ },
3576
+ "test:red": { examples: [{ arguments: "", note: "red, green, no-test or test-only" }] },
3577
+ start: {
3578
+ examples: [
3579
+ { arguments: "", note: "asks for the profile and the repository" },
3580
+ {
3581
+ arguments: "--yes --github --profile basic",
3582
+ note: "unattended, including the GitHub repository"
3583
+ }
3584
+ ],
3585
+ flags: [
3586
+ { names: ["--profile basic|full"], description: "the profile, without asking." },
3587
+ { names: ["--yes"], description: "take the defaults \u2014 not consent to write to GitHub." },
3588
+ { names: ["--github"], description: "create the GitHub repository and push `main`." },
3589
+ { names: ["--public"], description: "create the repository public instead of private." },
3590
+ {
3591
+ names: ['--init-command "<command>"'],
3592
+ description: "run this setup command in place of `josh init`."
3593
+ },
3594
+ {
3595
+ names: ['--push-preflight-command "<command>"'],
3596
+ description: "a check run before the first step when the plan pushes; a failure stops the run before it writes anything."
3597
+ },
3598
+ { names: ["--dry-run"], description: "print the plan and change nothing." }
3599
+ ]
3600
+ },
3601
+ latest: { examples: [{ arguments: "", note: "full update (update + audit + pnpm)" }] },
3602
+ "latest:corepack": { examples: [{ arguments: "", note: "update pnpm only" }] },
3603
+ "latest:update": { examples: [{ arguments: "", note: "update dependencies only" }] },
3604
+ "lane:restart": {
3605
+ examples: [{ arguments: "3769", note: "prints the fresh child's pid" }]
3606
+ },
3607
+ "rule:value": {
3608
+ examples: [
3609
+ { arguments: "" },
3610
+ {
3611
+ arguments: "--refresh",
3612
+ note: "write the reading to the loop-head cache instead of stdout"
3613
+ }
3614
+ ]
3615
+ }
3616
+ };
3617
+
3618
+ // scripts/josh/josh-usage-hooks.ts
3619
+ var PRE_TOOL_USE = "run by the PreToolUse hook, the pending tool call as JSON on stdin";
3620
+ var IN_PRETOOL_GUARD = "run inside pretool:guard, not wired on its own";
3621
+ var HOOK_USAGE = {
3622
+ "format:edited": {
3623
+ examples: [
3624
+ { arguments: "", note: "run by the PostToolUse hook, the tool call as JSON on stdin" }
3625
+ ]
3626
+ },
3627
+ "batch:guard": { examples: [{ arguments: "", note: IN_PRETOOL_GUARD }] },
3628
+ "investigation:guard": { examples: [{ arguments: "", note: PRE_TOOL_USE }] },
3629
+ "duplicate-read:guard": { examples: [{ arguments: "", note: IN_PRETOOL_GUARD }] },
3630
+ "rule:guard": { examples: [{ arguments: "", note: PRE_TOOL_USE }] },
3631
+ "rule:list": { examples: [{ arguments: "", note: "print every trigger-delivered rule" }] },
3632
+ "pretool:guard": { examples: [{ arguments: "", note: PRE_TOOL_USE }] },
3633
+ "codex:hook-adapter": {
3634
+ examples: [
3635
+ { arguments: "pretool", note: "a Codex PreToolUse payload on stdin" },
3636
+ { arguments: "posttool", note: "a Codex PostToolUse payload on stdin" }
3637
+ ]
3638
+ },
3639
+ "stop:guard": {
3640
+ examples: [{ arguments: "", note: "run by the Stop hook, the stop payload as JSON on stdin" }]
3641
+ },
3642
+ "session:lang": {
3643
+ examples: [{ arguments: "", note: "run by the UserPromptSubmit hook; `ja` prints nothing" }]
3644
+ }
3645
+ };
3646
+
3647
+ // scripts/josh/josh-usage-issue.ts
3648
+ var REPO2 = "--repo <owner/repo>";
3649
+ var BODY_FILE = "--body-file <path>";
3650
+ var CONCURRENT = "several numbers, read concurrently";
3651
+ var REPO_FLAG = {
3652
+ names: [REPO2],
3653
+ description: "read a child in another repository; applies to every number."
3654
+ };
3655
+ var JSON_FLAG2 = { names: ["--json"], description: "print the verdict and reason as JSON." };
3656
+ var ISSUE_USAGE = {
3657
+ "issue:read": {
3658
+ examples: [{ arguments: "1715" }, { arguments: "1715 1567 1605", note: CONCURRENT }]
3659
+ },
3660
+ "issue:ack": { examples: [{ arguments: "3773" }] },
3661
+ "issue:state": {
3662
+ examples: [
3663
+ { arguments: "42", note: "single-number shape" },
3664
+ { arguments: "42 43 44", note: "each block headed by `issue:`" },
3665
+ { arguments: "42 43 --repo joshuafolkken/app-kit", note: "a child in another repo" }
3666
+ ],
3667
+ flags: [REPO_FLAG]
3668
+ },
3669
+ "issue:release": {
3670
+ examples: [
3671
+ { arguments: "3360" },
3672
+ { arguments: "3716 --wait", note: "blocked by the release issue instead" }
3673
+ ],
3674
+ flags: [
3675
+ {
3676
+ names: ["--wait"],
3677
+ description: "the issue is blocked by the release issue instead, so `backlog:next` offers it again once the release ships (`.claude/skills/workflow-commands/prerequisite.md`)."
3678
+ }
3679
+ ]
3680
+ },
3681
+ "issue:scout": {
3682
+ examples: [
3683
+ { arguments: '"Stop the gate re-running after every edit"' },
3684
+ { arguments: '"<title>" --body "parent: #1246"' },
3685
+ { arguments: '"<title>" --body-file draft.md' }
3686
+ ],
3687
+ flags: [
3688
+ {
3689
+ names: ['--body "<text>"'],
3690
+ description: "supply references (`#N`) in a `parent: #N` line or an acceptance-criteria section so the epic half has a number to work from; without one there (a background mention does not count) it prints `Epic: not asked`."
3691
+ },
3692
+ {
3693
+ names: [BODY_FILE],
3694
+ description: 'pass the whole draft, including its purpose, requirements and acceptance criteria. A file that cannot be read ends the command rather than being treated as "no candidates".'
3695
+ }
3696
+ ]
3697
+ },
3698
+ "issue:file": {
3699
+ examples: [
3700
+ { arguments: '"<title>" --body-file body.md --depth 1' },
3701
+ {
3702
+ arguments: '"<title>" --body-file body.md --depth 0 --route tier-a --repo joshuafolkken/kit'
3703
+ },
3704
+ { arguments: '"<title>" --body-file body.md --depth 1 --distinct 2801,2795' }
3705
+ ],
3706
+ flags: [
3707
+ { names: [BODY_FILE], description: "the body. Required." },
3708
+ {
3709
+ names: ["--depth <0|1|2>"],
3710
+ description: 'the depth label. Required. The criteria are `.claude/skills/workflow-commands/observation-filing.md` \u2192 "The depth test".'
3711
+ },
3712
+ {
3713
+ names: ["--route <tier-a|split|interrupt|review-cap>"],
3714
+ description: "the `route:` label naming the filing route. Omit it for a filing with no route."
3715
+ },
3716
+ {
3717
+ names: ["--label <name>"],
3718
+ description: "an extra label (for example `epic`). Repeatable."
3719
+ },
3720
+ {
3721
+ names: [REPO2],
3722
+ description: "the target repository. Defaults to this repository."
3723
+ },
3724
+ {
3725
+ names: ["--distinct <N,\u2026>"],
3726
+ description: "duplicate candidates you read and judged distinct."
3727
+ },
3728
+ {
3729
+ names: ["--over-cap"],
3730
+ description: "the run is blocked by this filing; the cap lets it through."
3731
+ },
3732
+ {
3733
+ names: ["--no-auto-ok"],
3734
+ description: "needs a person's judgement (Tier B / C); no `auto-ok`."
3735
+ },
3736
+ {
3737
+ names: ["--release"],
3738
+ description: "a consumer can use the change only once it is published. The filed issue becomes a blocker of the target's release issue (step 10)."
3739
+ }
3740
+ ]
3741
+ },
3742
+ "issue:fold-existing": { examples: [{ arguments: "assessment.json --json" }] },
3743
+ "issue:fold": {
3744
+ examples: [
3745
+ {
3746
+ arguments: '"First finding" "Second finding"',
3747
+ note: "fold | separate | no-fold-needed | undetermined"
3748
+ },
3749
+ { arguments: '"a" "b" --not-separable', note: "the pair is really one deliverable \u2192 fold" },
3750
+ { arguments: '"a" "b" --json', note: "the verdict and reason, machine-readable" }
3751
+ ],
3752
+ flags: [
3753
+ {
3754
+ names: ["--not-separable"],
3755
+ description: "declare the judgement half: the findings are one deliverable, so they fold whatever their size."
3756
+ },
3757
+ JSON_FLAG2
3758
+ ]
3759
+ },
3760
+ "issue:cite": {
3761
+ examples: [
3762
+ { arguments: "2220" },
3763
+ { arguments: "2220 1758 1252", note: CONCURRENT },
3764
+ { arguments: "45 --repo joshuafolkken/app-kit", note: "bare numbers in another repository" },
3765
+ { arguments: "joshuafolkken/app-kit#45 2220", note: "per-token owner/repo#N notation" }
3766
+ ],
3767
+ flags: [
3768
+ {
3769
+ names: [REPO2],
3770
+ description: "the repository for every bare number; a token written `owner/repo#N` overrides it for itself."
3771
+ }
3772
+ ]
3773
+ },
3774
+ "issue:comment": {
3775
+ examples: [
3776
+ {
3777
+ arguments: "2304 --body-file .claude/tmp/park.md",
3778
+ note: "the body from a file \u2014 no shell evaluates it"
3779
+ },
3780
+ {
3781
+ arguments: '2304 --body "a short note"',
3782
+ note: "inline, for a body with no backticks or $"
3783
+ }
3784
+ ],
3785
+ flags: [
3786
+ {
3787
+ names: ["--body <text>"],
3788
+ description: "the comment body, inline. Trimmed, with `\\n` expanded to a newline."
3789
+ },
3790
+ {
3791
+ names: [BODY_FILE],
3792
+ description: "read the body from a file, or from stdin with `-`. The safe form the rule steers every caller toward."
3793
+ }
3794
+ ]
3795
+ },
3796
+ "pkg:scout": {
3797
+ examples: [{ arguments: '"date formatting" --size 5' }],
3798
+ flags: [
3799
+ { names: ["--size <n>"], description: "how many candidates to fetch and rank (default 10)." }
3800
+ ]
3801
+ },
3802
+ "issue:lint": { examples: [{ arguments: ".claude/tmp/issue-body.md" }] },
3803
+ "defect:rate": {
3804
+ examples: [{ arguments: "", note: "the last 14 days" }, { arguments: "--days 30" }],
3805
+ flags: [
3806
+ {
3807
+ names: ["--days <n>"],
3808
+ description: "the window in whole days (default 14, at most 3650). Anything else prints the usage and exits 1."
3809
+ }
3810
+ ]
3811
+ },
3812
+ "issue:backlinks": { examples: [{ arguments: "2123" }] },
3813
+ "report:lint": { examples: [{ arguments: "< summary.md" }] },
3814
+ "stash:pop": {
3815
+ examples: [
3816
+ { arguments: '"backlogrun: parked #2028"' },
3817
+ {
3818
+ arguments: '"backlogrun: josh latest before lanes" --dir "$dir"',
3819
+ note: "into a lane's work tree"
3820
+ }
3821
+ ],
3822
+ flags: [
3823
+ {
3824
+ names: ["--dir <path>"],
3825
+ description: "applies the pop in that work tree (`git -C <path>`); without it the pop lands in the current checkout. The stack is shared, so it reads the same either way."
3826
+ }
3827
+ ]
3828
+ }
3829
+ };
3830
+
3831
+ // scripts/josh/josh-usage-review.ts
3832
+ var MACHINE_READABLE = "the verdict and the reason, machine-readable";
3833
+ var LEVEL_ONLY = "--level-only";
3834
+ var JSON_FLAG3 = "--json";
3835
+ var CHECK = "--check";
3836
+ var ROUND_1_CLOSED = "--round-1-closed";
3837
+ var REVIEW_USAGE = {
3838
+ "review:brief": {
3839
+ examples: [
3840
+ { arguments: "", note: "round 1" },
3841
+ { arguments: "--round 2", note: "verification pass, scoped to the fix delta" },
3842
+ { arguments: LEVEL_ONLY, note: "the level alone (pre-commit review)" },
3843
+ { arguments: "--level-only --json", note: "level and reason, machine-readable" }
3844
+ ],
3845
+ flags: [
3846
+ {
3847
+ names: ["--round 2"],
3848
+ description: "target is round 1's fixes and nothing else (widened back to the whole change when the record's change base no longer matches)."
3849
+ },
3850
+ {
3851
+ names: [LEVEL_ONLY],
3852
+ description: "print the level alone (level to stdout, reason to stderr); bypasses the scoped-green refusal."
3853
+ },
3854
+ {
3855
+ names: ["--staged", "--json"],
3856
+ description: "the staged diff; machine-readable level and reason."
3857
+ }
3858
+ ]
3859
+ },
3860
+ "review:round2": {
3861
+ examples: [
3862
+ { arguments: "", note: "\u2192 required" },
3863
+ { arguments: ROUND_1_CLOSED, note: "\u2192 required | skip" },
3864
+ { arguments: JSON_FLAG3, note: MACHINE_READABLE }
3865
+ ],
3866
+ flags: [
3867
+ {
3868
+ names: [ROUND_1_CLOSED],
3869
+ description: "the caller states every round-1 High/Medium finding closed and none was filed or deferred; its absence answers `required`."
3870
+ },
3871
+ { names: [JSON_FLAG3], description: "machine-readable verdict and reason." }
3872
+ ]
3873
+ },
3874
+ disposition: { examples: [{ arguments: "<path...>", note: "\u2192 runtime | non-runtime" }] },
3875
+ "review:attest": {
3876
+ examples: [
3877
+ { arguments: "<nonce>", note: "run by the review, from the checkout it read" },
3878
+ { arguments: CHECK, note: "run by the run, before it acts on the review" }
3879
+ ],
3880
+ flags: [
3881
+ { names: ["<nonce>"], description: "attest the checkout the review read." },
3882
+ {
3883
+ names: [CHECK],
3884
+ description: "verify before acting; `josh followup` asks again before merging."
3885
+ }
3886
+ ]
3887
+ },
3888
+ delegate: {
3889
+ examples: [
3890
+ { arguments: "gate-fix", note: "\u2192 delegate" },
3891
+ { arguments: "review", note: "\u2192 keep" },
3892
+ { arguments: "--list", note: "the enumeration, and what was rejected and why" }
3893
+ ]
3894
+ },
3895
+ fanout: {
3896
+ examples: [
3897
+ { arguments: "scripts/a.ts,scripts/a.test.ts scripts/b.ts", note: "\u2192 parallel" },
3898
+ {
3899
+ arguments: "scripts/a.ts,shared.ts scripts/b.ts,shared.ts",
3900
+ note: "\u2192 serial (units share shared.ts)"
3901
+ },
3902
+ { arguments: "scripts/a.ts", note: "\u2192 serial (fewer than two units)" }
3903
+ ]
3904
+ },
3905
+ "split:assess": {
3906
+ examples: [
3907
+ { arguments: "", note: "\u2192 single" },
3908
+ { arguments: JSON_FLAG3, note: MACHINE_READABLE }
3909
+ ]
3910
+ },
3911
+ "oracle:list": { examples: [{ arguments: "" }] },
3912
+ "clone:scan": { examples: [{ arguments: "" }] },
3913
+ cases: {
3914
+ examples: [
3915
+ { arguments: "scripts/a.ts scripts/b.ts", note: "the boundaries these paths cross" }
3916
+ ]
3917
+ }
3918
+ };
3919
+
3920
+ // scripts/josh/josh-usage-run.ts
3921
+ var ISSUE = { arguments: "<N>" };
3922
+ var OUTPUT = "--output <path>";
3923
+ var OWNER = "--owner <pid>";
3924
+ var REPO_FLAG2 = { names: ["--repo <owner/name>"], description: "a child in another repository." };
3925
+ var RUN_USAGE = {
3926
+ "run:hold": {
3927
+ examples: [
3928
+ { arguments: "1091", note: "claim for issue 1091" },
3929
+ { arguments: "1091 --fullrun", note: "claim as `fullrun #1091` (what `run:entry` runs)" }
3930
+ ],
3931
+ flags: [
3932
+ { names: ["--fullrun"], description: "marks the record as `fullrun #N`'s." },
3933
+ {
3934
+ names: ["--halfrun-stop"],
3935
+ description: "marks a `halfrun` stop for `run:entry` to adopt."
3936
+ },
3937
+ {
3938
+ names: ["--prrun-stop"],
3939
+ description: "marks a `prrun` stop with the commit its pull request is on."
3940
+ }
3941
+ ]
3942
+ },
3943
+ "run:release": {
3944
+ examples: [
3945
+ { arguments: "1091", note: "release this run's own record" },
3946
+ { arguments: "--force", note: "clear a record left by a run that has ended" }
3947
+ ],
3948
+ flags: [
3949
+ {
3950
+ names: ["--force"],
3951
+ description: "removes a record this run did not write, clearing another run's stale claim."
3952
+ }
3953
+ ]
3954
+ },
3955
+ "run:tidy": { examples: [{ arguments: "" }] },
3956
+ "run:carry": {
3957
+ examples: [
3958
+ { arguments: '--begin "backlogrun --max 5" --owner "$PPID"' },
3959
+ { arguments: "--json", note: "read the record back in a resumed session" },
3960
+ { arguments: '--cut --owner "$PPID"', note: "hand the record off before a cut" },
3961
+ {
3962
+ arguments: '--retrospective --summary "0 filed; dropped #2240 already merged" --owner "$PPID"',
3963
+ note: "close the end-of-run retrospective"
3964
+ },
3965
+ {
3966
+ arguments: '--resume "backlogrun --max 5" --owner "$PPID"',
3967
+ note: "adopt a record no cut handed off"
3968
+ },
3969
+ {
3970
+ arguments: '--end --stopped "epic #2126: everything is blocked behind parked #2118"',
3971
+ note: "end + push"
3972
+ }
3973
+ ],
3974
+ flags: [
3975
+ {
3976
+ names: [OWNER],
3977
+ description: "the long-lived process spending the budget (`$PPID` under a loop); required by counts and `--begin` / `--resume`. A live PID stays `busy` if probes fail."
3978
+ },
3979
+ { names: ["--done <issue>"], description: "shrinks a named-issue run's `remaining` list." },
3980
+ {
3981
+ names: ["--merged <issue>"],
3982
+ description: "names the merged issue and counts it once against `merged_issues`, so a merge `run:merge` already recorded is not counted twice."
3983
+ },
3984
+ { names: ["--filed", "--cut"], description: "increments, never totals." },
3985
+ {
3986
+ names: ["--retrospective"],
3987
+ description: "marks the end-of-run retrospective run, once per invocation, and requires `--summary <text>` \u2014 the same close writes that result as one `retrospective` event on the run's event stream (best-effort), so a run that filed zero improvements reads apart from one whose retrospective never ran. Either flag without the other is refused."
3988
+ },
3989
+ {
3990
+ names: ["--stopped <reason>"],
3991
+ description: "rides on `--end`: the run stopped rather than finished, so one \u23F8\uFE0F confirmation is pushed with the reason."
3992
+ },
3993
+ {
3994
+ names: ["--end"],
3995
+ description: "over a live record flushes pending ledger lines once (a failed flush goes to stderr and the record is still cleared); first collects each lane the run launched whose issue merged, exactly as `run:merge` would; keeps the run it removes as the last ended run \u2014 its invocation, start and end, one record overwritten by the next `--end` \u2014 so `run:board` can still draw a finished run."
3996
+ }
3997
+ ]
3998
+ },
3999
+ "run:add": {
4000
+ examples: [
4001
+ { arguments: "101 102", note: "ahead of the rest of the queue (`#N` works too)" },
4002
+ { arguments: "101 --no-priority", note: "at the end of the queue" }
4003
+ ]
4004
+ },
4005
+ "run:wake": {
4006
+ examples: [
4007
+ { arguments: "--start", note: "launch the detached supervisor" },
4008
+ { arguments: "--list", note: "supervisor state, cut/wake counts, latest progress line" },
4009
+ { arguments: "--stop", note: "stop the supervisor alone" },
4010
+ { arguments: "--loop --interval 30", note: "run the loop body in the foreground" }
4011
+ ]
4012
+ },
4013
+ "run:cut": {
4014
+ examples: [
4015
+ { arguments: "1839", note: "take the cut and hand it to a fresh process" },
4016
+ {
4017
+ arguments: "--impl 1839 --handoff .claude/tmp/handoff-1839.json",
4018
+ note: "cut mid-implementation, carrying the instruction"
4019
+ },
4020
+ { arguments: "--resume 1839", note: "a fresh process's entry check" },
4021
+ { arguments: "--end", note: "clear the record" }
4022
+ ]
4023
+ },
4024
+ "run:liveness": {
4025
+ examples: [
4026
+ { arguments: "1169 --output <path>" },
4027
+ { arguments: "1169 --output <path> --window 45 --gap 2 --repo joshuafolkken/app-kit" }
4028
+ ],
4029
+ flags: [
4030
+ {
4031
+ names: [OUTPUT],
4032
+ description: "absolute, under the home or temp directory; a symlink is followed and size compared as well as mtime."
4033
+ },
4034
+ {
4035
+ names: ["--process alive | none"],
4036
+ description: "overrides the process trace, which the command otherwise reads itself: the child's own command line (`fullrun #<N>`) or its detached ship supervisor."
4037
+ },
4038
+ {
4039
+ names: ["--window <min>"],
4040
+ description: "silent window the file must be frozen for (default 30)."
4041
+ },
4042
+ { names: ["--gap <sec>"], description: "spacing between samples (default 5)." },
4043
+ REPO_FLAG2
4044
+ ]
4045
+ },
4046
+ "run:ending": {
4047
+ examples: [
4048
+ { arguments: "2118 --output <path>" },
4049
+ { arguments: "2118 --output <path> --repo joshuafolkken/app-kit" }
4050
+ ],
4051
+ flags: [
4052
+ {
4053
+ names: [OUTPUT],
4054
+ description: "the child's transcript, absolute and under the home or temp directory (validated the same way `run:liveness --output` is)."
4055
+ },
4056
+ REPO_FLAG2
4057
+ ]
4058
+ },
4059
+ "run:prep": { examples: [ISSUE] },
4060
+ "run:entry": {
4061
+ examples: [
4062
+ { arguments: "<N>", note: "a `fullrun`" },
4063
+ { arguments: "<N> --to halfrun", note: "a run that goes as far as `halfrun`" }
4064
+ ]
4065
+ },
4066
+ "run:status": {
4067
+ examples: [ISSUE, { arguments: "<N> --repo <owner/repo>", note: "a cross-repo child" }]
4068
+ },
4069
+ "run:next": { examples: [ISSUE] },
4070
+ "run:step": {
4071
+ examples: [
4072
+ { arguments: "<N>", note: "the run's next action for issue <N>" },
4073
+ { arguments: "", note: "the `backlogrun` parent's next action" }
4074
+ ]
4075
+ },
4076
+ "run:merge": {
4077
+ examples: [
4078
+ {
4079
+ arguments: '<N> --epic <E> --repo <owner/repo> --owner "$PPID" --output <path>',
4080
+ note: "prints the next child number, or a verdict"
4081
+ },
4082
+ { arguments: '<N> --owner "$PPID"', note: "backlog offer (no epic)" }
4083
+ ],
4084
+ flags: [
4085
+ {
4086
+ names: ["--epic <E>", "--repo <owner/repo>"],
4087
+ description: "offer the epic's next children; omit both for the opted-in backlog."
4088
+ },
4089
+ {
4090
+ names: [OWNER],
4091
+ description: "the parent's process, so the carry count respects the ownership guard."
4092
+ },
4093
+ {
4094
+ names: [OUTPUT],
4095
+ description: "the transcript the outage split reads; absent, it is off."
4096
+ }
4097
+ ]
4098
+ },
4099
+ "run:review": {
4100
+ examples: [
4101
+ {
4102
+ arguments: "",
4103
+ note: "detach the gate, print the brief; then launch the /code-review subagent"
4104
+ },
4105
+ { arguments: "--join", note: "after the review returns: join the gate, check its verdict" }
4106
+ ]
4107
+ },
4108
+ "run:tail": {
4109
+ examples: [
4110
+ { arguments: "<N>", note: "the closed issue" },
4111
+ { arguments: "<N> <follow-up-N>", note: "and a follow-up filed this run" }
4112
+ ]
4113
+ },
4114
+ ship: {
4115
+ examples: [
4116
+ { arguments: '"<title> #<N>" --body-file <evidence.md>' },
4117
+ { arguments: '"<title> #<N>" --review', note: "both review rounds inside the ship" },
4118
+ { arguments: '--detach "<title> #<N>"', note: "hand the ship to a supervisor" },
4119
+ { arguments: "--log <N>", note: "a detached ship's log" }
4120
+ ],
4121
+ flags: [
4122
+ {
4123
+ names: ["--body-file <path>"],
4124
+ description: "the PR body carrying the live-execution evidence `followup` gates on; forwarded to `git -y` alone."
4125
+ },
4126
+ { names: ["--notify-message <text>"], description: "forwarded to `followup` alone." },
4127
+ {
4128
+ names: ["--review"],
4129
+ description: "runs review round 1 beside the gate, and round 2 after the commit when `review:round2` requires it; a High or an unfixed Medium stops the ship."
4130
+ },
4131
+ {
4132
+ names: ["--detach"],
4133
+ description: "hands the ship to a supervisor (implied in a lane child); a stop emits `ship-stop` (`--log <N>`). The preflight, type check, unused exports, cspell and guard tests run before the hand-off; a stop returns to the same session."
4134
+ }
4135
+ ]
4136
+ },
4137
+ "run:report": {
4138
+ examples: [{ arguments: "", note: "print the report; the same text josh notify sends" }]
4139
+ },
4140
+ "run:event": {
4141
+ examples: [
4142
+ { arguments: "--append <kind> <text>", note: "append one event; prints its position" },
4143
+ { arguments: "--from <position>", note: "every event after <position>, in order (JSON)" },
4144
+ { arguments: "--follow <position>", note: "new events, waiting for one; position on stderr" },
4145
+ { arguments: "--watch [<position>]", note: "every new event, rendered, until interrupted" },
4146
+ { arguments: "--last", note: "the newest event alone" }
4147
+ ]
4148
+ }
4149
+ };
4150
+
4151
+ // scripts/josh/josh-usage-session.ts
4152
+ var BARE = { arguments: "" };
4153
+ var PATH_DIR = "--path <dir>";
4154
+ var SESSION_USAGE = {
4155
+ "run:progress": {
4156
+ examples: [
4157
+ { arguments: "--output <path>" },
4158
+ { arguments: "--once", note: "five labelled lines now, whatever the clock says" },
4159
+ { arguments: "--interval 20 --repo joshuafolkken/app-kit --hours 4" }
4160
+ ],
4161
+ flags: [
4162
+ {
4163
+ names: ["--mark"],
4164
+ description: "record that a real report happened without printing a line (keeps the last line for `run:wake --list`)."
4165
+ },
4166
+ {
4167
+ names: ["--path"],
4168
+ description: 'print the ambient heartbeat log\'s path and exit. Every heartbeat line is mirrored there, so `tail -F "$(pnpm josh run:progress --path)"` follows the run across a `backlogrun` session cut. Reads no run state, so it answers in a lane child too.'
4169
+ },
4170
+ {
4171
+ names: ["--interval <min>"],
4172
+ description: "silence interval (default 20; also `JOSH_PROGRESS_INTERVAL_MINUTES`, then `josh.progress_interval_minutes`)."
4173
+ },
4174
+ {
4175
+ names: ["--wait"],
4176
+ description: "start once in the background: each report goes to the event stream and ambient log, never stdout; exits only on an arrival, `josh followup` ending the run, or `--hours`."
4177
+ },
4178
+ {
4179
+ names: ["--hours <n>"],
4180
+ description: "how long the watcher lives (default 8 with `--wait`, 1 otherwise)."
4181
+ },
4182
+ { names: ["--repo <owner/name>"], description: "scopes the read." }
4183
+ ]
4184
+ },
4185
+ "run:board": {
4186
+ examples: [
4187
+ { arguments: "", note: "redraw until interrupted" },
4188
+ { arguments: "--once", note: "one frame, then exit" },
4189
+ { arguments: "--chat", note: "one frame for a chat, recorded as a progress report" },
4190
+ {
4191
+ arguments: "--every 5",
4192
+ note: "the --chat frame as a \u{1F4CA} Progress Telegram every 5 minutes, until the run ends"
4193
+ }
4194
+ ],
4195
+ flags: [
4196
+ {
4197
+ names: ["--chat"],
4198
+ description: "answers a `backlogrun` progress question: the frame `--once` draws, with only `\u{1F9E0}` on the machine line and no escapes; it records the report as `run:progress --mark` does."
4199
+ },
4200
+ {
4201
+ names: ["--every <minutes>"],
4202
+ description: "sends the `--chat` frame as a `\u{1F4CA} Progress` Telegram now and every `<minutes>` until the run ends \u2014 only when a person asks for it."
4203
+ }
4204
+ ]
4205
+ },
4206
+ "run:watcher:guard": { examples: [BARE] },
4207
+ "run:stranded": { examples: [BARE] },
4208
+ "lane:open": { examples: [{ arguments: "1490", note: "prints the lane directory on stdout" }] },
4209
+ "lane:close": {
4210
+ examples: [{ arguments: "1490" }, { arguments: "--all" }],
4211
+ flags: [{ names: ["--all"], description: "closes every lane." }]
4212
+ },
4213
+ "lane:list": { examples: [BARE] },
4214
+ "lane:prune": { examples: [BARE] },
4215
+ "lane:output": {
4216
+ examples: [
4217
+ { arguments: "1713 /abs/path/to/agent-7.jsonl", note: "record it; prints the path back" },
4218
+ { arguments: "1713", note: "read it; prints the path, or `none`" }
4219
+ ]
4220
+ },
4221
+ "lane:dispatch": {
4222
+ examples: [
4223
+ {
4224
+ arguments: "1749",
4225
+ note: "prints the child's pid; a refusal is an empty capture and exit 1"
4226
+ }
4227
+ ],
4228
+ flags: [
4229
+ {
4230
+ names: ["JOSH_{SCHEDULER,WORKER,REVIEWER}_MODEL"],
4231
+ description: "Claude Code role overrides; Anthropic defaults are respectively `claude-opus-5-5`, `claude-opus-5-5`, and `claude-opus-5-5`. Codex keeps its provider-specific model."
4232
+ },
4233
+ {
4234
+ names: ["JOSH_{SCHEDULER,WORKER,REVIEWER}_EFFORT"],
4235
+ description: "role effort overrides for either provider; defaults are `medium`, `medium`, and `high`."
4236
+ },
4237
+ {
4238
+ names: ["JOSH_REVIEWER_VERIFICATION_{MODEL,EFFORT}"],
4239
+ description: "review round 2 and later (the verification pass); Anthropic defaults are `claude-sonnet-5-5` / `high`, falling back to `JOSH_REVIEWER_*`."
4240
+ }
4241
+ ]
4242
+ },
4243
+ "lane:await": {
4244
+ examples: [
4245
+ { arguments: "1749 1750", note: "block until either lane completes" },
4246
+ {
4247
+ arguments: '1749 1750 --owner "$PPID"',
4248
+ note: "reclaim the carry record for a resumed conversation first"
4249
+ }
4250
+ ]
4251
+ },
4252
+ "lane:launch": {
4253
+ examples: [
4254
+ { arguments: "1749", note: "every lane after the first" },
4255
+ {
4256
+ arguments: '1749 --stash "backlogrun: josh latest before lanes"',
4257
+ note: "the first lane only"
4258
+ }
4259
+ ]
4260
+ },
4261
+ "lane:sample": { examples: [{ arguments: "", note: "one machine-load reading" }] },
4262
+ "lane:stats": {
4263
+ examples: [{ arguments: "--period 7", note: "a 7-day period's stage medians and maxima" }]
4264
+ },
4265
+ cost: {
4266
+ examples: [
4267
+ {
4268
+ arguments: "--cut",
4269
+ note: "compare billed input per request with the shared 135,000 context-cut threshold"
4270
+ },
4271
+ { arguments: "--over <tokens>", note: "compare with an explicit limit" },
4272
+ { arguments: `--cut ${PATH_DIR}`, note: "Anthropic project or current OpenAI worktree" }
4273
+ ],
4274
+ flags: [
4275
+ {
4276
+ names: [PATH_DIR],
4277
+ description: "selects another Anthropic project; OpenAI accepts only linked checkouts of the current project."
4278
+ }
4279
+ ]
4280
+ },
4281
+ "doc:section": {
4282
+ examples: [
4283
+ { arguments: '<file.md> "<heading>"' },
4284
+ { arguments: 'backlogrun.md "The hand-off"' }
4285
+ ]
4286
+ },
4287
+ "read:set": {
4288
+ examples: [
4289
+ { arguments: "", note: "every entry point" },
4290
+ { arguments: "backlogrun", note: "one of them" },
4291
+ { arguments: "backlogrun --json" }
4292
+ ]
4293
+ },
4294
+ "doc:read": {
4295
+ examples: [
4296
+ { arguments: "CLAUDE.md", note: "under the cap: prints the document" },
4297
+ { arguments: "backlogrun.md", note: "over the cap: prints a directive, no content" }
4298
+ ]
4299
+ },
4300
+ "read:files": {
4301
+ examples: [
4302
+ { arguments: "a.ts b.ts c.ts", note: "under the cap: prints each file under its own header" },
4303
+ {
4304
+ arguments: "a.ts big.ts",
4305
+ note: "over the cap: prints a directive to Read them in one turn, no content"
4306
+ }
4307
+ ]
4308
+ },
4309
+ "edit:files": {
4310
+ examples: [
4311
+ {
4312
+ arguments: "plan.txt",
4313
+ note: "applies every block in the plan, one line per edit reporting the outcome"
4314
+ },
4315
+ { arguments: "- <<'EDITS'", note: "plan from stdin, then EDITS" }
4316
+ ]
4317
+ },
4318
+ time: {
4319
+ examples: [
4320
+ { arguments: "", note: "the last run tree, wall clock and cost by role" },
4321
+ { arguments: "--run", note: "the same run-tree scope, named explicitly" },
4322
+ { arguments: "--json", note: "the same figures, machine-readable" },
4323
+ { arguments: PATH_DIR, note: "read another project's transcripts from this checkout" }
4324
+ ],
4325
+ flags: [
4326
+ {
4327
+ names: ["--run"],
4328
+ description: '(default) the whole run tree, wall clock led beside dollars. It is the only scope; any other flag is a usage error (exit 1). History: `docs/maintainers/josh-commands-run-rationale.md` \u2192 "`josh cost` and `josh time` lost their report scopes".'
4329
+ },
4330
+ {
4331
+ names: [PATH_DIR],
4332
+ description: "aim the read at another project (absolute path); keeps the `cwd` behavior when absent."
4333
+ },
4334
+ { names: ["--json"], description: "the run tree, machine-readable." }
4335
+ ]
4336
+ },
4337
+ "cost:audit": {
4338
+ examples: [
4339
+ { arguments: "", note: "the last 7 days against the 7 before" },
4340
+ { arguments: "--period 30", note: "the last 30 days against the 30 before" }
4341
+ ],
4342
+ flags: [
4343
+ {
4344
+ names: ["--period <days>"],
4345
+ description: "the length of each period, a positive whole number of days (default 7)."
4346
+ }
4347
+ ]
4348
+ },
4349
+ retrospective: {
4350
+ examples: [{ arguments: "", note: "the digest for the run this checkout carries" }]
4351
+ },
4352
+ eval: {
4353
+ examples: [
4354
+ { arguments: "", note: "every scenario" },
4355
+ { arguments: "consult-not-execute", note: "one scenario by name" }
4356
+ ],
4357
+ flags: [
4358
+ { names: ["JOSH_EVAL_MODEL"], description: "the model to run against (default `sonnet`)." },
4359
+ {
4360
+ names: ["JOSH_EVAL_CONCURRENCY"],
4361
+ description: "how many sessions run at once (default `5`); a non-positive-integer value is refused."
4362
+ }
4363
+ ]
4364
+ }
4365
+ };
4366
+
4367
+ // scripts/josh/josh-usage-workflow.ts
4368
+ var PR_TITLE = '"PR title #N"';
4369
+ var ISSUE_URL = '--issue-url "https://..."';
4370
+ var REVIEW_ISSUE = "--issue 2325";
4371
+ var CHECK2 = "--check";
4372
+ var JSON_FLAG4 = "--json";
4373
+ var BODY_FILE_NOTE = "(`-` reads stdin); use this whenever the body carries a backtick or `$`. Passing both forms is refused.";
4374
+ var WORKFLOW_USAGE = {
4375
+ followup: {
4376
+ examples: [
4377
+ { arguments: PR_TITLE, note: "merges (default)" },
4378
+ { arguments: String.raw`${PR_TITLE} --notify-message "Implemented X:\n- change 1"` },
4379
+ { arguments: `${PR_TITLE} --notify-message-file completion.md` },
4380
+ { arguments: `${PR_TITLE} --ai-review-ignore-reason "false positive"` },
4381
+ { arguments: `${PR_TITLE} --no-merge`, note: "do the work, leave the PR open" }
4382
+ ],
4383
+ flags: [
4384
+ {
4385
+ names: ["--no-merge"],
4386
+ description: "do the follow-up work but do not merge; leaves the PR open (the only flag that stops the merge). `--merge` is a deprecated no-op. A PR already merged by hand skips the CI wait, the AI-review scan and the merge and runs only the post-merge tail; a completion report already on the Issue is not posted again."
4387
+ },
4388
+ {
4389
+ names: ["--notify-message"],
4390
+ description: "inline completion body; `\\n` expands to newlines."
4391
+ },
4392
+ {
4393
+ names: ["--notify-message-file"],
4394
+ description: `read the completion body from a file ${BODY_FILE_NOTE}`
4395
+ },
4396
+ {
4397
+ names: ["--ai-review-ignore-reason"],
4398
+ description: "reason to dismiss an AI-review finding."
4399
+ }
4400
+ ]
4401
+ },
4402
+ notify: {
4403
+ examples: [
4404
+ { arguments: String.raw`--task-type planning ${ISSUE_URL} --body="- bullet 1\n- bullet 2"` },
4405
+ {
4406
+ arguments: `--task-type confirmation ${ISSUE_URL} --body-file r.md --recommended "Go" --alternative "Wait"`
4407
+ }
4408
+ ],
4409
+ flags: [
4410
+ {
4411
+ names: ["--task-type"],
4412
+ description: "one of `planning` \u{1F4CB}, `completion` \u2705, `failure` \u274C, `warning` \u26A0\uFE0F, `kickoff_retry` \u{1F504}, `confirmation` \u23F8\uFE0F. Do not send `completion` or `warning` by hand \u2014 `josh followup` sends them."
4413
+ },
4414
+ {
4415
+ names: ["--body", "--body=$'\u2026'"],
4416
+ description: "inline body; use `--body=$'\u2026'` when it starts with `-`."
4417
+ },
4418
+ { names: ["--body-file"], description: `read the body from a file ${BODY_FILE_NOTE}` },
4419
+ {
4420
+ names: ["--repo-name", "--issue-url", "--pr-url"],
4421
+ description: "header repository, resolved in that order, then the working directory. The issue title is read from `--issue-url`."
4422
+ },
4423
+ {
4424
+ names: ["--recommended", "--alternative"],
4425
+ description: "the choices appended under `JOSH_SESSION_LANG` headings; `--recommended` is required for `confirmation`, `--alternative` is repeatable."
4426
+ }
4427
+ ]
4428
+ },
4429
+ "observations:flush": { examples: [{ arguments: "" }] },
4430
+ "measure:rerun": { examples: [{ arguments: "2212" }] },
4431
+ "review:record": {
4432
+ examples: [
4433
+ { arguments: `${REVIEW_ISSUE} bug-risks:medium:src/foo.ts:42 tests:low:a.test.ts` },
4434
+ { arguments: REVIEW_ISSUE, note: "a zero-finding round \u2014 records one `none` line" },
4435
+ {
4436
+ arguments: `${REVIEW_ISSUE} --comment`,
4437
+ note: "a round that passed after the PR opened \u2014 an Issue comment, no append"
4438
+ },
4439
+ { arguments: `${CHECK2} ${REVIEW_ISSUE}`, note: "the merge gate: was it recorded?" }
4440
+ ]
4441
+ },
4442
+ bump: { examples: [{ arguments: "major" }, { arguments: "minor" }, { arguments: "patch" }] },
4443
+ release: {
4444
+ examples: [
4445
+ { arguments: "" },
4446
+ { arguments: "--dry-run", note: "count and report, write nothing" }
4447
+ ],
4448
+ flags: [
4449
+ {
4450
+ names: ["--dry-run"],
4451
+ description: "count and report only; writes nothing, fetches nothing, and creates no work tree."
4452
+ },
4453
+ {
4454
+ names: ["--help", "-h"],
4455
+ description: "print the usage line and exit 0 without fetching or releasing. Any other argument is refused with the usage line and a non-zero exit, before anything is fetched."
4456
+ }
4457
+ ]
4458
+ },
4459
+ "release:scope": {
4460
+ examples: [
4461
+ { arguments: "", note: "\u2192 required | skip | unknown" },
4462
+ { arguments: "--json", note: '{"scope":"\u2026","reason":"\u2026"} on one line' }
4463
+ ]
4464
+ },
4465
+ "ruleset:check": {
4466
+ examples: [
4467
+ { arguments: "", note: "report only" },
4468
+ { arguments: "--apply", note: "add the missing checks to the existing rule" }
4469
+ ],
4470
+ flags: [
4471
+ {
4472
+ names: ["--apply"],
4473
+ description: "append the missing checks to the ruleset's required-checks rule (or to the branch protection's contexts). It never creates a ruleset: with none, create one under Settings \u2192 Rules \u2192 Rulesets, then rerun. Writing a repository setting needs admin access, so only this flag writes \u2014 `josh sync` and `josh doctor` never do."
4474
+ }
4475
+ ]
4476
+ },
4477
+ "audit:provision": {
4478
+ examples: [
4479
+ { arguments: "", note: "no-op if a scanner is already present" },
4480
+ { arguments: "--force", note: "ignore backoff, allow a longer download" }
4481
+ ],
4482
+ flags: [
4483
+ {
4484
+ names: ["--force"],
4485
+ description: "ignore the 6-hour failure backoff and allow a longer download window; the command to run after fixing a network problem."
4486
+ }
4487
+ ]
4488
+ },
4489
+ "reconcile-templates": {
4490
+ examples: [
4491
+ { arguments: "", note: "regenerate copy templates + record tripwire hashes" },
4492
+ { arguments: CHECK2, note: "verify templates are in sync; non-zero on drift" }
4493
+ ],
4494
+ flags: [
4495
+ {
4496
+ names: [CHECK2],
4497
+ description: "verify only; exits non-zero on drift. A pre-commit hook runs this when a tracked source or copy template is staged."
4498
+ }
4499
+ ]
4500
+ },
4501
+ "pre-push-unit": { examples: [{ arguments: "" }] },
4502
+ "reserved-run": {
4503
+ examples: [
4504
+ { arguments: "2 -- pnpm install", note: "hold 2 cores while installing" },
4505
+ { arguments: "1 -- pnpm josh audit", note: "hold 1 core while scanning" }
4506
+ ]
4507
+ },
4508
+ "pre-commit-type-check": { examples: [{ arguments: "" }] },
4509
+ "pr:classification": {
4510
+ examples: [{ arguments: "", note: "run by CI on a pull request; fails without one label" }]
4511
+ },
4512
+ "observation:record": {
4513
+ examples: [
4514
+ { arguments: "flaky-e2e d1 'tests/e2e/login.test.ts' 'times out on CI only'" },
4515
+ {
4516
+ arguments: "flaky-e2e d1 'src/app.ts' 'fails there too' --checkout ~/Development/app",
4517
+ note: "another repository's ledger"
4518
+ }
4519
+ ],
4520
+ flags: [
4521
+ {
4522
+ names: ["--checkout <path>"],
4523
+ description: "count and append in that repository's `.josh/observations/` instead."
4524
+ }
4525
+ ]
4526
+ },
4527
+ "release:github": {
4528
+ examples: [
4529
+ { arguments: "", note: "run by CI with GH_TOKEN, RELEASE_TAG and GITHUB_REPOSITORY set" }
4530
+ ]
4531
+ },
4532
+ "latest:scope": {
4533
+ examples: [
4534
+ { arguments: "", note: "required or skip, the reason on stderr" },
4535
+ { arguments: "--record", note: "note a finished josh latest run" },
4536
+ { arguments: JSON_FLAG4, note: "the verdict and the reason as JSON" }
4537
+ ],
4538
+ flags: [
4539
+ { names: ["--record"], description: "write the completion record `josh latest` ends with." },
4540
+ { names: [JSON_FLAG4], description: "emit the verdict and its reason as JSON." }
4541
+ ]
4542
+ },
4543
+ "latest:guard": { examples: [{ arguments: "", note: "refuses inside a lane" }] },
4544
+ "prevent-main-commit": { examples: [{ arguments: "", note: "run by the pre-commit hook" }] },
4545
+ "check-commit-message": {
4546
+ examples: [{ arguments: ".git/COMMIT_EDITMSG", note: "run by the commit-msg hook" }]
4547
+ },
4548
+ "secretlint-scan": {
4549
+ examples: [{ arguments: "src/app.ts .env.example", note: "the staged paths" }]
4550
+ },
4551
+ "pre-commit-lint": {
4552
+ examples: [
4553
+ { arguments: "cspell src/app.ts" },
4554
+ { arguments: "prettier src/app.ts" },
4555
+ { arguments: "eslint src/app.ts" }
4556
+ ]
4557
+ }
4558
+ };
4559
+
4560
+ // scripts/josh/command-usage.ts
4561
+ var USAGE_PAGE_TABLES = {
4562
+ "docs/josh-commands.md": [DEVELOPER_USAGE],
4563
+ "docs/josh-commands-run.md": [RUN_USAGE, SESSION_USAGE],
4564
+ "docs/josh-commands-backlog.md": [ISSUE_USAGE, BACKLOG_USAGE, REVIEW_USAGE],
4565
+ "docs/josh-commands-automation.md": [AUTOMATION_USAGE, WORKFLOW_USAGE, HOOK_USAGE]
4566
+ };
4567
+ var COMMAND_USAGE = Object.fromEntries(
4568
+ Object.values(USAGE_PAGE_TABLES).flat().flatMap((table) => Object.entries(table))
4569
+ );
4570
+ var NOTE_GAP = 3;
4571
+ function usage_for(name) {
4572
+ return Object.hasOwn(COMMAND_USAGE, name) ? COMMAND_USAGE[name] : void 0;
4573
+ }
4574
+ function example_line(name, example) {
4575
+ const command = example.arguments ? `pnpm josh ${name} ${example.arguments}` : `pnpm josh ${name}`;
4576
+ return { command, note: example.note };
4577
+ }
4578
+ function format_examples(name, usage) {
4579
+ const lines = usage.examples.map((example) => example_line(name, example));
4580
+ const width = Math.max(...lines.map((line) => line.command.length)) + NOTE_GAP;
4581
+ const rendered = lines.map(
4582
+ (line) => line.note === void 0 ? line.command : `${line.command.padEnd(width)}# ${line.note}`
4583
+ );
4584
+ return ["```bash", ...rendered, "```"].join("\n");
4585
+ }
4586
+ function code_span(text) {
4587
+ return `\`${text}\``;
4588
+ }
4589
+ function format_flags(flags) {
4590
+ const items = flags.map(
4591
+ (flag) => `- ${flag.names.map((spelling) => code_span(spelling)).join(" / ")} \u2014 ${flag.description}`
4592
+ );
4593
+ return ["**Options:**", "", ...items].join("\n");
4594
+ }
4595
+ function format_usage(name) {
4596
+ const usage = usage_for(name);
4597
+ if (usage === void 0) return void 0;
4598
+ const flags = usage.flags === void 0 ? [] : [format_flags(usage.flags)];
4599
+ return [format_examples(name, usage), ...flags].join("\n\n");
4600
+ }
4601
+ function format_command_help(input) {
4602
+ const name = canonical_command(input);
4603
+ const entry = Object.hasOwn(COMMAND_MAP, name) ? COMMAND_MAP[name] : void 0;
4604
+ if (entry === void 0) return void 0;
4605
+ const [synopsis] = entry.reference;
4606
+ const head = synopsis ? `josh ${name} ${synopsis}` : `josh ${name}`;
4607
+ const usage = format_usage(name);
4608
+ return [head, "", entry.description, ...usage === void 0 ? [] : ["", usage]].join("\n");
4609
+ }
4610
+ function page_commands(page) {
4611
+ const tables = Object.hasOwn(USAGE_PAGE_TABLES, page) ? USAGE_PAGE_TABLES[page] : void 0;
4612
+ return (tables ?? []).flatMap((table) => Object.keys(table));
4613
+ }
4614
+ var command_usage = {
4615
+ USAGE_PAGES: Object.keys(USAGE_PAGE_TABLES),
4616
+ format_command_help,
4617
+ format_usage,
4618
+ page_commands
4619
+ };
4620
+
4621
+ // scripts/josh/josh-logic.ts
4622
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
4623
+ import path15 from "node:path";
4624
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
4625
+
4626
+ // scripts/build/local-bin.ts
4627
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
4628
+ import { createRequire } from "node:module";
4629
+ import path11 from "node:path";
4630
+
4631
+ // scripts/lib/ancestor-directories.ts
4632
+ import path10 from "node:path";
4633
+ var MAX_DEPTH = 64;
4634
+ function list(start_directory) {
4635
+ const directories = [];
4636
+ let current = path10.resolve(start_directory);
4637
+ while (directories.length < MAX_DEPTH && !directories.includes(current)) {
4638
+ directories.push(current);
4639
+ current = path10.dirname(current);
4640
+ }
4641
+ return directories;
4642
+ }
4643
+ function nearest(start_directory, is_match) {
4644
+ return list(start_directory).find((directory) => is_match(directory));
4645
+ }
4646
+ var ancestor_directories = { list, nearest };
4647
+
4648
+ // scripts/lib/schemas.ts
4649
+ import { z as z4 } from "zod";
4650
+ var overrides_snapshot_schema = z4.record(z4.string(), z4.string());
4651
+ var package_version_schema = z4.object({ version: z4.string() });
4652
+ var package_named_version_schema = z4.looseObject({ name: z4.string(), version: z4.string() });
4653
+ var package_with_version_schema = z4.looseObject({ version: z4.string() });
4654
+ var ls_global_dependency_schema = z4.looseObject({ version: z4.string() });
4655
+ var ls_global_dependencies_schema = z4.record(z4.string(), ls_global_dependency_schema).optional();
4656
+ var pnpm_ls_global_schema = z4.array(
4657
+ z4.looseObject({
4658
+ dependencies: ls_global_dependencies_schema
4659
+ })
4660
+ );
4661
+ var vscode_settings_schema = z4.record(z4.string(), z4.unknown());
4662
+ var with_scripts_schema = z4.looseObject({
4663
+ scripts: z4.record(z4.string(), z4.string()).optional()
4664
+ });
4665
+ var with_development_deps_schema = z4.looseObject({
4666
+ devDependencies: z4.record(z4.string(), z4.string()).optional()
4667
+ });
4668
+ var with_package_manager_schema = z4.looseObject({
4669
+ packageManager: z4.string().optional()
4670
+ });
4671
+ var with_development_engine_schema = z4.looseObject({
4672
+ devEngines: z4.record(z4.string(), z4.unknown()).optional()
4673
+ });
4674
+ var bin_entry_schema = z4.union([z4.string(), z4.record(z4.string(), z4.string())]).optional();
4675
+ var package_bin_schema = z4.looseObject({ bin: bin_entry_schema });
4676
+ var json_object_schema = z4.record(z4.string(), z4.unknown());
4677
+ var string_array_schema = z4.array(z4.string());
4678
+ var string_record_schema = z4.record(z4.string(), z4.string());
4679
+
4680
+ // scripts/build/local-bin.ts
4681
+ var NODE_MODULES2 = "node_modules";
4682
+ var BIN_DIR = ".bin";
4683
+ var WINDOWS_PLATFORM3 = "win32";
4684
+ var WINDOWS_SHIM_SUFFIX = ".cmd";
4685
+ var PACKAGE_JSON = "package.json";
4686
+ function resolve_local_bin(project_root, bin_name) {
4687
+ const shim = process.platform === WINDOWS_PLATFORM3 ? `${bin_name}${WINDOWS_SHIM_SUFFIX}` : bin_name;
4688
+ return path11.join(project_root, NODE_MODULES2, BIN_DIR, shim);
4689
+ }
4690
+ function read_bin_entry(manifest_path, package_name, bin_name) {
4691
+ const { bin } = package_bin_schema.parse(JSON.parse(readFileSync4(manifest_path, "utf8")));
4692
+ if (typeof bin === "string") return bin_name === path11.basename(package_name) ? bin : void 0;
4693
+ return bin?.[bin_name];
4694
+ }
4695
+ function resolve_package_bin(base_directory, package_name, bin_name) {
4696
+ try {
4697
+ const resolve_from = createRequire(path11.join(base_directory, PACKAGE_JSON));
4698
+ const manifest_path = resolve_from.resolve(`${package_name}/${PACKAGE_JSON}`);
4699
+ const bin_entry = read_bin_entry(manifest_path, package_name, bin_name);
4700
+ if (bin_entry === void 0) return void 0;
4701
+ const cli_entry = path11.join(path11.dirname(manifest_path), bin_entry);
4702
+ return existsSync3(cli_entry) ? cli_entry : void 0;
4703
+ } catch {
4704
+ return void 0;
4705
+ }
4706
+ }
4707
+
4708
+ // scripts/josh/josh-logic.ts
4709
+ import { execaSync as execaSync2 } from "execa";
4710
+
4711
+ // scripts/josh/command-suggest.ts
4712
+ var MAX_SUGGESTION_DISTANCE = 3;
4713
+ function at(values, index) {
4714
+ return values[index] ?? 0;
4715
+ }
4716
+ function fill_distance_row(source, target, row, previous) {
4717
+ const current = [row];
4718
+ for (let col = 1; col <= target.length; col++) {
4719
+ const cost = source[row - 1] === target[col - 1] ? 0 : 1;
4720
+ const deletion = at(previous, col) + 1;
4721
+ const insertion = at(current, col - 1) + 1;
4722
+ const substitution = at(previous, col - 1) + cost;
4723
+ current.push(Math.min(deletion, insertion, substitution));
4724
+ }
4725
+ return current;
4726
+ }
4727
+ function edit_distance(source, target) {
4728
+ let previous = Array.from({ length: target.length + 1 }, (_unused, index) => index);
4729
+ for (let row = 1; row <= source.length; row++) {
4730
+ previous = fill_distance_row(source, target, row, previous);
4731
+ }
4732
+ return at(previous, target.length);
4733
+ }
4734
+ function closest_command(input, commands) {
4735
+ const within = commands.map((command) => ({ command, distance: edit_distance(input, command) })).filter((scored) => scored.distance <= MAX_SUGGESTION_DISTANCE).toSorted((left, right) => left.distance - right.distance);
4736
+ return within[0]?.command;
4737
+ }
4738
+ var command_suggest = { closest_command, edit_distance };
4739
+
4740
+ // scripts/josh/josh-composite-arguments.ts
4741
+ var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["sh", "bash", "zsh", "dash"]);
4742
+ var SHELL_COMMAND_FLAG = "-c";
4743
+ var PATH_SEPARATOR = "/";
4744
+ var USAGE_ERROR_EXIT_CODE = 1;
4745
+ function is_shell_interpreter(executable) {
4746
+ return SHELL_INTERPRETERS.has(executable.split(PATH_SEPARATOR).at(-1) ?? executable);
4747
+ }
4748
+ function is_composite_shell(shell) {
4749
+ const [executable, flag] = shell ?? [];
4750
+ if (executable === void 0) return false;
4751
+ return is_shell_interpreter(executable) && flag === SHELL_COMMAND_FLAG;
4752
+ }
4753
+ function format_targets(targets) {
4754
+ return targets.map((target) => `josh ${target}`).join(" or ");
4755
+ }
4756
+ function format_rejection(cmd, targets) {
4757
+ const hint = targets.length > 0 ? `pass them to ${format_targets(targets)} instead` : "it runs a fixed sequence and forwards nothing";
4758
+ return `josh ${cmd} takes no extra arguments \u2014 ${hint}`;
4759
+ }
4760
+ function reject_extra_arguments(cmd, entry, extra) {
4761
+ if (extra.length === 0 || !is_composite_shell(entry.shell)) return void 0;
4762
+ return format_rejection(cmd, entry.argument_targets ?? []);
4763
+ }
4764
+ var composite_arguments = { format_rejection, is_composite_shell, reject_extra_arguments };
4765
+
4766
+ // scripts/josh/josh-in-process.ts
4767
+ import { realpathSync } from "node:fs";
4768
+ import path12 from "node:path";
4769
+ import { pathToFileURL } from "node:url";
4770
+ var TYPESCRIPT_EXTENSION = ".ts";
4771
+ var JAVASCRIPT_EXTENSION = ".js";
4772
+ var BUNDLED_COMMAND_DIRECTORY = path12.join("dist", "commands");
4773
+ var FAILURE_EXIT_CODE = 1;
4774
+ function is_typescript_dispatcher(dispatcher_url) {
4775
+ return dispatcher_url.endsWith(TYPESCRIPT_EXTENSION);
4776
+ }
4777
+ function bundled_script_path(package_directory, script) {
4778
+ const name = `${path12.basename(script, TYPESCRIPT_EXTENSION)}${JAVASCRIPT_EXTENSION}`;
4779
+ return path12.join(package_directory, BUNDLED_COMMAND_DIRECTORY, name);
4780
+ }
4781
+ function in_process_target(entry, package_directory, dispatcher_url) {
4782
+ if (entry.script === void 0 || entry.tsx_arguments !== void 0) return void 0;
4783
+ if (is_typescript_dispatcher(dispatcher_url)) return path12.join(package_directory, entry.script);
4784
+ return entry.is_bundled === true ? bundled_script_path(package_directory, entry.script) : void 0;
4785
+ }
4786
+ function read_exit_code() {
4787
+ const code = process.exitCode;
4788
+ return typeof code === "number" ? code : 0;
4789
+ }
4790
+ function resolve_real_path(script_path) {
4791
+ try {
4792
+ return realpathSync(script_path);
4793
+ } catch {
4794
+ return script_path;
4795
+ }
4796
+ }
4797
+ async function run_in_process(script_path, script_arguments) {
4798
+ const resolved_path = resolve_real_path(script_path);
4799
+ process.argv = [process.execPath, resolved_path, ...script_arguments];
4800
+ try {
4801
+ await import(pathToFileURL(resolved_path).href);
4802
+ } catch (error) {
4803
+ console.error(error);
2966
4804
  return FAILURE_EXIT_CODE;
2967
4805
  }
2968
4806
  return read_exit_code();
@@ -2985,9 +4823,20 @@ var kit_only = {
2985
4823
  notice: kit_only_notice
2986
4824
  };
2987
4825
 
4826
+ // scripts/josh/package-directory.ts
4827
+ import { existsSync as existsSync4 } from "node:fs";
4828
+ import path13 from "node:path";
4829
+ var PACKAGE_JSON2 = "package.json";
4830
+ function find_package_directory(start_directory) {
4831
+ return ancestor_directories.nearest(
4832
+ start_directory,
4833
+ (directory) => existsSync4(path13.join(directory, PACKAGE_JSON2))
4834
+ ) ?? start_directory;
4835
+ }
4836
+
2988
4837
  // scripts/josh/run-github-prerequisite.ts
2989
- import { existsSync as existsSync3 } from "node:fs";
2990
- import path10 from "node:path";
4838
+ import { existsSync as existsSync5 } from "node:fs";
4839
+ import path14 from "node:path";
2991
4840
 
2992
4841
  // scripts/discovery/repo-origin.ts
2993
4842
  var GITHUB_HOST = "github.com";
@@ -3096,7 +4945,7 @@ var GITHUB_RUN_COMMANDS = /* @__PURE__ */ new Set([
3096
4945
  ]);
3097
4946
  function explanation(root, command) {
3098
4947
  if (!GITHUB_RUN_COMMANDS.has(command)) return void 0;
3099
- if (!existsSync3(path10.join(root, ".git"))) {
4948
+ if (!existsSync5(path14.join(root, ".git"))) {
3100
4949
  return `${command} requires Git and a GitHub origin. Run git init and add a GitHub origin first.`;
3101
4950
  }
3102
4951
  const url = git_spawn_sync.origin_url(root);
@@ -3110,21 +4959,15 @@ var COLUMN_WIDTH = 26;
3110
4959
  var ALIAS_PAD_WIDTH = 2;
3111
4960
  var TSX_BIN = "tsx";
3112
4961
  var TSX_PACKAGE = "tsx";
3113
- var PACKAGE_JSON2 = "package.json";
4962
+ var PACKAGE_JSON3 = "package.json";
3114
4963
  var UNKNOWN_COMMAND_EXIT_CODE = -1;
3115
- function find_package_directory(start_directory) {
3116
- return ancestor_directories.nearest(
3117
- start_directory,
3118
- (directory) => existsSync4(path11.join(directory, PACKAGE_JSON2))
3119
- ) ?? start_directory;
3120
- }
3121
- var PACKAGE_DIR2 = find_package_directory(path11.dirname(fileURLToPath2(import.meta.url)));
4964
+ var PACKAGE_DIR2 = find_package_directory(path15.dirname(fileURLToPath2(import.meta.url)));
3122
4965
  function resolve_tsx_executable() {
3123
4966
  const candidates = [
3124
4967
  resolve_local_bin(PACKAGE_DIR2, TSX_BIN),
3125
4968
  resolve_local_bin(process.cwd(), TSX_BIN)
3126
4969
  ];
3127
- return candidates.find(existsSync4) ?? TSX_BIN;
4970
+ return candidates.find(existsSync6) ?? TSX_BIN;
3128
4971
  }
3129
4972
  function resolve_tsx_cli_entry() {
3130
4973
  for (const base_directory of [PACKAGE_DIR2, process.cwd()]) {
@@ -3141,7 +4984,7 @@ function resolve_tsx_runner() {
3141
4984
  return { executable: resolve_tsx_executable(), leading_arguments: [] };
3142
4985
  }
3143
4986
  function read_package_version() {
3144
- const raw = readFileSync5(path11.join(PACKAGE_DIR2, PACKAGE_JSON2), "utf8");
4987
+ const raw = readFileSync5(path15.join(PACKAGE_DIR2, PACKAGE_JSON3), "utf8");
3145
4988
  return package_version_schema.parse(JSON.parse(raw)).version;
3146
4989
  }
3147
4990
  var HEADER = `josh v${read_package_version()} \u2014 Joshua Folkken's dev toolkit`;
@@ -3228,7 +5071,7 @@ async function run_script_entry(entry, subcommand_arguments) {
3228
5071
  const script_arguments = [...entry.default_script_arguments ?? [], ...subcommand_arguments];
3229
5072
  const target = josh_in_process.in_process_target(entry, PACKAGE_DIR2, import.meta.url);
3230
5073
  if (target !== void 0) return await josh_in_process.run_in_process(target, script_arguments);
3231
- return spawn_script_entry(entry, path11.join(PACKAGE_DIR2, entry.script ?? ""), script_arguments);
5074
+ return spawn_script_entry(entry, path15.join(PACKAGE_DIR2, entry.script ?? ""), script_arguments);
3232
5075
  }
3233
5076
  function github_prerequisite_exit(resolved, is_consumer) {
3234
5077
  if (!is_consumer) return void 0;
@@ -3260,13 +5103,13 @@ async function run_entry(entry, subcommand_arguments) {
3260
5103
  function declared(value) {
3261
5104
  return typeof value === "function" ? value() : value;
3262
5105
  }
3263
- async function dispatch_entry(entry, subcommand_arguments) {
5106
+ async function dispatch_entry(command, entry, subcommand_arguments) {
3264
5107
  const weight = declared(entry.core_weight);
3265
5108
  if (weight === void 0) return await run_entry(entry, subcommand_arguments);
3266
5109
  return await core_budget.with_command_reservation(
3267
5110
  weight,
3268
5111
  async () => await run_entry(entry, subcommand_arguments),
3269
- { memory_mb: declared(entry.memory_mb) }
5112
+ { memory_mb: declared(entry.memory_mb), command }
3270
5113
  );
3271
5114
  }
3272
5115
  async function run_command(cmd, subcommand_arguments, is_consumer = false) {
@@ -3275,7 +5118,7 @@ async function run_command(cmd, subcommand_arguments, is_consumer = false) {
3275
5118
  if (!entry) return UNKNOWN_COMMAND_EXIT_CODE;
3276
5119
  const early_exit = pre_dispatch_exit(resolved, entry, subcommand_arguments, is_consumer);
3277
5120
  if (early_exit !== void 0) return early_exit;
3278
- return await dispatch_entry(entry, subcommand_arguments);
5121
+ return await dispatch_entry(resolved, entry, subcommand_arguments);
3279
5122
  }
3280
5123
  var josh_logic = {
3281
5124
  format_help,
@@ -3289,13 +5132,17 @@ var josh_logic = {
3289
5132
  var ARGV_OFFSET = 2;
3290
5133
  var HELP_COMMANDS = /* @__PURE__ */ new Set(["help", "--help", "-h"]);
3291
5134
  var ALL_FLAG = "--all";
3292
- function print_help(is_all, is_consumer) {
3293
- console.info(josh_logic.format_help(is_all, is_consumer));
3294
- }
3295
5135
  function handle_unknown(cmd) {
3296
5136
  console.error(josh_logic.format_unknown_command(cmd));
3297
5137
  process.exit(1);
3298
5138
  }
5139
+ function print_help(is_all, is_consumer, topic) {
5140
+ if (topic === void 0 || topic.startsWith("-")) {
5141
+ console.info(josh_logic.format_help(is_all, is_consumer));
5142
+ return;
5143
+ }
5144
+ console.info(command_usage.format_command_help(topic) ?? handle_unknown(topic));
5145
+ }
3299
5146
  function record_exit_code(exit_code) {
3300
5147
  if (exit_code !== 0) process.exitCode = exit_code;
3301
5148
  }
@@ -3304,7 +5151,7 @@ async function main() {
3304
5151
  const is_all = process.argv.includes(ALL_FLAG);
3305
5152
  const is_consumer = doctor_consumer.is_kit_consumer(find_package_directory(process.cwd()));
3306
5153
  if (!cmd || cmd === ALL_FLAG || HELP_COMMANDS.has(cmd)) {
3307
- print_help(is_all, is_consumer);
5154
+ print_help(is_all, is_consumer, process.argv[ARGV_OFFSET + 1]);
3308
5155
  return;
3309
5156
  }
3310
5157
  const subcommand_arguments = process.argv.slice(ARGV_OFFSET + 1);