@joshuafolkken/kit 1.1178.0 → 1.1240.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/settings.json +2 -2
  3. package/.claude/skills/dependency-update/SKILL.md +4 -8
  4. package/.claude/skills/epic-commands/SKILL.md +2 -2
  5. package/.claude/skills/epic-commands/epic-bundle.md +1 -1
  6. package/.claude/skills/workflow-commands/SKILL.md +2 -4
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +7 -3
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +23 -2
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +9 -4
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +21 -13
  13. package/.claude/skills/workflow-commands/chain-rule.md +1 -1
  14. package/.claude/skills/workflow-commands/delegation.md +2 -2
  15. package/.claude/skills/workflow-commands/followup.md +10 -0
  16. package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
  17. package/.claude/skills/workflow-commands/halfrun.md +1 -1
  18. package/.claude/skills/workflow-commands/issue-scout.md +1 -1
  19. package/.claude/skills/workflow-commands/kickoff.md +2 -1
  20. package/.claude/skills/workflow-commands/latest-gate.md +1 -1
  21. package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
  22. package/.claude/skills/workflow-commands/pre-gate-cut.md +6 -1
  23. package/.claude/skills/workflow-commands/prerequisite.md +2 -2
  24. package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
  25. package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
  26. package/.codex/hooks.json +2 -2
  27. package/.cursorrules +6 -41
  28. package/.github/actions/setup-node/action.yml +1 -1
  29. package/.github/workflows/auto-tag.yml +3 -4
  30. package/.github/workflows/ci.yml +32 -31
  31. package/.github/workflows/dependabot-auto-merge.yml +13 -6
  32. package/.github/workflows/publish.yml +50 -4
  33. package/.github/workflows/sonar-qube.yml +3 -0
  34. package/.pnpmfile.mjs +4 -3
  35. package/.prettierignore +8 -2
  36. package/CLAUDE.md +18 -20
  37. package/README.md +44 -38
  38. package/SECURITY.md +15 -1
  39. package/dist/CLAUDE.md +18 -20
  40. package/dist/commands/document-section-cli.js +734 -0
  41. package/dist/hooks/audit-provision.js +346 -0
  42. package/dist/hooks/batch-guard.js +5 -4
  43. package/dist/hooks/{chunk-G6KRCKEC.js → chunk-4TZVB44A.js} +496 -378
  44. package/dist/hooks/chunk-5VNDSNG3.js +12 -0
  45. package/dist/hooks/chunk-CDY4NFQG.js +15 -0
  46. package/dist/hooks/chunk-ELCN5LCH.js +135 -0
  47. package/dist/hooks/{chunk-VQLLNFNT.js → chunk-F3ELQ2AF.js} +3 -3
  48. package/dist/hooks/{chunk-I4XSL4TT.js → chunk-GPTFMK5E.js} +1 -1
  49. package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
  50. package/dist/hooks/{chunk-UAGLLIGZ.js → chunk-KHGB5S3Y.js} +7 -6
  51. package/dist/hooks/{chunk-GDS5N2KB.js → chunk-KHX4U3BG.js} +735 -683
  52. package/dist/hooks/{chunk-VESKRQOF.js → chunk-KRBERBBI.js} +3 -3
  53. package/dist/hooks/{chunk-DO6NWWHU.js → chunk-QVSWU24O.js} +4 -4
  54. package/dist/hooks/{chunk-S6X6WYKR.js → chunk-WK2XSHSI.js} +1643 -923
  55. package/dist/hooks/{chunk-ZAL3NRGL.js → chunk-XZYKEM6J.js} +25 -10
  56. package/dist/hooks/{chunk-32GQTL4F.js → chunk-ZQGD4GBY.js} +4 -2
  57. package/dist/hooks/codex-hook-adapter.js +14 -12
  58. package/dist/hooks/duplicate-read-guard.js +6 -5
  59. package/dist/hooks/format-edited.js +4 -3
  60. package/dist/hooks/inputs.json +203 -179
  61. package/dist/hooks/investigation-guard.js +6 -5
  62. package/dist/hooks/pretool-guard.js +12 -10
  63. package/dist/hooks/session-lang.js +2 -4
  64. package/dist/hooks/stop-guard.js +66 -72
  65. package/dist/josh.js +1088 -78
  66. package/dist/version/index.d.ts +7 -2
  67. package/dist/version/index.js +152 -75
  68. package/eslint/base.js +23 -1
  69. package/eslint/rules/code-quality.js +3 -0
  70. package/eslint/rules/early-return-one-liner.js +98 -0
  71. package/eslint/rules/unicorn.js +16 -1
  72. package/package.json +7 -6
  73. package/pnpm-workspace.yaml +9 -4
  74. package/prettier/basic.d.ts +3 -1
  75. package/prettier/index.d.ts +3 -1
  76. package/prettier/static.d.ts +3 -1
  77. package/prompts/coding-standards.md +1 -1
  78. package/prompts/collaboration-workflow/file-edits.md +4 -4
  79. package/prompts/collaboration-workflow/glossary.md +1 -1
  80. package/prompts/collaboration-workflow/issue-template.md +1 -0
  81. package/prompts/collaboration-workflow/operating-rules.md +17 -9
  82. package/prompts/collaboration-workflow/overview.md +2 -2
  83. package/prompts/collaboration-workflow/principles.md +14 -12
  84. package/prompts/collaboration-workflow/report-format.md +1 -1
  85. package/prompts/collaboration-workflow/residency.md +1 -1
  86. package/prompts/collaboration-workflow/rule-delivery.md +6 -6
  87. package/prompts/collaboration-workflow/shell-body.md +6 -6
  88. package/prompts/collaboration-workflow/turn-batching.md +1 -1
  89. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
  90. package/prompts/collaboration-workflow/wip-cap.md +10 -10
  91. package/prompts/review-rubric.md +1 -2
  92. package/prompts/review.md +1 -1
  93. package/scripts/agent/agent-headless.ts +29 -0
  94. package/scripts/agent/agent-launch-environment.ts +6 -2
  95. package/scripts/agent/codex-agent-argv.ts +2 -1
  96. package/scripts/auto-ok/auto-ok-cli.ts +1 -1
  97. package/scripts/backlog/backlog-arrival.ts +3 -3
  98. package/scripts/backlog/backlog-budget.ts +1 -1
  99. package/scripts/backlog/backlog-drive-args.ts +135 -0
  100. package/scripts/backlog/backlog-drive-cli.ts +11 -137
  101. package/scripts/backlog/backlog-drive-launch.ts +39 -0
  102. package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
  103. package/scripts/backlog/backlog-drive-named.ts +3 -3
  104. package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
  105. package/scripts/backlog/backlog-drive-owner.ts +1 -1
  106. package/scripts/backlog/backlog-drive-restore.ts +1 -1
  107. package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
  108. package/scripts/backlog/backlog-drive.ts +1 -1
  109. package/scripts/backlog/backlog-next.ts +16 -5
  110. package/scripts/backlog/backlog-offer-cli.ts +6 -6
  111. package/scripts/backlog/backlog-offer.ts +1 -1
  112. package/scripts/backlog/backlog-plan-cli.ts +1 -1
  113. package/scripts/backlog/backlog-pool.ts +57 -11
  114. package/scripts/backlog/backlog-ready.ts +1 -1
  115. package/scripts/backlog/backlog-scope.ts +1 -1
  116. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  117. package/scripts/backlog/backlog-stalled.ts +1 -1
  118. package/scripts/build/build-codex-hooks.ts +3 -1
  119. package/scripts/build/build-commands.ts +66 -0
  120. package/scripts/build/build-hooks.ts +2 -1
  121. package/scripts/build/build-library.ts +12 -3
  122. package/scripts/build/build.ts +70 -0
  123. package/scripts/build/local-bin.ts +3 -13
  124. package/scripts/delegation/investigation-reads.ts +3 -1
  125. package/scripts/discovery/repo-origin.ts +5 -5
  126. package/scripts/doctor/doctor-consumer.ts +2 -2
  127. package/scripts/doctor/doctor-io.ts +2 -3
  128. package/scripts/document/capped-print-part.ts +30 -0
  129. package/scripts/document/capped-print.ts +3 -4
  130. package/scripts/document/document-byte-budget.ts +11 -2
  131. package/scripts/document/document-scan.ts +117 -0
  132. package/scripts/document/document-section.ts +143 -16
  133. package/scripts/document/entry-read-budget.ts +5 -3
  134. package/scripts/document/entry-read-set.ts +10 -89
  135. package/scripts/document/read-set-cost.ts +98 -0
  136. package/scripts/epic/epic-add-body.ts +1 -0
  137. package/scripts/epic/epic-audit-checks.ts +1 -1
  138. package/scripts/epic/epic-audit-rationale.ts +4 -3
  139. package/scripts/epic/epic-bundle-cli.ts +14 -3
  140. package/scripts/epic/epic-bundle-poll.ts +41 -0
  141. package/scripts/epic/epic-busy.ts +1 -1
  142. package/scripts/epic/epic-candidate-confirm.ts +3 -2
  143. package/scripts/epic/epic-classify.ts +1 -1
  144. package/scripts/epic/epic-cli-argv.ts +78 -46
  145. package/scripts/epic/epic-cli-remove.ts +11 -19
  146. package/scripts/epic/epic-cli.ts +74 -73
  147. package/scripts/epic/epic-close-comment.ts +1 -1
  148. package/scripts/epic/epic-close.ts +1 -1
  149. package/scripts/epic/epic-cross-repo.ts +5 -5
  150. package/scripts/epic/epic-graph.ts +1 -1
  151. package/scripts/epic/epic-index.ts +1 -1
  152. package/scripts/epic/epic-issue.ts +2 -6
  153. package/scripts/epic/epic-lane-offer.ts +2 -2
  154. package/scripts/epic/epic-next.ts +29 -42
  155. package/scripts/epic/epic-solo-stale.ts +1 -1
  156. package/scripts/epic/epic-triage.ts +8 -4
  157. package/scripts/epic/epic-validate.ts +1 -1
  158. package/scripts/followup/git-followup-finish.ts +2 -2
  159. package/scripts/followup/git-followup-label.ts +2 -2
  160. package/scripts/followup/git-followup-merged.ts +2 -2
  161. package/scripts/followup/git-pr-followup-checks.ts +142 -0
  162. package/scripts/followup/git-pr-followup.ts +4 -129
  163. package/scripts/gate/core-admission.ts +162 -0
  164. package/scripts/gate/core-budget.ts +190 -68
  165. package/scripts/gate/gate-plan.ts +80 -7
  166. package/scripts/gate/gate-report.ts +28 -9
  167. package/scripts/gate/gate-skip.ts +38 -7
  168. package/scripts/gate/hook-gate-reuse.ts +12 -10
  169. package/scripts/gate/machine-capacity.ts +175 -0
  170. package/scripts/gate/pre-commit-type-check.ts +4 -1
  171. package/scripts/gate/project-checks.ts +6 -9
  172. package/scripts/gate/type-check-step.ts +2 -3
  173. package/scripts/gate/verification-gate.ts +34 -10
  174. package/scripts/gh/git-gh-api-path.ts +2 -2
  175. package/scripts/gh/git-gh-issue-rest.ts +3 -3
  176. package/scripts/gh/git-gh-issue-write.ts +53 -7
  177. package/scripts/gh/git-gh-pr-read.ts +64 -19
  178. package/scripts/gh/git-gh-pr-rest.ts +1 -1
  179. package/scripts/gh/git-gh-pr.ts +5 -1
  180. package/scripts/gh/git-pr-ai-review.ts +1 -1
  181. package/scripts/gh/git-pr-checks-eval.ts +1 -3
  182. package/scripts/gh/git-pr-checks-parse.ts +1 -1
  183. package/scripts/gh/git-pr-checks-watch.ts +6 -5
  184. package/scripts/gh/git-pr-checks.ts +7 -5
  185. package/scripts/gh/git-pr-coderabbit.ts +1 -1
  186. package/scripts/gh/git-pr-error.ts +1 -3
  187. package/scripts/gh/git-pr.ts +9 -13
  188. package/scripts/gh/github-issue-url.ts +2 -2
  189. package/scripts/git/constants.ts +2 -6
  190. package/scripts/git/git-command.ts +20 -218
  191. package/scripts/git/git-diff-reads.ts +225 -0
  192. package/scripts/git/git-error.ts +4 -12
  193. package/scripts/git/git-pre-push-hook.ts +139 -0
  194. package/scripts/git/git-prompt.ts +2 -6
  195. package/scripts/git/git-spawn-sync.ts +3 -1
  196. package/scripts/git/git-spawn.ts +1 -1
  197. package/scripts/git/git-staging.ts +4 -6
  198. package/scripts/git/git-status.ts +3 -9
  199. package/scripts/hooks/codex-hook-adapter.ts +1 -1
  200. package/scripts/hooks/edited-cspell.ts +2 -4
  201. package/scripts/hooks/format-edited-file.ts +3 -12
  202. package/scripts/hooks/pre-push-unit.ts +31 -9
  203. package/scripts/hooks/reserved-run.ts +2 -2
  204. package/scripts/hooks/run-hook.sh +5 -4
  205. package/scripts/hooks/stop-guard.ts +5 -3
  206. package/scripts/init/init-actions.ts +0 -5
  207. package/scripts/init/init-ai-copy.ts +13 -2
  208. package/scripts/init/init-logic-json-merge.ts +19 -0
  209. package/scripts/init/init-logic-secretlint.ts +7 -7
  210. package/scripts/init/init-logic-workspace.ts +41 -2
  211. package/scripts/init/init-logic.ts +16 -26
  212. package/scripts/init/init.ts +10 -16
  213. package/scripts/init/initial-commit.ts +4 -4
  214. package/scripts/init/kit-development-versions.ts +40 -0
  215. package/scripts/init/kit-setup-state.ts +2 -2
  216. package/scripts/init/package-manager-pin.ts +35 -0
  217. package/scripts/init/start-exec.ts +68 -19
  218. package/scripts/init/start-setup-pr.ts +9 -9
  219. package/scripts/init/start-steps.ts +6 -10
  220. package/scripts/issue/defect-rate-cli.ts +1 -1
  221. package/scripts/issue/git-next-issues.ts +1 -1
  222. package/scripts/issue/issue-auto-ok.ts +32 -7
  223. package/scripts/issue/issue-backlinks-cli.ts +2 -2
  224. package/scripts/issue/issue-backlinks.ts +9 -9
  225. package/scripts/issue/issue-comment-cli.ts +19 -13
  226. package/scripts/issue/issue-file-cli.ts +72 -22
  227. package/scripts/issue/issue-file.ts +29 -1
  228. package/scripts/issue/issue-labels.ts +13 -0
  229. package/scripts/issue/issue-release-cli.ts +155 -0
  230. package/scripts/issue/issue-release.ts +58 -0
  231. package/scripts/issue/issue-scout-closed.ts +1 -1
  232. package/scripts/issue/issue-state-cli.ts +30 -74
  233. package/scripts/josh/josh-command-map.ts +2 -0
  234. package/scripts/josh/josh-command-types.ts +14 -0
  235. package/scripts/josh/josh-commands-ai.ts +4 -4
  236. package/scripts/josh/josh-commands-backlog.ts +4 -1
  237. package/scripts/josh/josh-commands-development.ts +18 -0
  238. package/scripts/josh/josh-commands-document.ts +1 -0
  239. package/scripts/josh/josh-commands-issue.ts +8 -1
  240. package/scripts/josh/josh-commands-lane.ts +24 -1
  241. package/scripts/josh/josh-commands-maintenance.ts +6 -6
  242. package/scripts/josh/josh-commands-run.ts +12 -7
  243. package/scripts/josh/josh-in-process.ts +28 -9
  244. package/scripts/josh/josh-logic.ts +34 -15
  245. package/scripts/josh/process-identity.ts +1 -3
  246. package/scripts/josh/session-language-cli.ts +3 -4
  247. package/scripts/lane/lane-await.ts +5 -1
  248. package/scripts/lane/lane-child-invocation.ts +2 -2
  249. package/scripts/lane/lane-cli.ts +12 -4
  250. package/scripts/lane/lane-dispatch.ts +14 -10
  251. package/scripts/lane/lane-environment.ts +5 -1
  252. package/scripts/lane/lane-handoff.ts +1 -1
  253. package/scripts/lane/lane-install.ts +2 -13
  254. package/scripts/lane/lane-launch-cli.ts +15 -1
  255. package/scripts/lane/lane-ledger.ts +145 -0
  256. package/scripts/lane/lane-leftover.ts +44 -30
  257. package/scripts/lane/lane-load.ts +108 -0
  258. package/scripts/lane/lane-measure-cli.ts +149 -0
  259. package/scripts/lane/lane-occupancy.ts +3 -0
  260. package/scripts/lane/lane-open.ts +5 -5
  261. package/scripts/lane/lane-paths.ts +13 -0
  262. package/scripts/lane/lane-reap.ts +1 -3
  263. package/scripts/lane/lane-registry.ts +11 -0
  264. package/scripts/lane/lane-relaunch.ts +1 -1
  265. package/scripts/lane/lane-stats.ts +224 -0
  266. package/scripts/lane/lane-stray.ts +140 -0
  267. package/scripts/lane/lane-vacant.ts +36 -0
  268. package/scripts/lane/openai-lane-ship.ts +2 -2
  269. package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
  270. package/scripts/lane/openai-lane-supervisor.ts +7 -165
  271. package/scripts/lane/openai-review-broker.ts +2 -4
  272. package/scripts/lib/ancestor-directories.ts +35 -0
  273. package/scripts/lib/animation-helpers.ts +2 -2
  274. package/scripts/lib/buffered-process.ts +3 -7
  275. package/scripts/lib/cli-flags.ts +71 -1
  276. package/scripts/lib/spawn-exit.ts +2 -2
  277. package/scripts/lib/timeouts.ts +35 -2
  278. package/scripts/lines/line-budget.ts +4 -6
  279. package/scripts/notify/telegram-test.ts +6 -0
  280. package/scripts/observations/observation-ledger-line.ts +6 -6
  281. package/scripts/observations/observation-ledger-migrate.ts +55 -4
  282. package/scripts/observations/observation-ledger.ts +26 -7
  283. package/scripts/overrides/lockfile-overrides.ts +1 -1
  284. package/scripts/overrides/overrides-logic.ts +1 -1
  285. package/scripts/propagate/propagate-git.ts +1 -1
  286. package/scripts/propagate/propagate-publish.ts +2 -2
  287. package/scripts/propagate/propagate-steps.ts +20 -15
  288. package/scripts/propagate/propagate.ts +36 -27
  289. package/scripts/refactor/import-graph.ts +18 -1
  290. package/scripts/refactor/refactor-lint.ts +4 -4
  291. package/scripts/release/github-release-environment.ts +1 -0
  292. package/scripts/release/github-release-runs.ts +115 -0
  293. package/scripts/release/github-release.ts +13 -67
  294. package/scripts/repo/repository-labels.ts +2 -2
  295. package/scripts/repo/required-checks-logic.ts +1 -3
  296. package/scripts/review/live-evidence.ts +2 -4
  297. package/scripts/review/review-brief-cli.ts +1 -1
  298. package/scripts/review/review-finding-ledger.ts +1 -1
  299. package/scripts/review/review-findings-cli.ts +1 -3
  300. package/scripts/rules/decision-oracle-batch.ts +128 -0
  301. package/scripts/rules/decision-oracle-tokens.ts +21 -0
  302. package/scripts/rules/decision-oracle.ts +8 -120
  303. package/scripts/rules/delivered-rules.ts +26 -182
  304. package/scripts/rules/direct-filing.ts +1 -1
  305. package/scripts/rules/early-heartbeat.ts +3 -3
  306. package/scripts/rules/git-argv.ts +1 -1
  307. package/scripts/rules/git-force.ts +1 -1
  308. package/scripts/rules/implementation-cut.ts +2 -2
  309. package/scripts/rules/index-guard.ts +1 -1
  310. package/scripts/rules/issue-comments.ts +118 -0
  311. package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
  312. package/scripts/rules/lane-background.ts +2 -2
  313. package/scripts/rules/lane-park.ts +18 -0
  314. package/scripts/rules/lane-split-park.ts +128 -0
  315. package/scripts/rules/oracle-firing.ts +1 -1
  316. package/scripts/rules/poll-loop.ts +1 -1
  317. package/scripts/rules/pre-gate-cut.ts +1 -1
  318. package/scripts/rules/protected-files.ts +59 -4
  319. package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
  320. package/scripts/rules/shell-body-trigger.ts +1 -1
  321. package/scripts/rules/stop-rules.ts +18 -7
  322. package/scripts/rules/test-declared-commit.ts +4 -4
  323. package/scripts/rules/wip-cap.ts +63 -0
  324. package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
  325. package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
  326. package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
  327. package/scripts/run/{run-carry.ts → carry/run-carry.ts} +1 -1
  328. package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
  329. package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +18 -5
  330. package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
  331. package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
  332. package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
  333. package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +1 -0
  334. package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
  335. package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
  336. package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +20 -2
  337. package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
  338. package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
  339. package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
  340. package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
  341. package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
  342. package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
  343. package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
  344. package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
  345. package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
  346. package/scripts/run/run-ending.ts +2 -2
  347. package/scripts/run/run-halfrun-resume.ts +6 -1
  348. package/scripts/run/run-headless.ts +1 -1
  349. package/scripts/run/run-invocation.ts +85 -84
  350. package/scripts/run/run-liveness.ts +1 -1
  351. package/scripts/run/run-parent-cut-hook.ts +1 -1
  352. package/scripts/run/run-preflight.ts +6 -6
  353. package/scripts/run/run-prep-cli.ts +1 -1
  354. package/scripts/run/run-prrun-resume.ts +1 -1
  355. package/scripts/run/run-report-cli.ts +4 -4
  356. package/scripts/run/run-report.ts +2 -2
  357. package/scripts/run/run-status-cli.ts +1 -1
  358. package/scripts/run/run-step-cli.ts +3 -3
  359. package/scripts/run/run-step.ts +7 -4
  360. package/scripts/run/run-stop-notify.ts +1 -1
  361. package/scripts/run/run-stranded-detect.ts +4 -4
  362. package/scripts/run/run-stranded.ts +2 -2
  363. package/scripts/run/run-watcher-guard-cli.ts +1 -1
  364. package/scripts/run/run-watcher-guard.ts +5 -3
  365. package/scripts/run/run-watcher-hook.ts +1 -1
  366. package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +2 -2
  367. package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
  368. package/scripts/run/ship/run-ship-lock.ts +50 -0
  369. package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
  370. package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
  371. package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +4 -3
  372. package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
  373. package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
  374. package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +11 -12
  375. package/scripts/run/ship/run-ship-stop-text.ts +13 -0
  376. package/scripts/run/{run-ship-sync.ts → ship/run-ship-sync.ts} +25 -11
  377. package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
  378. package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +25 -11
  379. package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +58 -7
  380. package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +59 -8
  381. package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
  382. package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +13 -15
  383. package/scripts/run/wake/run-wake-restart.ts +58 -0
  384. package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
  385. package/scripts/run/{run-wake.ts → wake/run-wake.ts} +16 -65
  386. package/scripts/safe-chain/preinstall-command.ts +20 -21
  387. package/scripts/safe-chain/project-config.ts +19 -15
  388. package/scripts/sync/sync-ai-files.ts +1 -1
  389. package/scripts/sync/sync-configs.ts +2 -1
  390. package/scripts/sync/sync.ts +15 -2
  391. package/scripts/test/unit-worker-share.ts +1 -0
  392. package/scripts/time-runtime/time-background.ts +5 -1
  393. package/scripts/time-runtime/time-instant.ts +3 -1
  394. package/scripts/time-runtime/time-spans.ts +6 -139
  395. package/scripts/time-runtime/time-tool-call.ts +160 -0
  396. package/scripts/version/effective-upstream.ts +2 -20
  397. package/scripts/version/latest-corepack.ts +124 -93
  398. package/scripts/version/latest-regression.ts +1 -1
  399. package/scripts/version/latest-update.ts +4 -4
  400. package/scripts/version/npm-registry.ts +20 -5
  401. package/scripts/version/publishable-range-check.ts +2 -2
  402. package/scripts/version/publishable-range.ts +1 -1
  403. package/scripts/version/release-age.ts +48 -38
  404. package/scripts/version/release-hold.ts +1 -1
  405. package/scripts/version/safe-chain-version-update.ts +40 -0
  406. package/scripts/version/upgrade-shell-command.ts +48 -8
  407. package/scripts/version/version-check-logic.ts +23 -14
  408. package/scripts/version/version-commands.ts +23 -11
  409. package/scripts/version/version-remote.ts +28 -13
  410. package/templates/pnpm-workspace.basic.yaml +4 -0
  411. package/templates/workflows/dependabot-auto-merge.yml +5 -5
  412. package/templates/workflows/github-release.yml +6 -0
  413. package/scripts/build/build-config-merge.ts +0 -27
  414. package/scripts/build/build-managed-marker.ts +0 -27
  415. package/scripts/build/build-self-sync-guard.ts +0 -27
  416. package/scripts/build/build-version.ts +0 -26
  417. package/scripts/version/preinstall-version-update.ts +0 -86
  418. /package/scripts/git/{schemas.ts → git-schemas.ts} +0 -0
  419. /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
  420. /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
  421. /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
  422. /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
  423. /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
  424. /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
  425. /package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +0 -0
  426. /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
  427. /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
  428. /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
  429. /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
  430. /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
  431. /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
  432. /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
  433. /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
  434. /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
  435. /package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +0 -0
  436. /package/scripts/run/{run-ship-pre-detach.ts → ship/run-ship-pre-detach.ts} +0 -0
  437. /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
  438. /package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +0 -0
  439. /package/scripts/run/{run-ship.ts → ship/run-ship.ts} +0 -0
  440. /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
  441. /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
  442. /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
@@ -1,3 +1,6 @@
1
+ import {
2
+ duplicate_read_outcome
3
+ } from "./chunk-QVSWU24O.js";
1
4
  import {
2
5
  ALREADY_DONE_LABEL,
3
6
  CONTEXT_CUT_THRESHOLD,
@@ -8,7 +11,6 @@ import {
8
11
  agent_role_profile,
9
12
  bash_triggers,
10
13
  cli_flags,
11
- error_text,
12
14
  filing_cap,
13
15
  git_command,
14
16
  git_common_directory,
@@ -31,35 +33,35 @@ import {
31
33
  run_cut_handoff,
32
34
  run_headless,
33
35
  run_hold,
34
- run_tail,
36
+ run_tail_rule,
35
37
  shell_segments,
36
38
  tail_commands
37
- } from "./chunk-GDS5N2KB.js";
39
+ } from "./chunk-KHX4U3BG.js";
38
40
  import {
41
+ ancestor_directories,
39
42
  find_local_bin_upwards
40
- } from "./chunk-3V37IID6.js";
43
+ } from "./chunk-JBBMAPC4.js";
44
+ import {
45
+ error_text
46
+ } from "./chunk-CDY4NFQG.js";
41
47
  import {
42
48
  batch_outcome
43
- } from "./chunk-VQLLNFNT.js";
49
+ } from "./chunk-F3ELQ2AF.js";
44
50
  import {
45
51
  investigation_refusal
46
- } from "./chunk-VESKRQOF.js";
47
- import {
48
- duplicate_read_outcome
49
- } from "./chunk-DO6NWWHU.js";
52
+ } from "./chunk-KRBERBBI.js";
50
53
  import {
51
54
  investigation_reads
52
- } from "./chunk-ZAL3NRGL.js";
55
+ } from "./chunk-XZYKEM6J.js";
53
56
  import {
54
57
  lane_guard_policy
55
- } from "./chunk-I4XSL4TT.js";
58
+ } from "./chunk-GPTFMK5E.js";
56
59
  import {
57
60
  hook_decision,
58
61
  lane_child_marker,
59
62
  run_issue_number
60
- } from "./chunk-32GQTL4F.js";
63
+ } from "./chunk-ZQGD4GBY.js";
61
64
  import {
62
- PLATFORM_TEMP_ROOT,
63
65
  cost_attribute,
64
66
  cost_blocks,
65
67
  cost_transcript,
@@ -68,13 +70,18 @@ import {
68
70
  json_value,
69
71
  lane_paths,
70
72
  open_issue_schema,
71
- stamp_file,
72
73
  time_batch_guard,
74
+ time_density_hook,
73
75
  time_shell
74
- } from "./chunk-S6X6WYKR.js";
76
+ } from "./chunk-WK2XSHSI.js";
77
+ import {
78
+ PLATFORM_TEMP_ROOT,
79
+ PROBE_TIMEOUT_MS,
80
+ stamp_file
81
+ } from "./chunk-ELCN5LCH.js";
75
82
 
76
83
  // scripts/rules/direct-filing.ts
77
- var DIRECT_FILING_REASON = '\u26D4 direct filing: an Issue is filed with `pnpm josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <route>] [--label <name>] [--repo <owner/repo>]`, never with `gh api \u2026/issues` or `gh issue create`. The command runs every filing step in order \u2014 the duplicate scout, the body lint and its classification labels, the depth and `route:` labels, the `## Origin` check when filing into another repository, and `epic:bundle` after the create \u2014 so none of them is a step to remember. When the scout prints candidates it holds the filing: read each, then reissue with `--distinct <N,\u2026>` for the ones that are separate deliverables. Reference: `docs/josh-commands-automation.md` \u2192 "`josh issue:file`". **This rule fires on every occurrence, not once per run.**';
84
+ var DIRECT_FILING_REASON = '\u26D4 direct filing: an Issue is filed with `pnpm josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <route>] [--label <name>] [--repo <owner/repo>]`, never with `gh api \u2026/issues` or `gh issue create`. The command runs every filing step in order \u2014 the duplicate scout, the body lint and its classification labels, the depth and `route:` labels, the `## Origin` check when filing into another repository, and `epic:bundle` after the create \u2014 so none of them is a step to remember. When the scout prints candidates it holds the filing: read each, then reissue with `--distinct <N,\u2026>` for the ones that are separate deliverables. Reference: `docs/josh-commands-backlog.md` \u2192 "`josh issue:file`". **This rule fires on every occurrence, not once per run.**';
78
85
  function is_any_filing(command) {
79
86
  return bash_triggers.is_direct_filing(command) || bash_triggers.is_issue_filing(command);
80
87
  }
@@ -88,7 +95,7 @@ var ROW = {
88
95
  };
89
96
  var direct_filing = { DIRECT_FILING_REASON, ROW };
90
97
 
91
- // scripts/run/run-progress.ts
98
+ // scripts/run/progress/run-progress.ts
92
99
  var DEFAULT_INTERVAL_MINUTES = 20;
93
100
  var INTERVAL_KEY = "JOSH_PROGRESS_INTERVAL_MINUTES";
94
101
  var MS_PER_MINUTE = 6e4;
@@ -207,7 +214,7 @@ var run_progress = {
207
214
  observation_key
208
215
  };
209
216
 
210
- // scripts/run/run-progress-clock.ts
217
+ // scripts/run/progress/run-progress-clock.ts
211
218
  import { z } from "zod";
212
219
  var PROGRESS_PREFIX = "josh-run-progress-";
213
220
  var LIFE_PREFIX = "josh-run-progress-life-";
@@ -333,24 +340,14 @@ var run_progress_clock = {
333
340
  stamp_target_of
334
341
  };
335
342
 
336
- // scripts/run/run-progress-config.ts
343
+ // scripts/run/progress/run-progress-config.ts
337
344
  import { readFileSync } from "node:fs";
338
345
  import path from "node:path";
339
346
  import { z as z2 } from "zod";
340
347
  var PACKAGE_FILE = "package.json";
341
- var MAX_DEPTH = 10;
342
348
  var package_schema = z2.object({
343
349
  josh: z2.object({ progress_interval_minutes: z2.number().positive() }).optional()
344
350
  });
345
- function ancestors_of(start) {
346
- const directories = [];
347
- let current = path.resolve(start);
348
- while (directories.length < MAX_DEPTH && !directories.includes(current)) {
349
- directories.push(current);
350
- current = path.dirname(current);
351
- }
352
- return directories;
353
- }
354
351
  function read_declared_minutes(directory) {
355
352
  try {
356
353
  const raw = readFileSync(path.join(directory, PACKAGE_FILE), "utf8");
@@ -361,7 +358,7 @@ function read_declared_minutes(directory) {
361
358
  }
362
359
  }
363
360
  function read_package_minutes(start) {
364
- for (const directory of ancestors_of(start)) {
361
+ for (const directory of ancestor_directories.list(start)) {
365
362
  const minutes = read_declared_minutes(directory);
366
363
  if (minutes !== void 0) return minutes;
367
364
  }
@@ -551,48 +548,6 @@ var file_body = {
551
548
  carries_a_file_body
552
549
  };
553
550
 
554
- // scripts/rules/gh-api.ts
555
- var GH_FLAGS = String.raw`(?:-{1,2}[\w-]+(?:[= ][^\s]+)?\s+)*`;
556
- var GH_API_COMMAND = new RegExp(String.raw`^gh\s+${GH_FLAGS}api\s`, "u");
557
- var API_METHOD = /(?:^|\s)(?:--method|-X)[= ]([A-Za-z]+)/u;
558
- var API_FIELD = /(?:^|\s)(?:--raw-field|--field|--input|-F|-f)(?:[= ]|$)/u;
559
- var READ_METHOD = "GET";
560
- var REPO_PATH = /repos\/([^/\s'"?]+)\/([^/\s'"?]+)/u;
561
- var OWNER_GROUP = 1;
562
- var REPO_GROUP = 2;
563
- var OWNER_PLACEHOLDERS = /* @__PURE__ */ new Set(["{owner}", ":owner"]);
564
- function is_gh_api(segment) {
565
- return GH_API_COMMAND.test(segment);
566
- }
567
- function method_of(segment) {
568
- return API_METHOD.exec(segment)?.[1]?.toUpperCase();
569
- }
570
- function is_read(segment) {
571
- const method = method_of(segment);
572
- if (method !== void 0) return method.toUpperCase() === READ_METHOD;
573
- return !API_FIELD.test(segment);
574
- }
575
- function is_write(segment) {
576
- return !is_read(segment);
577
- }
578
- function repo_target(segment) {
579
- const match = REPO_PATH.exec(segment);
580
- if (match === null) return void 0;
581
- return { owner: match[OWNER_GROUP] ?? "", repo: match[REPO_GROUP] ?? "" };
582
- }
583
- function is_owner_placeholder(owner) {
584
- return OWNER_PLACEHOLDERS.has(owner);
585
- }
586
- var gh_api = {
587
- GH_FLAGS,
588
- is_gh_api,
589
- is_owner_placeholder,
590
- is_read,
591
- is_write,
592
- method_of,
593
- repo_target
594
- };
595
-
596
551
  // scripts/rules/git-argv.ts
597
552
  var ENV_ASSIGNMENT = /^[A-Za-z_]\w*=/u;
598
553
  var WRAPPER_WORDS = /* @__PURE__ */ new Set([
@@ -1200,7 +1155,141 @@ var implementation_cut = {
1200
1155
  uncut_run_issue
1201
1156
  };
1202
1157
 
1203
- // scripts/rules/issue-fold.ts
1158
+ // scripts/rules/gh-api.ts
1159
+ var GH_FLAGS = String.raw`(?:-{1,2}[\w-]+(?:[= ][^\s]+)?\s+)*`;
1160
+ var GH_API_COMMAND = new RegExp(String.raw`^gh\s+${GH_FLAGS}api\s`, "u");
1161
+ var API_METHOD = /(?:^|\s)(?:--method|-X)[= ]([A-Za-z]+)/u;
1162
+ var API_FIELD = /(?:^|\s)(?:--raw-field|--field|--input|-F|-f)(?:[= ]|$)/u;
1163
+ var READ_METHOD = "GET";
1164
+ var REPO_PATH = /repos\/([^/\s'"?]+)\/([^/\s'"?]+)/u;
1165
+ var OWNER_GROUP = 1;
1166
+ var REPO_GROUP = 2;
1167
+ var OWNER_PLACEHOLDERS = /* @__PURE__ */ new Set(["{owner}", ":owner"]);
1168
+ function is_gh_api(segment) {
1169
+ return GH_API_COMMAND.test(segment);
1170
+ }
1171
+ function method_of(segment) {
1172
+ return API_METHOD.exec(segment)?.[1]?.toUpperCase();
1173
+ }
1174
+ function is_read(segment) {
1175
+ const method = method_of(segment);
1176
+ if (method !== void 0) return method.toUpperCase() === READ_METHOD;
1177
+ return !API_FIELD.test(segment);
1178
+ }
1179
+ function is_write(segment) {
1180
+ return !is_read(segment);
1181
+ }
1182
+ function repo_target(segment) {
1183
+ const match = REPO_PATH.exec(segment);
1184
+ if (match === null) return void 0;
1185
+ return { owner: match[OWNER_GROUP] ?? "", repo: match[REPO_GROUP] ?? "" };
1186
+ }
1187
+ function is_owner_placeholder(owner) {
1188
+ return OWNER_PLACEHOLDERS.has(owner);
1189
+ }
1190
+ var gh_api = {
1191
+ GH_FLAGS,
1192
+ is_gh_api,
1193
+ is_owner_placeholder,
1194
+ is_read,
1195
+ is_write,
1196
+ method_of,
1197
+ repo_target
1198
+ };
1199
+
1200
+ // scripts/rules/prior-comment-read.ts
1201
+ var ISSUE_PATH_NUMBER = /issues\/(\d+)/gu;
1202
+ var ISSUE_VIEW_NUMBER = /\bissue\s+view\s+(?:-{1,2}\S+\s+)*(\d+)/u;
1203
+ var ISSUE_READ_ARGUMENTS = /\bissue:read\b([\s\d]*)/u;
1204
+ var A_NUMBER = /^\d+$/u;
1205
+ var COMMENTS_ENDPOINT = /issues\/\d+\/comments\b/u;
1206
+ var VIEW_WITH_COMMENTS = /\bissue\s+view\b[^\n]*--comments\b/u;
1207
+ var ISSUE_READ_COMMAND = /\bissue:read\b/u;
1208
+ function add_issue_read_numbers(command, numbers) {
1209
+ const match = ISSUE_READ_ARGUMENTS.exec(command);
1210
+ if (match === null) return;
1211
+ const tokens = (match[1] ?? "").split(/\s+/u);
1212
+ for (const token of tokens) {
1213
+ if (A_NUMBER.test(token)) numbers.add(Number(token));
1214
+ }
1215
+ }
1216
+ function issue_numbers_of(command) {
1217
+ const numbers = /* @__PURE__ */ new Set();
1218
+ for (const match of command.matchAll(ISSUE_PATH_NUMBER)) numbers.add(Number(match[1]));
1219
+ const view = ISSUE_VIEW_NUMBER.exec(command);
1220
+ if (view !== null) numbers.add(Number(view[1]));
1221
+ add_issue_read_numbers(command, numbers);
1222
+ return numbers;
1223
+ }
1224
+ function fetches_comments(command) {
1225
+ return COMMENTS_ENDPOINT.test(command) || VIEW_WITH_COMMENTS.test(command) || ISSUE_READ_COMMAND.test(command);
1226
+ }
1227
+ function reads_target_comments(command, target) {
1228
+ if (!fetches_comments(command)) return false;
1229
+ for (const number of issue_numbers_of(command)) {
1230
+ if (target.has(number)) return true;
1231
+ }
1232
+ return false;
1233
+ }
1234
+ function already_read_comments_for(tail, command) {
1235
+ const target = issue_numbers_of(command);
1236
+ if (target.size === 0) return false;
1237
+ return tail_commands.prior_bash_commands(tail).some((prior) => reads_target_comments(prior, target));
1238
+ }
1239
+ function already_read_for_call(tail, call) {
1240
+ if (call.name !== cost_blocks.BASH_TOOL) return false;
1241
+ return already_read_comments_for(tail, time_shell.bash_command(call.input));
1242
+ }
1243
+ var prior_comment_read = {
1244
+ already_read_comments_for,
1245
+ already_read_for_call,
1246
+ fetches_comments,
1247
+ issue_numbers_of
1248
+ };
1249
+
1250
+ // scripts/rules/issue-comments.ts
1251
+ var ISSUE_VIEW_COMMAND = new RegExp(String.raw`^gh\s+${gh_api.GH_FLAGS}issue\s+view\s`, "u");
1252
+ var ISSUE_BODY_PATH = /repos\/[^\s'"]*\/issues\/\d+(?=$|["'\s])/u;
1253
+ var FETCHES_COMMENTS = /--comments\b|--json\s[\w,]*\bcomments\b|\/comments\b/u;
1254
+ var ISSUES_PATH = /repos\/[^\s'"]*\/issues\//u;
1255
+ var FIELD_PROJECTION = /(?:--jq|--json)[= ]\s*('[^']*'|"[^"]*"|\S+)/u;
1256
+ var NAMES_THE_BODY = /\bbody\b/u;
1257
+ function projects_away_body(segment) {
1258
+ const projection = FIELD_PROJECTION.exec(segment);
1259
+ return projection !== null && !NAMES_THE_BODY.test(projection[1] ?? "");
1260
+ }
1261
+ function is_body_read_segment(segment) {
1262
+ if (projects_away_body(segment)) return false;
1263
+ if (ISSUE_VIEW_COMMAND.test(segment)) return true;
1264
+ return gh_api.is_gh_api(segment) && ISSUE_BODY_PATH.test(segment) && gh_api.is_read(segment);
1265
+ }
1266
+ function fetches_issue_comments(segment) {
1267
+ if (!FETCHES_COMMENTS.test(segment)) return false;
1268
+ return ISSUE_VIEW_COMMAND.test(segment) || gh_api.is_gh_api(segment) && ISSUES_PATH.test(segment);
1269
+ }
1270
+ function is_body_only_issue_read(command) {
1271
+ const segments = shell_segments.segments_of(command);
1272
+ if (segments.some((segment) => fetches_issue_comments(segment))) return false;
1273
+ return segments.some((segment) => is_body_read_segment(segment));
1274
+ }
1275
+ function reads_issue_comments(command) {
1276
+ return shell_segments.segments_of(command).some((segment) => fetches_issue_comments(segment));
1277
+ }
1278
+ var ISSUE_COMMENTS_REASON = "\u26D4 an Issue's comments are part of the Issue: read them before implementing, not only the body. A decision recorded after the body was written lives only in a comment \u2014 a corrected diagnosis (joshuafolkken/kit#1537), a changed default and an added acceptance criterion (joshuafolkken/kit#1520), a scope handed to another Issue (joshuafolkken/kit#1304) \u2014 and nothing in the body says it was superseded, so a body-only reader builds the wrong thing and sees no contradiction. Reissue this read with the comments included: `gh api repos/{owner}/{repo}/issues/<N>/comments --jq '.[] | {user: .user.login, created_at, body}'` beside the body read, or `gh issue view <N> --comments` where GraphQL is reachable. Then the later text is the agreement in force \u2014 a comment supersedes the body it contradicts \u2014 except for two answers that are not the run's to make: work a comment reassigns to another Issue is out of scope and is not implemented, and a comment saying the Issue no longer has a reason to exist stops the run with a `confirmation` Telegram. The procedure is `.claude/skills/workflow-commands/issue-comments.md`. Every body-only read is refused until the comments are read.";
1279
+ var ROW5 = {
1280
+ id: "issue-comments",
1281
+ is_trigger: bash_triggers.on_bash_command(is_body_only_issue_read),
1282
+ reason: ISSUE_COMMENTS_REASON,
1283
+ already_satisfied: prior_comment_read.already_read_for_call,
1284
+ keeps: bash_triggers.on_bash_command(reads_issue_comments)
1285
+ };
1286
+ var issue_comments = {
1287
+ ISSUE_COMMENTS_REASON,
1288
+ ROW: ROW5,
1289
+ is_body_only_issue_read
1290
+ };
1291
+
1292
+ // scripts/rules/issue-fold-rule.ts
1204
1293
  var FOLD_NAMES = /* @__PURE__ */ new Set(["issue:fold"]);
1205
1294
  function runs_the_fold(command) {
1206
1295
  return shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, FOLD_NAMES));
@@ -1210,14 +1299,14 @@ function already_folded(tail, _call) {
1210
1299
  return tail_commands.prior_bash_commands(tail).some((command) => runs_the_fold(command));
1211
1300
  }
1212
1301
  var ISSUE_FOLD_REASON = '\u26D4 issue fold: this run has filed an Issue before and is filing another with no `pnpm josh issue:fold` between them. Several findings from one session fold into one Issue by default \u2014 the filing-time counterpart to the split assessment, whose two questions it reuses: are the findings separable, and does the whole clearly exceed one verification gate (the size half is `split:assess`, called not recomputed). Reissue after folding: `pnpm josh issue:fold "<title>" "<title>" \u2026` \u2014 `fold` means file one Issue covering them, `separate` means file them apart (separable findings whose combined size clears the split guide), and the default is `fold`. Add `--not-separable` when they are really one deliverable. The procedure is `.claude/skills/workflow-commands/split-assessment.md` \u2192 "The question". Every later filing is refused until the fold is on the transcript.';
1213
- var ROW5 = {
1302
+ var ROW6 = {
1214
1303
  id: "issue-fold",
1215
1304
  is_trigger: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
1216
1305
  reason: ISSUE_FOLD_REASON,
1217
1306
  already_satisfied: already_folded,
1218
1307
  keeps: bash_triggers.on_bash_command(runs_the_fold)
1219
1308
  };
1220
- var issue_fold = { ISSUE_FOLD_REASON, ROW: ROW5, already_folded, runs_the_fold };
1309
+ var issue_fold_rule = { ISSUE_FOLD_REASON, ROW: ROW6, already_folded, runs_the_fold };
1221
1310
 
1222
1311
  // scripts/rules/josh-git-bare.ts
1223
1312
  var GIT_COMMAND2 = /* @__PURE__ */ new Set(["git"]);
@@ -1233,7 +1322,7 @@ function is_bare_josh_git(command) {
1233
1322
  return shell_segments.segments_of(command).some((segment) => is_bare_segment(segment));
1234
1323
  }
1235
1324
  var JOSH_GIT_BARE_REASON = '\u26D4 bare `pnpm josh git`: this prompts to confirm the staging, and with no TTY the prompt falls to its cancel branch \u2014 `Checking unstaged files... Found` then `Operation cancelled.` \u2014 so the call throws away the time it takes to fail (about 31 seconds in one measured lane) and you reissue it with `-y` anyway (joshuafolkken/kit#2297). Run it unattended from the start: `pnpm josh git -y "<title> #<N>"`. The recovery form that opens the PR for a push that already landed is `pnpm josh git -y --skip-commit --skip-push`. This rule fires on every occurrence, not once per run.';
1236
- var ROW6 = {
1325
+ var ROW7 = {
1237
1326
  id: "josh-git-bare",
1238
1327
  is_trigger: bash_triggers.on_bash_command(is_bare_josh_git),
1239
1328
  reason: JOSH_GIT_BARE_REASON,
@@ -1241,7 +1330,7 @@ var ROW6 = {
1241
1330
  };
1242
1331
  var josh_git_bare = {
1243
1332
  JOSH_GIT_BARE_REASON,
1244
- ROW: ROW6,
1333
+ ROW: ROW7,
1245
1334
  is_bare_josh_git
1246
1335
  };
1247
1336
 
@@ -1255,7 +1344,7 @@ function is_carry_conflict(command, is_lane_child = lane_child_marker.is_child_o
1255
1344
  return is_lane_child;
1256
1345
  }
1257
1346
  var LANE_CARRY_CONFLICT_REASON = '\u26D4 lane child carry conflict: this session is a dispatched lane child, and `pnpm josh run:merge` / `pnpm josh run:carry` are the parent backlogrun budget commands \u2014 a child must never run them. The live carry record here belongs to the parent that launched this child, not to a competing run: the parent holds the one run budget for the repository, and every lane shares the common git directory it is keyed on, so a live owner a lane child reads is always its parent (joshuafolkken/kit#2267, measured 2026-09-21 in backlogrun #2252 \u2014 a child read run:merge `busy` as a competitor and parked #2258 unimplemented). Do not run this command, and do not treat it as a conflict: no `needs-decision`, no `confirmation` Telegram, no question to a person \u2014 a dispatched child cannot ask, and there is nothing to decide. Continue implementing this Issue; the parent owns the budget and counts the outcome at the return. The procedure is `.claude/skills/workflow-commands/pre-gate-cut.md` and `backlogrun-progress.md` \u2192 "The hand-off". This rule fires on every occurrence, not once per run.';
1258
- var ROW7 = {
1347
+ var ROW8 = {
1259
1348
  id: "lane-carry-conflict",
1260
1349
  is_trigger: bash_triggers.on_bash_command(is_carry_conflict),
1261
1350
  reason: LANE_CARRY_CONFLICT_REASON,
@@ -1263,7 +1352,7 @@ var ROW7 = {
1263
1352
  };
1264
1353
  var lane_carry_conflict = {
1265
1354
  LANE_CARRY_CONFLICT_REASON,
1266
- ROW: ROW7,
1355
+ ROW: ROW8,
1267
1356
  invokes_budget_command,
1268
1357
  is_carry_conflict
1269
1358
  };
@@ -1316,13 +1405,73 @@ function is_interactive_ask(call, is_lane_child = lane_child_marker.is_child_of(
1316
1405
  return is_lane_child;
1317
1406
  }
1318
1407
  var LANE_INTERACTIVE_ASK_REASON = '\u26D4 lane child interactive ask: this session is a dispatched lane child and cannot ask a person, so this call is refused. Left to run it would end the turn with nothing on the Issue \u2014 the parent then finds an OPEN Issue with `in-progress` and no question (joshuafolkken/kit#2201; joshuafolkken/kit#2034 placed the same rule one tool-call too late). Do not ask: park the decision instead. Apply `needs-decision` and post a comment carrying the question, the options and whether work was stashed \u2014 `gh api repos/{owner}/{repo}/issues/<N>/labels -f \'labels[]=needs-decision\'` then `pnpm josh issue:comment <N> --body-file <path>` \u2014 then send the `confirmation` Telegram and stop. The procedure is `.claude/skills/workflow-commands/pre-gate-cut.md` \u2192 "A lane child records its park before it stops", and the park itself is `backlogrun-park.md` \u2192 "park and continue". This rule fires on every occurrence, not once per run.';
1319
- var ROW8 = {
1408
+ var ROW9 = {
1320
1409
  id: "lane-interactive-ask",
1321
1410
  is_trigger: is_interactive_ask,
1322
1411
  reason: LANE_INTERACTIVE_ASK_REASON,
1323
1412
  decide: () => true
1324
1413
  };
1325
- var lane_interactive_ask = { LANE_INTERACTIVE_ASK_REASON, ROW: ROW8, is_interactive_ask };
1414
+ var lane_interactive_ask = { LANE_INTERACTIVE_ASK_REASON, ROW: ROW9, is_interactive_ask };
1415
+
1416
+ // scripts/rules/lane-split-park.ts
1417
+ var EPIC_COMMANDS = /* @__PURE__ */ new Set(["epic"]);
1418
+ var PROMOTED_ISSUE = /--promote[=\s]+["']?(?<issue>\d+)\b/u;
1419
+ var LABEL_TARGETS = [
1420
+ /\/issues\/(?<issue>\d+)\/labels\b/u,
1421
+ /\bissue\s+edit\s+["']?(?<issue>\d+)\b/u
1422
+ ];
1423
+ var ISSUE_URL = /--issue-url[=\s]+["']?([^\s"']+)/u;
1424
+ var URL_ISSUE = /\/issues\/(\d+)\b/u;
1425
+ function promoted_in(segment) {
1426
+ if (!shell_segments.is_josh_command(segment, EPIC_COMMANDS)) return void 0;
1427
+ return PROMOTED_ISSUE.exec(segment)?.groups?.["issue"];
1428
+ }
1429
+ function promoted_issues(commands) {
1430
+ const segments = commands.flatMap((command) => shell_segments.segments_of(command));
1431
+ const issues = segments.map((segment) => promoted_in(segment));
1432
+ return new Set(issues.filter((issue) => issue !== void 0));
1433
+ }
1434
+ function label_target(command) {
1435
+ for (const pattern of LABEL_TARGETS) {
1436
+ const issue = pattern.exec(command)?.groups?.["issue"];
1437
+ if (issue !== void 0) return issue;
1438
+ }
1439
+ return void 0;
1440
+ }
1441
+ function is_park_of(command, issue) {
1442
+ return lane_park.records_the_park(command) && label_target(command) === issue;
1443
+ }
1444
+ function notified_issue(command) {
1445
+ const [, url = ""] = ISSUE_URL.exec(command) ?? [];
1446
+ const [, issue] = URL_ISSUE.exec(url) ?? [];
1447
+ return issue;
1448
+ }
1449
+ function is_notify_of(command, issue) {
1450
+ return lane_park.is_confirmation_notify(command) && notified_issue(command) === issue;
1451
+ }
1452
+ function is_candidate(command) {
1453
+ const issue = lane_child_marker.marked_issue();
1454
+ if (issue === void 0) return false;
1455
+ if (!is_park_of(command, issue) && !is_notify_of(command, issue)) return false;
1456
+ return lane_child_marker.is_child_of(process.cwd());
1457
+ }
1458
+ function decide3(_call, run2) {
1459
+ const issue = lane_child_marker.marked_issue();
1460
+ const commands = tail_commands.prior_bash_commands(time_density_hook.read_tail(run2.transcript));
1461
+ return issue !== void 0 && promoted_issues(commands).has(issue);
1462
+ }
1463
+ var LANE_SPLIT_PARK_REASON = '\u26D4 lane split park: this session is a dispatched lane child that promoted its own Issue to an epic this run, and a split is not a park \u2014 do not apply `needs-decision` to the promoted epic and do not send a `confirmation` Telegram. The parent reads an open Issue carrying `epic` as a split and continues through its children; a question here has nobody to answer it (joshuafolkken/kit#3296, measured 2026-10-06 when the child for #3255 parked its promoted epic). If what remains of the original work needs a person, park that child issue instead, then end the turn. The procedure is `.claude/skills/workflow-commands/backlogrun-park.md` \u2192 "Splitting a child mid-run". This rule fires on every occurrence, not once per run.';
1464
+ var ROW10 = {
1465
+ id: "lane-split-park",
1466
+ is_trigger: bash_triggers.on_bash_command(is_candidate),
1467
+ reason: LANE_SPLIT_PARK_REASON,
1468
+ decide: decide3
1469
+ };
1470
+ var lane_split_park = {
1471
+ LANE_SPLIT_PARK_REASON,
1472
+ ROW: ROW10,
1473
+ promoted_issues
1474
+ };
1326
1475
 
1327
1476
  // scripts/rules/lane-switch-main.ts
1328
1477
  var SWITCH_SUBCOMMAND = "switch";
@@ -1353,7 +1502,7 @@ function is_switch_away_from_lane(command, is_lane_child = lane_child_marker.is_
1353
1502
  return switch_targets(command).some((target) => target !== own_branch);
1354
1503
  }
1355
1504
  var LANE_SWITCH_MAIN_REASON = "\u26D4 lane child on `git switch main`: this session is a dispatched lane child, and switching to the default branch here always fails \u2014 a lane is a linked work tree, the primary checkout holds the default branch, and git allows one branch in one work tree at a time (`fatal: main is already used by worktree at <the primary checkout>`, measured 11 times in the 2026-09-21 backlogrun). Do not switch: the lane is already on its own issue branch, branched from a fresh default before it opened, and the latest default is brought in during the gate by `pnpm josh main:merge`. So skip this step and keep implementing on the current branch \u2014 no `git switch`, no `git pull`. `pnpm josh main:sync` (`josh ms`) is refused in a lane for the same reason, and a lane is finished with `pnpm josh lane:close <issue-number>`. The parent runs `git switch main && git pull` in the primary checkout before each lane opens, never the child \u2014 `backlogrun-lanes.md` and `backlogrun-child.md` are the single sources. This rule fires on every occurrence, not once per run.";
1356
- var ROW9 = {
1505
+ var ROW11 = {
1357
1506
  id: "lane-switch-main",
1358
1507
  is_trigger: bash_triggers.on_bash_command(is_switch_away_from_lane),
1359
1508
  reason: LANE_SWITCH_MAIN_REASON,
@@ -1361,12 +1510,138 @@ var ROW9 = {
1361
1510
  };
1362
1511
  var lane_switch_main = {
1363
1512
  LANE_SWITCH_MAIN_REASON,
1364
- ROW: ROW9,
1513
+ ROW: ROW11,
1365
1514
  is_switch_away_from_lane,
1366
1515
  switch_target,
1367
1516
  switch_targets
1368
1517
  };
1369
1518
 
1519
+ // scripts/rules/decision-oracle-tokens.ts
1520
+ var oracle_tokens = {
1521
+ REQUIRED: "required",
1522
+ SKIP: "skip",
1523
+ STOP: "stop",
1524
+ WAIT: "wait",
1525
+ RETRY: "retry",
1526
+ OVER: "over",
1527
+ UNKNOWN: "unknown",
1528
+ NONE: "none",
1529
+ NOT_A_LANE: "not-a-lane",
1530
+ BUSY: "busy",
1531
+ HUMAN_REVIEW: "human-review",
1532
+ ISSUE_N_ARG: "<N>",
1533
+ EXCLUDE_ARG: "[--exclude <n>...]"
1534
+ };
1535
+
1536
+ // scripts/rules/decision-oracle-batch.ts
1537
+ var {
1538
+ BUSY,
1539
+ EXCLUDE_ARG,
1540
+ HUMAN_REVIEW,
1541
+ ISSUE_N_ARG,
1542
+ NONE,
1543
+ NOT_A_LANE,
1544
+ OVER,
1545
+ RETRY,
1546
+ STOP,
1547
+ UNKNOWN,
1548
+ WAIT
1549
+ } = oracle_tokens;
1550
+ var PRE_GATE_CUT_MD = ".claude/skills/workflow-commands/pre-gate-cut.md";
1551
+ var BACKLOGRUN_MD = ".claude/skills/workflow-commands/backlogrun.md";
1552
+ var RUN_MERGE_REFERENCE = "docs/josh-commands-run.md \u2192 `josh run:merge`";
1553
+ var EPIC_NEXT_REFERENCE = "docs/josh-commands-backlog.md \u2192 `josh epic:next`";
1554
+ var BACKLOG_BUDGET_REFERENCE = "docs/josh-commands-backlog.md \u2192 `josh backlog:budget`";
1555
+ var RUN_LIVENESS_REFERENCE = "docs/josh-commands-run.md \u2192 `josh run:liveness`";
1556
+ var AUTO_OK_NEXT_REFERENCE = "docs/josh-commands-backlog.md \u2192 `josh auto-ok:next`";
1557
+ var RUN_CUT_CMD = "run:cut";
1558
+ var BATCH_ORACLES = [
1559
+ {
1560
+ name: "run:cut:resume",
1561
+ command: RUN_CUT_CMD,
1562
+ decision: "Whether a lane child is resuming prior work or starting fresh",
1563
+ args: `--resume ${ISSUE_N_ARG}`,
1564
+ vocabulary: ["fresh", "resume", NOT_A_LANE],
1565
+ single_source: PRE_GATE_CUT_MD
1566
+ },
1567
+ {
1568
+ name: "run:cut:gate",
1569
+ command: RUN_CUT_CMD,
1570
+ decision: "Whether a lane child should take its pre-gate cut now",
1571
+ args: ISSUE_N_ARG,
1572
+ vocabulary: ["cut", NOT_A_LANE],
1573
+ single_source: PRE_GATE_CUT_MD
1574
+ },
1575
+ {
1576
+ name: "backlog:budget",
1577
+ decision: "Whether a backlogrun may start more work, keep watching, or finish",
1578
+ args: "",
1579
+ vocabulary: ["run", "watch", STOP],
1580
+ single_source: BACKLOG_BUDGET_REFERENCE
1581
+ },
1582
+ {
1583
+ name: "run:liveness",
1584
+ decision: "Whether a delegated unit is still working or has stopped without reporting",
1585
+ args: `${ISSUE_N_ARG} --output <path>`,
1586
+ vocabulary: ["alive", "stopped", "settled", "undetermined"],
1587
+ single_source: RUN_LIVENESS_REFERENCE
1588
+ },
1589
+ {
1590
+ name: "stash:pop",
1591
+ decision: "Whether a named stash entry exists and can be applied",
1592
+ args: "<message>",
1593
+ vocabulary: ["popped", "conflicted", "no-match", "ambiguous"],
1594
+ single_source: ".claude/skills/workflow-commands/prerequisite.md"
1595
+ },
1596
+ {
1597
+ name: "backlog:next",
1598
+ decision: "Which issue the backlog offers next, or whether to wait or stop",
1599
+ args: EXCLUDE_ARG,
1600
+ vocabulary: [WAIT, STOP, RETRY, "error", NONE],
1601
+ single_source: BACKLOGRUN_MD
1602
+ },
1603
+ {
1604
+ name: "auto-ok:next",
1605
+ decision: "The next opted-in issue outside any epic, or none",
1606
+ args: EXCLUDE_ARG,
1607
+ vocabulary: [NONE],
1608
+ single_source: AUTO_OK_NEXT_REFERENCE
1609
+ },
1610
+ {
1611
+ name: "epic:next",
1612
+ decision: "The next runnable child of an epic, or the epic's status",
1613
+ args: "<epic>",
1614
+ vocabulary: [WAIT, STOP, "complete"],
1615
+ single_source: EPIC_NEXT_REFERENCE
1616
+ },
1617
+ {
1618
+ name: "run:merge",
1619
+ decision: "The post-merge batch step: confirm child, advance, or report a stop condition",
1620
+ args: ISSUE_N_ARG,
1621
+ vocabulary: [OVER, HUMAN_REVIEW, STOP, RETRY, BUSY],
1622
+ single_source: RUN_MERGE_REFERENCE
1623
+ },
1624
+ {
1625
+ name: "run:step",
1626
+ decision: "The run\u2019s next single action, computed from the event stream, carry and issue state",
1627
+ args: ISSUE_N_ARG,
1628
+ // The non-command answers: `run:step` prints a runnable command for a phase with one to run, and
1629
+ // one of these verdicts otherwise. A command line is not a vocabulary token, exactly as an issue
1630
+ // number is not one for `backlog:next`.
1631
+ vocabulary: [
1632
+ "implement",
1633
+ HUMAN_REVIEW,
1634
+ "update-deps",
1635
+ "already-done",
1636
+ "keep-work",
1637
+ WAIT,
1638
+ STOP,
1639
+ UNKNOWN
1640
+ ],
1641
+ single_source: BACKLOGRUN_MD
1642
+ }
1643
+ ];
1644
+
1370
1645
  // scripts/run/run-stage.ts
1371
1646
  var KICKOFF = "kickoff";
1372
1647
  var HALFRUN = "halfrun";
@@ -1409,7 +1684,7 @@ function state_of(facts) {
1409
1684
  if (facts.is_halfrun_stopped) return HALFRUN_STOPPED;
1410
1685
  return facts.is_planned ? PLANNED : FRESH;
1411
1686
  }
1412
- function decide3(state, command) {
1687
+ function decide4(state, command) {
1413
1688
  const is_reached = STATES.indexOf(state) > COMMANDS.indexOf(command);
1414
1689
  const start = is_reached ? REACHED : STARTS[state];
1415
1690
  return { state, command, start, end: ENDS[command], is_reached };
@@ -1426,7 +1701,7 @@ function format_decision(issue, decision) {
1426
1701
  return `stage #${issue} \u2014 ${fields.join(" \xB7 ")}`;
1427
1702
  }
1428
1703
  function cell(state, command) {
1429
- const decision = decide3(state, command);
1704
+ const decision = decide4(state, command);
1430
1705
  return decision.is_reached ? REACHED_CELL : `${decision.start}${CELL_ARROW}${decision.end}`;
1431
1706
  }
1432
1707
  function table_rows() {
@@ -1451,7 +1726,7 @@ var run_stage = {
1451
1726
  REACHED,
1452
1727
  START_TOKENS,
1453
1728
  STATES,
1454
- decide: decide3,
1729
+ decide: decide4,
1455
1730
  format_decision,
1456
1731
  next_commands,
1457
1732
  next_line,
@@ -1470,35 +1745,15 @@ var RUN_ENTRY_ORACLE = {
1470
1745
  };
1471
1746
 
1472
1747
  // scripts/rules/decision-oracle.ts
1748
+ var { BUSY: BUSY2, ISSUE_N_ARG: ISSUE_N_ARG2, NONE: NONE2, OVER: OVER2, REQUIRED, SKIP, UNKNOWN: UNKNOWN2 } = oracle_tokens;
1473
1749
  var CHAIN_RULE_MD = ".claude/skills/workflow-commands/chain-rule.md";
1474
1750
  var BACKLOGRUN_PROGRESS_MD = ".claude/skills/workflow-commands/backlogrun-progress.md";
1475
1751
  var SKILL_2E = ".claude/skills/workflow-commands/issue-scout.md";
1476
1752
  var SPLIT_ASSESSMENT_QUESTION = ".claude/skills/workflow-commands/split-assessment.md \u2192 The question";
1477
- var PRE_GATE_CUT_MD = ".claude/skills/workflow-commands/pre-gate-cut.md";
1478
- var BACKLOGRUN_MD = ".claude/skills/workflow-commands/backlogrun.md";
1479
- var RUN_MERGE_REFERENCE = "docs/josh-commands-automation.md \u2192 `josh run:merge`";
1480
- var EPIC_NEXT_REFERENCE = "docs/josh-commands-automation.md \u2192 `josh epic:next`";
1481
- var BACKLOG_BUDGET_REFERENCE = "docs/josh-commands-automation.md \u2192 `josh backlog:budget`";
1482
- var RUN_LIVENESS_REFERENCE = "docs/josh-commands-automation.md \u2192 `josh run:liveness`";
1483
- var AUTO_OK_NEXT_REFERENCE = "docs/josh-commands-automation.md \u2192 `josh auto-ok:next`";
1484
- var RUN_CUT_CMD = "run:cut";
1485
- var REQUIRED = "required";
1486
- var SKIP = "skip";
1487
- var STOP = "stop";
1488
- var WAIT = "wait";
1489
- var RETRY = "retry";
1490
- var OVER = "over";
1491
- var UNKNOWN = "unknown";
1492
- var NONE = "none";
1493
- var NOT_A_LANE = "not-a-lane";
1494
- var BUSY = "busy";
1495
- var HUMAN_REVIEW = "human-review";
1496
1753
  var LIVE = "live";
1497
1754
  var STOPPED = "stopped";
1498
1755
  var RECONCILED = "reconciled";
1499
1756
  var NOTHING_TO_RECONCILE = "nothing to reconcile";
1500
- var ISSUE_N_ARG = "<N>";
1501
- var EXCLUDE_ARG = "[--exclude <n>...]";
1502
1757
  var DECISION_ORACLES = [
1503
1758
  {
1504
1759
  name: "delegate",
@@ -1546,8 +1801,8 @@ var DECISION_ORACLES = [
1546
1801
  {
1547
1802
  name: "run:hold",
1548
1803
  decision: "Whether this working tree is free for a run to claim",
1549
- args: `[${ISSUE_N_ARG}]`,
1550
- vocabulary: ["hold", BUSY, UNKNOWN],
1804
+ args: `[${ISSUE_N_ARG2}]`,
1805
+ vocabulary: ["hold", BUSY2, UNKNOWN2],
1551
1806
  single_source: ".claude/skills/workflow-commands/working-tree-hold.md"
1552
1807
  },
1553
1808
  {
@@ -1555,20 +1810,20 @@ var DECISION_ORACLES = [
1555
1810
  command: "cost",
1556
1811
  decision: "Whether the session cost exceeds the hand-off threshold",
1557
1812
  args: "--cut",
1558
- vocabulary: [OVER, "under"],
1813
+ vocabulary: [OVER2, "under"],
1559
1814
  single_source: BACKLOGRUN_PROGRESS_MD
1560
1815
  },
1561
1816
  {
1562
1817
  name: "release:scope",
1563
1818
  decision: "Whether a release is owed after this merge",
1564
1819
  args: "",
1565
- vocabulary: [REQUIRED, SKIP, UNKNOWN],
1820
+ vocabulary: [REQUIRED, SKIP, UNKNOWN2],
1566
1821
  single_source: ".claude/skills/workflow-commands/followup.md"
1567
1822
  },
1568
1823
  {
1569
1824
  name: "epic:bundle",
1570
1825
  decision: "Which epic a newly filed issue belongs to",
1571
- args: ISSUE_N_ARG,
1826
+ args: ISSUE_N_ARG2,
1572
1827
  vocabulary: ["add_to_epic", "create_epic", "ask", "nothing_to_bundle"],
1573
1828
  single_source: SKILL_2E
1574
1829
  },
@@ -1604,100 +1859,17 @@ var DECISION_ORACLES = [
1604
1859
  name: "cases",
1605
1860
  decision: "Each boundary a change crosses (network, process, fs, time) and the abnormal cases it owes",
1606
1861
  args: "<path...>",
1607
- vocabulary: ["network", "process", "fs", "time", NONE],
1862
+ vocabulary: ["network", "process", "fs", "time", NONE2],
1608
1863
  single_source: "prompts/collaboration-workflow/report-format.md \u2192 \u5909\u66F4\u3068\u30C6\u30B9\u30C8"
1609
1864
  },
1610
1865
  {
1611
1866
  name: "issue:state",
1612
1867
  decision: "An issue's state, labels, and human-review flag",
1613
- args: ISSUE_N_ARG,
1868
+ args: ISSUE_N_ARG2,
1614
1869
  vocabulary: ["human_review: yes", "human_review: no"],
1615
1870
  single_source: ".claude/skills/workflow-commands/needs-human-review.md"
1616
1871
  },
1617
- {
1618
- name: "run:cut:resume",
1619
- command: RUN_CUT_CMD,
1620
- decision: "Whether a lane child is resuming prior work or starting fresh",
1621
- args: `--resume ${ISSUE_N_ARG}`,
1622
- vocabulary: ["fresh", "resume", NOT_A_LANE],
1623
- single_source: PRE_GATE_CUT_MD
1624
- },
1625
- {
1626
- name: "run:cut:gate",
1627
- command: RUN_CUT_CMD,
1628
- decision: "Whether a lane child should take its pre-gate cut now",
1629
- args: ISSUE_N_ARG,
1630
- vocabulary: ["cut", NOT_A_LANE],
1631
- single_source: PRE_GATE_CUT_MD
1632
- },
1633
- {
1634
- name: "backlog:budget",
1635
- decision: "Whether a backlogrun may start more work, keep watching, or finish",
1636
- args: "",
1637
- vocabulary: ["run", "watch", STOP],
1638
- single_source: BACKLOG_BUDGET_REFERENCE
1639
- },
1640
- {
1641
- name: "run:liveness",
1642
- decision: "Whether a delegated unit is still working or has stopped without reporting",
1643
- args: `${ISSUE_N_ARG} --output <path>`,
1644
- vocabulary: ["alive", "stopped", "settled", "undetermined"],
1645
- single_source: RUN_LIVENESS_REFERENCE
1646
- },
1647
- {
1648
- name: "stash:pop",
1649
- decision: "Whether a named stash entry exists and can be applied",
1650
- args: "<message>",
1651
- vocabulary: ["popped", "conflicted", "no-match", "ambiguous"],
1652
- single_source: ".claude/skills/workflow-commands/prerequisite.md"
1653
- },
1654
- {
1655
- name: "backlog:next",
1656
- decision: "Which issue the backlog offers next, or whether to wait or stop",
1657
- args: EXCLUDE_ARG,
1658
- vocabulary: [WAIT, STOP, RETRY, "error", NONE],
1659
- single_source: BACKLOGRUN_MD
1660
- },
1661
- {
1662
- name: "auto-ok:next",
1663
- decision: "The next opted-in issue outside any epic, or none",
1664
- args: EXCLUDE_ARG,
1665
- vocabulary: [NONE],
1666
- single_source: AUTO_OK_NEXT_REFERENCE
1667
- },
1668
- {
1669
- name: "epic:next",
1670
- decision: "The next runnable child of an epic, or the epic's status",
1671
- args: "<epic>",
1672
- vocabulary: [WAIT, STOP, "complete"],
1673
- single_source: EPIC_NEXT_REFERENCE
1674
- },
1675
- {
1676
- name: "run:merge",
1677
- decision: "The post-merge batch step: confirm child, advance, or report a stop condition",
1678
- args: ISSUE_N_ARG,
1679
- vocabulary: [OVER, HUMAN_REVIEW, STOP, RETRY, BUSY],
1680
- single_source: RUN_MERGE_REFERENCE
1681
- },
1682
- {
1683
- name: "run:step",
1684
- decision: "The run\u2019s next single action, computed from the event stream, carry and issue state",
1685
- args: ISSUE_N_ARG,
1686
- // The non-command answers: `run:step` prints a runnable command for a phase with one to run, and
1687
- // one of these verdicts otherwise. A command line is not a vocabulary token, exactly as an issue
1688
- // number is not one for `backlog:next`.
1689
- vocabulary: [
1690
- "implement",
1691
- HUMAN_REVIEW,
1692
- "update-deps",
1693
- "already-done",
1694
- "keep-work",
1695
- WAIT,
1696
- STOP,
1697
- UNKNOWN
1698
- ],
1699
- single_source: BACKLOGRUN_MD
1700
- },
1872
+ ...BATCH_ORACLES,
1701
1873
  {
1702
1874
  name: "refactor:scan",
1703
1875
  decision: "Whether the refactoring scope still holds high- or medium-priority candidates",
@@ -1724,7 +1896,7 @@ var DECISION_ORACLES = [
1724
1896
  decision: "Whether cross-file or cross-repository code duplication exists",
1725
1897
  args: "",
1726
1898
  vocabulary: ["clean", "clones:"],
1727
- single_source: "prompts/collaboration-workflow/principles.md \u2192 \u30AF\u30ED\u30FC\u30F3\u7981\u6B62"
1899
+ single_source: "prompts/collaboration-workflow/principles.md \u2192 no-clones"
1728
1900
  },
1729
1901
  {
1730
1902
  name: "epic:reconcile",
@@ -1732,14 +1904,14 @@ var DECISION_ORACLES = [
1732
1904
  decision: "Whether an epic's declaration matches its recorded relations, and the repair when it does not",
1733
1905
  args: "--reconcile <E>",
1734
1906
  vocabulary: [RECONCILED, NOTHING_TO_RECONCILE],
1735
- single_source: "docs/josh-commands-automation.md \u2192 `josh epic --reconcile`"
1907
+ single_source: "docs/josh-commands-backlog.md \u2192 `josh epic --reconcile`"
1736
1908
  },
1737
1909
  {
1738
1910
  name: "lane:list",
1739
1911
  decision: "Whether each in-progress issue holds a live lane, and the two-directional difference",
1740
1912
  args: "",
1741
- vocabulary: [LIVE, STOPPED, UNKNOWN],
1742
- single_source: "docs/josh-commands-automation.md \u2192 The `in-progress` / lane difference"
1913
+ vocabulary: [LIVE, STOPPED, UNKNOWN2],
1914
+ single_source: "docs/josh-commands-run.md \u2192 The `in-progress` / lane difference"
1743
1915
  },
1744
1916
  RUN_ENTRY_ORACLE
1745
1917
  ];
@@ -1821,7 +1993,7 @@ function declaration_for(name) {
1821
1993
  function firing_oracles() {
1822
1994
  return decision_oracle.DECISION_ORACLES.flatMap((oracle) => {
1823
1995
  const firing_point = FIRING.get(oracle.name);
1824
- return firing_point === void 0 ? [] : [{ oracle, firing_point }];
1996
+ return firing_point === void 0 ? [] : { oracle, firing_point };
1825
1997
  });
1826
1998
  }
1827
1999
  var oracle_firing = {
@@ -1911,13 +2083,13 @@ function is_destructive_command(command) {
1911
2083
  return shell_segments.segments_of(command).some((segment) => is_destructive_segment2(segment));
1912
2084
  }
1913
2085
  var DESTRUCTIVE_COMMAND_REASON = "\u26D4 destructive command: a recursive forced `rm` (`rm -rf`, `rm -fr`, `rm -r -f`, `rm --recursive --force`), `gh repo delete` / `archive`, `gh repo edit --visibility`, `gh pr close`, or a `gh api -X DELETE repos/\u2026` call deletes or closes something that cannot be restored, which is Tier C and needs explicit current-turn user instruction (`prompts/collaboration-workflow/operating-rules.md`). The `.claude/settings.json` deny list matches prefix globs, so it misses these spellings; this row reads the argv instead. Remove a file or directory one path at a time without `-f`, or ask the user to run the command in their own terminal. `gh issue close` and removing a label from an Issue (`DELETE repos/<o>/<r>/issues/<N>/labels/<name>`) are not refused. **This rule fires on every occurrence, not once per run.**";
1914
- var ROW10 = {
2086
+ var ROW12 = {
1915
2087
  id: "destructive-command",
1916
2088
  is_trigger: bash_triggers.on_bash_command(is_destructive_command),
1917
2089
  reason: DESTRUCTIVE_COMMAND_REASON,
1918
2090
  decide: () => true
1919
2091
  };
1920
- var destructive_command = { DESTRUCTIVE_COMMAND_REASON, ROW: ROW10, is_destructive_command };
2092
+ var destructive_command = { DESTRUCTIVE_COMMAND_REASON, ROW: ROW12, is_destructive_command };
1921
2093
 
1922
2094
  // scripts/rules/direct-pr-create.ts
1923
2095
  var GH_COMMAND2 = "gh";
@@ -1972,13 +2144,13 @@ function is_direct_pr_create(command) {
1972
2144
  return shell_segments.segments_of(command).some((segment) => is_pr_create_segment(segment));
1973
2145
  }
1974
2146
  var DIRECT_PR_CREATE_REASON = "\u26D4 direct PR creation: `gh pr create` (or a `gh api` POST to `repos/<o>/<r>/pulls`) bypasses `build_body`, which writes the `closes #N` line \u2014 the Issue would stay open after the merge (`CLAUDE.md` \u2192 Git Rules). Open the PR with `pnpm josh pr` instead; after a failed push, fix, push, then run `pnpm josh pr`. **This rule fires on every occurrence, not once per run.**";
1975
- var ROW11 = {
2147
+ var ROW13 = {
1976
2148
  id: "direct-pr-create",
1977
2149
  is_trigger: bash_triggers.on_bash_command(is_direct_pr_create),
1978
2150
  reason: DIRECT_PR_CREATE_REASON,
1979
2151
  decide: () => true
1980
2152
  };
1981
- var direct_pr_create = { DIRECT_PR_CREATE_REASON, ROW: ROW11, is_direct_pr_create };
2153
+ var direct_pr_create = { DIRECT_PR_CREATE_REASON, ROW: ROW13, is_direct_pr_create };
1982
2154
 
1983
2155
  // scripts/rules/worktree-guard.ts
1984
2156
  var CHECKOUT_SUBCOMMAND = "checkout";
@@ -2040,14 +2212,14 @@ function is_unauthorized_worktree_change(command) {
2040
2212
  return shell_segments.segments_of(command).some((segment) => is_unauthorized_segment(segment));
2041
2213
  }
2042
2214
  var WORKTREE_MUTATION_REASON = '\u26D4 unauthorized working-tree change: `git checkout -- <path>`, `git restore <path>`, a forced `git clean` and an unauthorized `git stash` discard work or mutate the repository-wide stash every work tree shares, and none is on the `.claude/settings.json` deny list (`operating-rules.md`). Do not run them on your own judgement. A commit goes through `pnpm josh git`; a stash pop goes through `pnpm josh stash:pop "<message>"`, never a positional `git stash pop` a shared stack lets another lane divert. A stash push is authorized only inside a documented flow and only with a findable message \u2014 `git stash push -u -m "<message>"` \u2014 which this row allows; a bare `git stash`, a message-less push, or `pop` / `apply` / `drop` is refused. **This rule fires on every occurrence, not once per run.**';
2043
- var ROW12 = {
2215
+ var ROW14 = {
2044
2216
  id: "worktree-mutation",
2045
2217
  is_trigger: bash_triggers.on_bash_command(is_unauthorized_worktree_change),
2046
2218
  reason: WORKTREE_MUTATION_REASON,
2047
2219
  decide: () => true
2048
2220
  };
2049
2221
  var worktree_guard = {
2050
- ROW: ROW12,
2222
+ ROW: ROW14,
2051
2223
  WORKTREE_MUTATION_REASON,
2052
2224
  is_unauthorized_worktree_change,
2053
2225
  touches_worktree
@@ -2075,14 +2247,14 @@ function is_index_segment(segment) {
2075
2247
  function is_index_mutation(command) {
2076
2248
  return shell_segments.segments_of(command).some((segment) => is_index_segment(segment));
2077
2249
  }
2078
- var INDEX_MUTATION_REASON = '\u26D4 git index mutation: `git add` / `stage` / `commit` / `reset` / `rm` / `mv` and `git restore --staged` overwrite the user\'s staged snapshot, which `CLAUDE.md` \u2192 Git Rules forbids on your own judgement (`prompts/collaboration-workflow/operating-rules.md` \u2192 "git index \u3092\u52DD\u624B\u306B\u5909\u66F4\u3057\u306A\u3044"). The `.claude/settings.json` deny list matches prefix globs, so it misses `git -C . commit`, `git -c k=v commit` and `env git commit`; this row reads the argv instead, so the spelling does not matter. A commit goes through `pnpm josh git -y`; any other staging needs explicit current-turn user instruction, run by the user in their own terminal. **This rule fires on every occurrence, not once per run.**';
2079
- var ROW13 = {
2250
+ var INDEX_MUTATION_REASON = '\u26D4 git index mutation: `git add` / `stage` / `commit` / `reset` / `rm` / `mv` and `git restore --staged` overwrite the user\'s staged snapshot, which `CLAUDE.md` \u2192 Git Rules forbids on your own judgement (`prompts/collaboration-workflow/operating-rules.md` \u2192 "no-self-staging"). The `.claude/settings.json` deny list matches prefix globs, so it misses `git -C . commit`, `git -c k=v commit` and `env git commit`; this row reads the argv instead, so the spelling does not matter. A commit goes through `pnpm josh git -y`; any other staging needs explicit current-turn user instruction, run by the user in their own terminal. **This rule fires on every occurrence, not once per run.**';
2251
+ var ROW15 = {
2080
2252
  id: "index-mutation",
2081
2253
  is_trigger: bash_triggers.on_bash_command(is_index_mutation),
2082
2254
  reason: INDEX_MUTATION_REASON,
2083
2255
  decide: () => true
2084
2256
  };
2085
- var index_guard = { INDEX_MUTATION_REASON, ROW: ROW13, is_index_mutation };
2257
+ var index_guard = { INDEX_MUTATION_REASON, ROW: ROW15, is_index_mutation };
2086
2258
 
2087
2259
  // scripts/rules/protected-files.ts
2088
2260
  import path5 from "node:path";
@@ -2102,7 +2274,6 @@ var CLAUDE_MD = "CLAUDE.md";
2102
2274
  var PLUGIN_ID = "kit@kit";
2103
2275
  var CLAUDE_MD_POINTER = `@${NODE_MODULES}/${KIT_PACKAGE_NAME}/dist/${CLAUDE_MD}`;
2104
2276
  var SECRETLINT_BIN = path4.join(NODE_MODULES, ".bin", "secretlint");
2105
- var GIT_TIMEOUT_MS = 2e3;
2106
2277
  var manifest_schema = z6.object({
2107
2278
  name: z6.string().optional(),
2108
2279
  dependencies: z6.record(z6.string(), z6.string()).optional(),
@@ -2137,7 +2308,7 @@ function is_plugin_declared(root) {
2137
2308
  }
2138
2309
  function configured_hooks_path(root) {
2139
2310
  const value = git_spawn_sync.read(["-C", root, "config", "--get", "core.hooksPath"], {
2140
- timeout: GIT_TIMEOUT_MS
2311
+ timeout: PROBE_TIMEOUT_MS
2141
2312
  });
2142
2313
  return value === "" ? void 0 : value;
2143
2314
  }
@@ -2213,13 +2384,51 @@ var READ_TOOL = "Read";
2213
2384
  var EDIT_TOOLS2 = /* @__PURE__ */ new Set(["Edit", "Write"]);
2214
2385
  var ENVIRONMENT_FILE = ".env";
2215
2386
  var SETTINGS_FILE = path5.join(".claude", "settings.json");
2387
+ var WHITESPACE3 = /\s+/u;
2388
+ var TOKEN_QUOTES = /["']/gu;
2389
+ var INPUT_REDIRECT = "<";
2390
+ var SHELL_READERS = /* @__PURE__ */ new Set([
2391
+ ".",
2392
+ "awk",
2393
+ "bat",
2394
+ "cat",
2395
+ "cut",
2396
+ "grep",
2397
+ "head",
2398
+ "less",
2399
+ "more",
2400
+ "nl",
2401
+ "od",
2402
+ "rg",
2403
+ "sed",
2404
+ "sort",
2405
+ "source",
2406
+ "strings",
2407
+ "tail",
2408
+ "xxd"
2409
+ ]);
2216
2410
  function file_path_of(call) {
2217
2411
  if (!json_value.is_record(call.input)) return "";
2218
2412
  const value = call.input["file_path"];
2219
2413
  return typeof value === "string" ? value : "";
2220
2414
  }
2415
+ function is_environment_path(token) {
2416
+ return path5.basename(token.replaceAll(TOKEN_QUOTES, "")) === ENVIRONMENT_FILE;
2417
+ }
2418
+ function is_environment_read_segment(segment) {
2419
+ const words = segment.replaceAll(INPUT_REDIRECT, " < ").split(WHITESPACE3).filter((word) => word !== "");
2420
+ const is_redirected = words.some(
2421
+ (word, index) => word === INPUT_REDIRECT && is_environment_path(words[index + 1] ?? "")
2422
+ );
2423
+ const [command = "", ...rest] = words;
2424
+ return is_redirected || SHELL_READERS.has(command) && rest.some((word) => is_environment_path(word));
2425
+ }
2426
+ function is_environment_shell_read(command) {
2427
+ return shell_segments.segments_of(command).some((segment) => is_environment_read_segment(segment));
2428
+ }
2221
2429
  function is_environment_read(call) {
2222
- return call.name === READ_TOOL && path5.basename(file_path_of(call)) === ENVIRONMENT_FILE;
2430
+ if (call.name === READ_TOOL) return is_environment_path(file_path_of(call));
2431
+ return bash_triggers.on_bash_command(is_environment_shell_read)(call);
2223
2432
  }
2224
2433
  function is_consumer_settings_edit(call, is_kit_consumer2) {
2225
2434
  if (!EDIT_TOOLS2.has(call.name)) return false;
@@ -2230,14 +2439,14 @@ function is_consumer_settings_edit(call, is_kit_consumer2) {
2230
2439
  function is_protected_file_call(call, is_kit_consumer2 = doctor_consumer.is_kit_consumer) {
2231
2440
  return is_environment_read(call) || is_consumer_settings_edit(call, is_kit_consumer2);
2232
2441
  }
2233
- var PROTECTED_FILE_REASON = '\u26D4 protected file: `.env` holds the Telegram bot token, so reading it puts a secret in the transcript \u2014 the scripts that need it load it in their own process. `.claude/settings.json` in a consumer repository is the deny list and the hooks every other rule rests on, so a run must not rewrite it; `josh sync` owns it, and a change belongs upstream in kit (`CLAUDE.md` \u2192 "Route distributed-doc / config changes upstream to kit"). Leave both files alone, or ask the user. **This rule fires on every occurrence, not once per run.**';
2234
- var ROW14 = {
2442
+ var PROTECTED_FILE_REASON = '\u26D4 protected file: `.env` holds the Telegram bot token, so reading it \u2014 with the Read tool or a shell reader such as `cat` / `grep` / `source` \u2014 puts a secret in the transcript \u2014 the scripts that need it load it in their own process. `.claude/settings.json` in a consumer repository is the deny list and the hooks every other rule rests on, so a run must not rewrite it; `josh sync` owns it, and a change belongs upstream in kit (`CLAUDE.md` \u2192 "Route distributed-doc / config changes upstream to kit"). Leave both files alone, or ask the user. **This rule fires on every occurrence, not once per run.**';
2443
+ var ROW16 = {
2235
2444
  id: "protected-file",
2236
2445
  is_trigger: is_protected_file_call,
2237
2446
  reason: PROTECTED_FILE_REASON,
2238
2447
  decide: () => true
2239
2448
  };
2240
- var protected_files = { ROW: ROW14, is_protected_file_call };
2449
+ var protected_files = { ROW: ROW16, is_protected_file_call };
2241
2450
 
2242
2451
  // scripts/rules/permission-guards.ts
2243
2452
  var ROWS2 = [
@@ -2316,7 +2525,7 @@ function is_poll_loop(command) {
2316
2525
  return LOOP_KEYWORD2.test(plain) && SLEEP_COMMAND.test(plain);
2317
2526
  }
2318
2527
  var POLL_LOOP_REASON = '\u26D4 output poll loop: this hand-writes a `while`/`until` wait loop over a command\'s output, a marker file or a process \u2014 with a `sleep` or, worse, without one \u2014 and the loop does not know when the command finished. A regex that never matches waits to the harness block limit; one that matches a mid-run line exits too early \u2014 either way the command exit is not what ends the wait, and each pass spends a full-context round trip (joshuafolkken/kit#2371 measured about 45 minutes, 13% of six lanes, lost to exactly this). Do not poll the output file. A command known to take minutes \u2014 `pnpm josh gate`, `pnpm josh followup`, `pnpm josh format` \u2014 is started detached from the first with `run_in_background` set, and the harness re-invokes you when it exits: its completion notification is what ends the wait, on the task exit rather than a regex or the block limit. So issue the long command in the background and end the turn \u2014 do not arm a loop; the wake-up brings you back. To glance at interim output, `Read` the output file once, never in a sleep loop. If you are waiting on CI, `pnpm josh followup` waits on it for you. The procedure is `.claude/skills/workflow-commands/background-commands.md`, "A command that can take minutes is issued in the background". **This rule fires on every occurrence, not once per run.**';
2319
- var ROW15 = {
2528
+ var ROW17 = {
2320
2529
  id: "poll-loop",
2321
2530
  is_trigger: bash_triggers.on_bash_command(is_poll_loop),
2322
2531
  reason: POLL_LOOP_REASON,
@@ -2324,7 +2533,7 @@ var ROW15 = {
2324
2533
  };
2325
2534
  var poll_loop = {
2326
2535
  POLL_LOOP_REASON,
2327
- ROW: ROW15,
2536
+ ROW: ROW17,
2328
2537
  is_poll_loop
2329
2538
  };
2330
2539
 
@@ -2388,69 +2597,19 @@ var pre_gate_cut = {
2388
2597
  uncut_lane_issue
2389
2598
  };
2390
2599
 
2391
- // scripts/rules/prior-comment-read.ts
2392
- var ISSUE_PATH_NUMBER = /issues\/(\d+)/gu;
2393
- var ISSUE_VIEW_NUMBER = /\bissue\s+view\s+(?:-{1,2}\S+\s+)*(\d+)/u;
2394
- var ISSUE_READ_ARGUMENTS = /\bissue:read\b([\s\d]*)/u;
2395
- var A_NUMBER = /^\d+$/u;
2396
- var COMMENTS_ENDPOINT = /issues\/\d+\/comments\b/u;
2397
- var VIEW_WITH_COMMENTS = /\bissue\s+view\b[^\n]*--comments\b/u;
2398
- var ISSUE_READ_COMMAND = /\bissue:read\b/u;
2399
- function add_issue_read_numbers(command, numbers) {
2400
- const match = ISSUE_READ_ARGUMENTS.exec(command);
2401
- if (match === null) return;
2402
- const tokens = (match[1] ?? "").split(/\s+/u);
2403
- for (const token of tokens) {
2404
- if (A_NUMBER.test(token)) numbers.add(Number(token));
2405
- }
2406
- }
2407
- function issue_numbers_of(command) {
2408
- const numbers = /* @__PURE__ */ new Set();
2409
- for (const match of command.matchAll(ISSUE_PATH_NUMBER)) numbers.add(Number(match[1]));
2410
- const view = ISSUE_VIEW_NUMBER.exec(command);
2411
- if (view !== null) numbers.add(Number(view[1]));
2412
- add_issue_read_numbers(command, numbers);
2413
- return numbers;
2414
- }
2415
- function fetches_comments(command) {
2416
- return COMMENTS_ENDPOINT.test(command) || VIEW_WITH_COMMENTS.test(command) || ISSUE_READ_COMMAND.test(command);
2417
- }
2418
- function reads_target_comments(command, target) {
2419
- if (!fetches_comments(command)) return false;
2420
- for (const number of issue_numbers_of(command)) {
2421
- if (target.has(number)) return true;
2422
- }
2423
- return false;
2424
- }
2425
- function already_read_comments_for(tail, command) {
2426
- const target = issue_numbers_of(command);
2427
- if (target.size === 0) return false;
2428
- return tail_commands.prior_bash_commands(tail).some((prior) => reads_target_comments(prior, target));
2429
- }
2430
- function already_read_for_call(tail, call) {
2431
- if (call.name !== cost_blocks.BASH_TOOL) return false;
2432
- return already_read_comments_for(tail, time_shell.bash_command(call.input));
2433
- }
2434
- var prior_comment_read = {
2435
- already_read_comments_for,
2436
- already_read_for_call,
2437
- fetches_comments,
2438
- issue_numbers_of
2439
- };
2440
-
2441
2600
  // scripts/rules/raw-field-body.ts
2442
2601
  var RAW_FIELD_BODY_AT = /(?:^|\s)(?:-f|--raw-field)[\s=]+['"]?body=['"]?@/u;
2443
2602
  function posts_at_path_literally(command) {
2444
2603
  return RAW_FIELD_BODY_AT.test(command);
2445
2604
  }
2446
2605
  var RAW_FIELD_BODY_REASON = "\u26D4 literal @path body: this passes `body=@<path>` to `gh api` with a raw field flag (`-f` / `--raw-field`), which sends the value verbatim \u2014 the string `@<path>` is posted as the comment, not the file it points at. `-F` / `--field` is the flag that reads the file, and the two spellings differ by one character while `gh` exits 0 and returns a comment URL either way, so the break is invisible until a person reads the issue (joshuafolkken/kit#2304 lost two park comments this way). Post an issue or PR comment with `pnpm josh issue:comment <N> --body-file <path>` \u2014 the one spelling, which cannot take the wrong flag. The rule and the safe forms are in `prompts/collaboration-workflow/shell-body.md`. This rule fires on every occurrence, not once per run.";
2447
- var ROW16 = {
2606
+ var ROW18 = {
2448
2607
  id: "raw-field-body",
2449
2608
  is_trigger: bash_triggers.on_bash_command(posts_at_path_literally),
2450
2609
  reason: RAW_FIELD_BODY_REASON,
2451
2610
  decide: () => true
2452
2611
  };
2453
- var raw_field_body = { RAW_FIELD_BODY_REASON, ROW: ROW16, posts_at_path_literally };
2612
+ var raw_field_body = { RAW_FIELD_BODY_REASON, ROW: ROW18, posts_at_path_literally };
2454
2613
 
2455
2614
  // scripts/rules/rule-body-guard.ts
2456
2615
  var EDIT_TOOL = "Edit";
@@ -2521,7 +2680,7 @@ function runs_placement_oracle(command) {
2521
2680
  (names) => shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, names))
2522
2681
  );
2523
2682
  }
2524
- var ROW17 = {
2683
+ var ROW19 = {
2525
2684
  id: "rule-body",
2526
2685
  is_trigger: writes_rule_prose,
2527
2686
  reason: RULE_BODY_REASON,
@@ -2530,7 +2689,7 @@ var ROW17 = {
2530
2689
  };
2531
2690
  var rule_body_guard = {
2532
2691
  RULE_BODY_REASON,
2533
- ROW: ROW17,
2692
+ ROW: ROW19,
2534
2693
  answered_placement,
2535
2694
  is_rule_document,
2536
2695
  writes_rule_prose
@@ -2761,13 +2920,10 @@ var TYPE_CONFIGS = ["tsconfig.json", "jsconfig.json"];
2761
2920
  var DEPENDENCY_FIELDS = ["dependencies", "devDependencies"];
2762
2921
  var object_schema = z7.record(z7.string(), z7.unknown());
2763
2922
  function project_root(directory) {
2764
- let current = directory;
2765
- while (!existsSync4(path7.join(current, PACKAGE_JSON))) {
2766
- const parent = path7.dirname(current);
2767
- if (parent === current) return directory;
2768
- current = parent;
2769
- }
2770
- return current;
2923
+ return ancestor_directories.nearest(
2924
+ directory,
2925
+ (candidate) => existsSync4(path7.join(candidate, PACKAGE_JSON))
2926
+ ) ?? directory;
2771
2927
  }
2772
2928
  function is_ignored(entry) {
2773
2929
  return entry.split(/[/\\]/u).some((part) => IGNORED_DIRS.has(part));
@@ -2961,12 +3117,12 @@ var test_declared_changed = { current_verdict, path_of, read_changed_paths_sync,
2961
3117
 
2962
3118
  // scripts/rules/test-declared-commit.ts
2963
3119
  function is_untested_commit(call) {
2964
- if (!run_tail.is_push_step_call(call)) return false;
3120
+ if (!run_tail_rule.is_push_step_call(call)) return false;
2965
3121
  return test_declared_changed.current_verdict() === "required";
2966
3122
  }
2967
3123
  var REASON = '\u26D4 test declared: this commits a runtime code change with no test beside it \u2014 `pnpm josh test:declared` reads the working tree as `required` (a changed file that is neither a test \u2014 `*.test.ts` / `*.e2e.ts` \u2014 nor a mechanically-exempt non-runtime path). `CLAUDE.md` \u2192 Code Change Rules requires a test for ALL code changes: a bug fix gets a regression test, a refactor gets tests pinning behavior first, logic gets a unit test, an observable UI/timing change gets an E2E. Add the declared test and reissue this commit. **If the change genuinely touches no executable runtime path** it is the pre-approved non-runtime exception \u2014 declare it in your Step 0 work summary with the reason it is exempt (non-executable config, a cosmetic asset swap) and reissue; the exception is a presentation, not a stop. Run `pnpm josh test:declared` to see the files it names. The procedure is `prompts/collaboration-workflow/rule-delivery.md` \u2192 "\u914D\u9001\u3055\u308C\u3066\u3044\u308B\u898F\u5247". This fires once per run, so reissuing will not repeat on the call in hand.';
2968
- var ROW18 = { id: "test-declared", is_trigger: is_untested_commit, reason: REASON };
2969
- var test_declared_commit = { REASON, ROW: ROW18 };
3124
+ var ROW20 = { id: "test-declared", is_trigger: is_untested_commit, reason: REASON };
3125
+ var test_declared_commit = { REASON, ROW: ROW20 };
2970
3126
 
2971
3127
  // scripts/rules/third-party-write.ts
2972
3128
  function is_third_party_write_segment(segment, session_owner, resolve_placeholder_owners) {
@@ -3004,50 +3160,21 @@ function writes_third_party(command, resolve_owner = repo_party.current_owner, r
3004
3160
  );
3005
3161
  }
3006
3162
  var THIRD_PARTY_WRITE_REASON = '\u26D4 third-party write: this `gh api` call writes to a repository whose owner is not this session \u2014 a tracker we do not own is Tier C, and every write there (Issue, comment, PR, Discussion, review) needs explicit current-turn user instruction; typing the target, or holding a correct diagnosis, is not that instruction (`CLAUDE.md` \u2192 "Third-party repositories are Tier C", `prompts/collaboration-workflow/upstream-interrupt.md`). Confirm the party with `pnpm josh repo:party <owner/repo>` \u2014 owner equality, computed not judged. A read (`-X GET`, or no field flag) passes untouched; only a write (`-f`/`-F`/`--field`/`--raw-field`/`--input`, or `-X POST|PATCH|PUT|DELETE`) is refused. Record the finding on our own Issue and draft the artifact for a person to post \u2014 do not publish it yourself. **This rule fires on every occurrence, not once per run.**';
3007
- var ROW19 = {
3163
+ var ROW21 = {
3008
3164
  id: "third-party-write",
3009
3165
  is_trigger: bash_triggers.on_bash_command((command) => writes_third_party(command)),
3010
3166
  reason: THIRD_PARTY_WRITE_REASON,
3011
3167
  decide: () => true
3012
3168
  };
3013
3169
  var third_party_write = {
3014
- ROW: ROW19,
3170
+ ROW: ROW21,
3015
3171
  THIRD_PARTY_WRITE_REASON,
3016
3172
  writes_third_party
3017
3173
  };
3018
3174
 
3019
- // scripts/rules/delivered-rules.ts
3020
- var SWITCH_ENV_KEY = "JOSH_RULE_GUARD";
3021
- var STAMP_PREFIX = "josh-rule-guard-";
3022
- var { is_issue_filing, on_bash_command } = bash_triggers;
3175
+ // scripts/rules/wip-cap.ts
3023
3176
  var WIP_CAP = 30;
3024
3177
  var WIP_CAP_REASON = `\u26D4 backlog WIP cap: \`pnpm josh issue:file\` counts the target repository's open Issues before filing. With more than ${String(WIP_CAP)} open, close one first; nothing honestly closable means do not file \u2014 the command holds such a filing. Two filings are exempt and proceed while stating the overage \u2014 one the run is blocked by (\`--over-cap\`, or the \`tier-a\` / \`split\` route), and an interrupt (\`--route interrupt\`), decided by three tests rather than judgement: a verification answers wrongly, a documented workflow cannot complete, or data is lost or written outside the repository. Meeting none of the three, the finding is discretionary and waits. Both procedures are in \`prompts/collaboration-workflow/wip-cap.md\`. Reissue this call and let the command count \u2014 it fires once per run and cannot repeat on the call in hand.`;
3025
- var ISSUE_VIEW_COMMAND = new RegExp(String.raw`^gh\s+${gh_api.GH_FLAGS}issue\s+view\s`, "u");
3026
- var ISSUE_BODY_PATH = /repos\/[^\s'"]*\/issues\/\d+(?=$|["'\s])/u;
3027
- var FETCHES_COMMENTS = /--comments\b|--json\s[\w,]*\bcomments\b|\/comments\b/u;
3028
- var ISSUES_PATH = /repos\/[^\s'"]*\/issues\//u;
3029
- var FIELD_PROJECTION = /(?:--jq|--json)[= ]\s*('[^']*'|"[^"]*"|\S+)/u;
3030
- var NAMES_THE_BODY = /\bbody\b/u;
3031
- function projects_away_body(segment) {
3032
- const projection = FIELD_PROJECTION.exec(segment);
3033
- return projection !== null && !NAMES_THE_BODY.test(projection[1] ?? "");
3034
- }
3035
- function is_body_read_segment(segment) {
3036
- if (projects_away_body(segment)) return false;
3037
- if (ISSUE_VIEW_COMMAND.test(segment)) return true;
3038
- return gh_api.is_gh_api(segment) && ISSUE_BODY_PATH.test(segment) && gh_api.is_read(segment);
3039
- }
3040
- function fetches_issue_comments(segment) {
3041
- if (!FETCHES_COMMENTS.test(segment)) return false;
3042
- return ISSUE_VIEW_COMMAND.test(segment) || gh_api.is_gh_api(segment) && ISSUES_PATH.test(segment);
3043
- }
3044
- function is_body_only_issue_read(command) {
3045
- const segments = shell_segments.segments_of(command);
3046
- if (segments.some((segment) => fetches_issue_comments(segment))) return false;
3047
- return segments.some((segment) => is_body_read_segment(segment));
3048
- }
3049
- var ISSUE_COMMENTS_REASON = "\u26D4 an Issue's comments are part of the Issue: read them before implementing, not only the body. A decision recorded after the body was written lives only in a comment \u2014 a corrected diagnosis (joshuafolkken/kit#1537), a changed default and an added acceptance criterion (joshuafolkken/kit#1520), a scope handed to another Issue (joshuafolkken/kit#1304) \u2014 and nothing in the body says it was superseded, so a body-only reader builds the wrong thing and sees no contradiction. Reissue this read with the comments included: `gh api repos/{owner}/{repo}/issues/<N>/comments --jq '.[] | {user: .user.login, created_at, body}'` beside the body read, or `gh issue view <N> --comments` where GraphQL is reachable. Then the later text is the agreement in force \u2014 a comment supersedes the body it contradicts \u2014 except for two answers that are not the run's to make: work a comment reassigns to another Issue is out of scope and is not implemented, and a comment saying the Issue no longer has a reason to exist stops the run with a `confirmation` Telegram. The procedure is `.claude/skills/workflow-commands/issue-comments.md`. Every body-only read is refused until the comments are read.";
3050
- var { SHELL_BODY_REASON: SHELL_BODY_REASON2, carries_a_body: carries_a_body2, is_shell_evaluated_body: is_shell_evaluated_body2, keeps_body_safe: keeps_body_safe2 } = shell_body_trigger;
3051
3178
  var ISSUE_LISTING = /^gh\s+(?:-{1,2}[\w-]+(?:[= ]\S+)?\s+)*issue\s+list\b/u;
3052
3179
  var ISSUES_QUERY = /repos\/[^\s'"]*\/issues\?[^\s'"]*state=open/u;
3053
3180
  var OPEN_STATE = /--state[= ]open|state=open/u;
@@ -3058,9 +3185,19 @@ function counts_open_issues(command) {
3058
3185
  return ISSUE_LISTING.test(segment) && OPEN_STATE.test(segment) || ISSUES_QUERY.test(segment);
3059
3186
  });
3060
3187
  }
3061
- function reads_issue_comments(command) {
3062
- return shell_segments.segments_of(command).some((segment) => fetches_issue_comments(segment));
3063
- }
3188
+ var ROW22 = {
3189
+ id: "wip-cap",
3190
+ is_trigger: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
3191
+ reason: WIP_CAP_REASON,
3192
+ keeps: bash_triggers.on_bash_command(counts_open_issues)
3193
+ };
3194
+ var wip_cap = { ROW: ROW22, WIP_CAP, WIP_CAP_REASON };
3195
+
3196
+ // scripts/rules/delivered-rules.ts
3197
+ var SWITCH_ENV_KEY = "JOSH_RULE_GUARD";
3198
+ var STAMP_PREFIX = "josh-rule-guard-";
3199
+ var { is_issue_filing, on_bash_command } = bash_triggers;
3200
+ var { SHELL_BODY_REASON: SHELL_BODY_REASON2, carries_a_body: carries_a_body2, is_shell_evaluated_body: is_shell_evaluated_body2, keeps_body_safe: keeps_body_safe2 } = shell_body_trigger;
3064
3201
  var DELIVERED_RULES = [
3065
3202
  // **Listed first, ahead of the filing trigger it overlaps** (joshuafolkken/kit#2122). A `gh api`
3066
3203
  // that files an Issue into a repository we do not own trips both this row and `wip-cap` /
@@ -3075,12 +3212,7 @@ var DELIVERED_RULES = [
3075
3212
  // `epic:bundle` itself. It claims the direct call only, so the rows below — which trigger on the
3076
3213
  // `issue:file` call — never overlap it.
3077
3214
  direct_filing.ROW,
3078
- {
3079
- id: "wip-cap",
3080
- is_trigger: on_bash_command(is_issue_filing),
3081
- reason: WIP_CAP_REASON,
3082
- keeps: on_bash_command(counts_open_issues)
3083
- },
3215
+ wip_cap.ROW,
3084
3216
  // **Two more rows share the filing trigger** (joshuafolkken/kit#2119, joshuafolkken/kit#2213), listed
3085
3217
  // after `wip-cap` so the backlog count still speaks first: `filing-cap` refuses every filing past
3086
3218
  // the per-run ceiling, and `issue-fold` refuses a *second* filing the run has not folded. Each is a
@@ -3088,14 +3220,8 @@ var DELIVERED_RULES = [
3088
3220
  // delivery is still correct one reissue later — so an unfolded, over-cap second filing is delivered
3089
3221
  // `wip-cap`, then `filing-cap`, then `issue-fold` across its reissues.
3090
3222
  filing_cap.ROW,
3091
- issue_fold.ROW,
3092
- {
3093
- id: "issue-comments",
3094
- is_trigger: on_bash_command(is_body_only_issue_read),
3095
- reason: ISSUE_COMMENTS_REASON,
3096
- already_satisfied: prior_comment_read.already_read_for_call,
3097
- keeps: on_bash_command(reads_issue_comments)
3098
- },
3223
+ issue_fold_rule.ROW,
3224
+ issue_comments.ROW,
3099
3225
  {
3100
3226
  id: "shell-body",
3101
3227
  is_trigger: on_bash_command(is_shell_evaluated_body2),
@@ -3150,11 +3276,11 @@ var DELIVERED_RULES = [
3150
3276
  // second inside one `fullrun` when round 2 fixes a finding in place.
3151
3277
  {
3152
3278
  id: "run-tail",
3153
- is_trigger: run_tail.is_foreground_push_step,
3154
- reason: run_tail.RUN_TAIL_REASON,
3155
- decide: run_tail.decide,
3156
- keeps: run_tail.is_backgrounded_push_step,
3157
- reaches: run_tail.is_push_step_call
3279
+ is_trigger: run_tail_rule.is_foreground_push_step,
3280
+ reason: run_tail_rule.RUN_TAIL_REASON,
3281
+ decide: run_tail_rule.decide,
3282
+ keeps: run_tail_rule.is_backgrounded_push_step,
3283
+ reaches: run_tail_rule.is_push_step_call
3158
3284
  },
3159
3285
  // **The one row whose trigger consults the world beside the command**, because "is this a lane that
3160
3286
  // has not cut" is not readable from the call: it is the working directory's own name and the cut
@@ -3197,22 +3323,14 @@ var DELIVERED_RULES = [
3197
3323
  // "is this a dispatched lane child" is the dispatch mark against this checkout's own issue, read
3198
3324
  // synchronously by `lane_child_marker.is_child_of`, and the command match runs first so an ordinary
3199
3325
  // run pays nothing for it.
3326
+ // Its once-per-run disposition is stated beside the row in `lane-park.ts`.
3200
3327
  //
3201
- // **Once per run rather than `decide`, and no `already_satisfied`.** The stop ends the turn, so it
3202
- // is not the recurring act joshuafolkken/kit#1570 wrote `decide` for. It carries no
3203
- // `already_satisfied` because the park it asks for is a GitHub label a synchronous guard cannot
3204
- // read, and the transcript-tail alternative would read a child that merely *read*
3205
- // `backlogrun-park.md` — whose prose carries the `labels[]=needs-decision` command — as compliant
3206
- // and fall silent on a real violation. `lane-park.ts` states why one wasted reissue on the compliant
3207
- // path is the safe direction. `keeps` reads an actual label-application `Bash` command, never the
3208
- // tail, so it is not fooled the same way; `reaches` stays `is_trigger`, since a compliant run issues
3209
- // the same confirmation notify and belongs in the denominator.
3210
- {
3211
- id: "lane-park",
3212
- is_trigger: on_bash_command(lane_park.is_unparked_stop),
3213
- reason: lane_park.LANE_PARK_REASON,
3214
- keeps: on_bash_command(lane_park.records_the_park)
3215
- },
3328
+ // **A split child's park is refused first** (joshuafolkken/kit#3296): a lane child that promoted its
3329
+ // own Issue to an epic this run has nothing to park, so `lane-split-park` refuses the label write
3330
+ // and the confirmation notify onto that epic on every occurrence. It shares the notify with
3331
+ // `lane-park` and is listed first; a run that promoted nothing falls through to `lane-park`.
3332
+ lane_split_park.ROW,
3333
+ lane_park.ROW,
3216
3334
  // **joshuafolkken/kit#2034's rule, one tool-call earlier** (joshuafolkken/kit#2201). `lane-park`
3217
3335
  // fires on the `confirmation` notify a routed child reaches; a child that reaches for
3218
3336
  // `AskUserQuestion` never gets there, because the harness ends its turn at the refused ask. This
@@ -3354,12 +3472,12 @@ function is_first_delivery(tail, call, delivered_at_ms, run2) {
3354
3472
  return delivered_at_ms === hook_decision.NEVER_MS && !will_batch_guard_refuse(tail, call, run2);
3355
3473
  }
3356
3474
  function delivery_decision(rule) {
3357
- const { decide: decide6, already_satisfied } = rule;
3358
- if (decide6 === void 0 && already_satisfied === void 0) return is_first_delivery;
3475
+ const { decide: decide7, already_satisfied } = rule;
3476
+ if (decide7 === void 0 && already_satisfied === void 0) return is_first_delivery;
3359
3477
  return function should_block(tail, call, delivered_at_ms, run2) {
3360
3478
  if (already_satisfied?.(tail, call) === true) return false;
3361
- if (decide6 === void 0) return true;
3362
- return decide6(call, run2, !will_batch_guard_refuse(tail, call, run2), delivered_at_ms);
3479
+ if (decide7 === void 0) return true;
3480
+ return decide7(call, run2, !will_batch_guard_refuse(tail, call, run2), delivered_at_ms);
3363
3481
  };
3364
3482
  }
3365
3483
  function guard_of(rule) {
@@ -3392,8 +3510,8 @@ var delivered_rules = {
3392
3510
  DIRECT_FILING_REASON: direct_filing.DIRECT_FILING_REASON,
3393
3511
  FILING_CAP_REASON: filing_cap.FILING_CAP_REASON,
3394
3512
  GIT_FORCE_REASON: git_force.GIT_FORCE_REASON,
3395
- ISSUE_COMMENTS_REASON,
3396
- ISSUE_FOLD_REASON: issue_fold.ISSUE_FOLD_REASON,
3513
+ ISSUE_COMMENTS_REASON: issue_comments.ISSUE_COMMENTS_REASON,
3514
+ ISSUE_FOLD_REASON: issue_fold_rule.ISSUE_FOLD_REASON,
3397
3515
  LANE_INTERACTIVE_ASK_REASON: lane_interactive_ask.LANE_INTERACTIVE_ASK_REASON,
3398
3516
  LANE_PARK_REASON: lane_park.LANE_PARK_REASON,
3399
3517
  MEASURED_RULES,
@@ -3401,24 +3519,21 @@ var delivered_rules = {
3401
3519
  POLL_LOOP_REASON: poll_loop.POLL_LOOP_REASON,
3402
3520
  PRE_GATE_CUT_REASON: pre_gate_cut.PRE_GATE_CUT_REASON,
3403
3521
  RAW_FIELD_BODY_REASON: raw_field_body.RAW_FIELD_BODY_REASON,
3404
- RUN_TAIL_REASON: run_tail.RUN_TAIL_REASON,
3522
+ RUN_TAIL_REASON: run_tail_rule.RUN_TAIL_REASON,
3405
3523
  SHELL_BODY_REASON: SHELL_BODY_REASON2,
3406
3524
  SWITCH_ENV_KEY,
3407
3525
  THIRD_PARTY_WRITE_REASON: third_party_write.THIRD_PARTY_WRITE_REASON,
3408
- WIP_CAP,
3409
- WIP_CAP_REASON,
3526
+ WIP_CAP: wip_cap.WIP_CAP,
3527
+ WIP_CAP_REASON: wip_cap.WIP_CAP_REASON,
3410
3528
  WORKTREE_MUTATION_REASON: worktree_guard.WORKTREE_MUTATION_REASON,
3411
3529
  delivery,
3412
3530
  delivery_path,
3413
- is_body_only_issue_read,
3531
+ is_body_only_issue_read: issue_comments.is_body_only_issue_read,
3414
3532
  is_enabled,
3415
3533
  is_issue_filing
3416
3534
  };
3417
3535
 
3418
- // scripts/run/run-watcher-hook.ts
3419
- import { z as z12 } from "zod";
3420
-
3421
- // scripts/run/run-progress-read.ts
3536
+ // scripts/run/progress/run-progress-read.ts
3422
3537
  import { loadavg } from "node:os";
3423
3538
 
3424
3539
  // scripts/epic/epic-busy.ts
@@ -3766,7 +3881,7 @@ function to_advice(verdict, has_work_to_stash) {
3766
3881
  if (verdict === UNDETERMINED_VERDICT) return UNDETERMINED_ADVICE;
3767
3882
  return has_work_to_stash ? STOPPED_DIRTY_ADVICE : STOPPED_CLEAN_ADVICE;
3768
3883
  }
3769
- function decide4(traces) {
3884
+ function decide5(traces) {
3770
3885
  const verdict = to_verdict(traces);
3771
3886
  const has_work_to_stash = verdict === STOPPED_VERDICT && traces.is_tree_dirty;
3772
3887
  return {
@@ -3865,11 +3980,11 @@ async function read_traces(request) {
3865
3980
  }
3866
3981
  async function check(request) {
3867
3982
  run_issue_number.require_issue_number(request.issue);
3868
- return decide4(await read_traces(request));
3983
+ return decide5(await read_traces(request));
3869
3984
  }
3870
3985
  var run_liveness = {
3871
3986
  check,
3872
- decide: decide4,
3987
+ decide: decide5,
3873
3988
  describe_agent_state,
3874
3989
  read_child_settled,
3875
3990
  read_output_frozen,
@@ -4019,7 +4134,7 @@ function to_advice2(verdict, issue, tree) {
4019
4134
  if (verdict === PARK_VERDICT) return PARK_ADVICE;
4020
4135
  return CLEAN_ADVICE;
4021
4136
  }
4022
- function decide5(tree, child, issue) {
4137
+ function decide6(tree, child, issue) {
4023
4138
  const verdict = to_verdict2(tree, child);
4024
4139
  return {
4025
4140
  advice: to_advice2(verdict, issue, tree),
@@ -4120,8 +4235,8 @@ async function read_child_state(issue) {
4120
4235
  async function check2(issue) {
4121
4236
  require_issue_number(issue);
4122
4237
  const tree = await read_tree_state();
4123
- if (needs_reclaim(tree)) return decide5(tree, NO_CHILD_WORK, issue);
4124
- return decide5(tree, await read_child_state(issue), issue);
4238
+ if (needs_reclaim(tree)) return decide6(tree, NO_CHILD_WORK, issue);
4239
+ return decide6(tree, await read_child_state(issue), issue);
4125
4240
  }
4126
4241
  var run_preflight = {
4127
4242
  CLEAN_VERDICT,
@@ -4129,13 +4244,13 @@ var run_preflight = {
4129
4244
  MERGED_PR,
4130
4245
  STASH_LABEL_PREFIX,
4131
4246
  check: check2,
4132
- decide: decide5,
4247
+ decide: decide6,
4133
4248
  read_child_state,
4134
4249
  read_pr_state,
4135
4250
  to_pr_state
4136
4251
  };
4137
4252
 
4138
- // scripts/run/run-progress-read.ts
4253
+ // scripts/run/progress/run-progress-read.ts
4139
4254
  var { mark: mark2, parse_stamp: parse_stamp2, read_last_report: read_last_report2 } = run_progress_clock;
4140
4255
  var FIRST_LOAD_AVERAGE = 0;
4141
4256
  var IDLE_READ = { kind: "idle" };
@@ -4226,6 +4341,9 @@ var run_progress_read = {
4226
4341
  stamp_target
4227
4342
  };
4228
4343
 
4344
+ // scripts/run/run-watcher-hook.ts
4345
+ import { z as z12 } from "zod";
4346
+
4229
4347
  // scripts/run/run-watcher-guard.ts
4230
4348
  var WATCHER_TICK_MS = 3e4;
4231
4349
  var TICK_MULTIPLIER = 3;
@@ -4235,7 +4353,7 @@ var STALE_THRESHOLD_S = STALE_THRESHOLD_MS / MS_PER_SECOND3;
4235
4353
  var STALE_NOTE = `Lane children are in-flight but the progress watcher has not pinged in the last ${String(STALE_THRESHOLD_S)}s \u2014 run \`pnpm josh run:progress --wait\` in the background before proceeding. It reports to the run event stream on its own and exits only when newly runnable work arrives, so start it once and relay nothing`;
4236
4354
  var OK_RESULT = { kind: "ok" };
4237
4355
  async function owes_watcher() {
4238
- if (!await run_headless.is_backlog_parent()) return false;
4356
+ if (run_headless.is_headless() || !await run_headless.is_backlog_parent()) return false;
4239
4357
  return await lane_registry.has_lanes_in_flight();
4240
4358
  }
4241
4359
  async function check3(life_target) {
@@ -4354,7 +4472,7 @@ function is_runtime_file(file_path, root) {
4354
4472
  if (relative.startsWith(PARENT_PREFIX) || path9.isAbsolute(relative)) return false;
4355
4473
  return !test_declared_logic.is_exempt(relative.split(path9.sep).join("/"));
4356
4474
  }
4357
- function is_candidate(raw_payload, root) {
4475
+ function is_candidate2(raw_payload, root) {
4358
4476
  const payload = hook_decision.parse_hook_payload(raw_payload);
4359
4477
  if (payload === void 0 || !EDIT_TOOLS3.has(payload.tool_name)) return void 0;
4360
4478
  const has_runtime_file = edited_paths(payload.tool_input).some(
@@ -4363,7 +4481,7 @@ function is_candidate(raw_payload, root) {
4363
4481
  return has_runtime_file ? payload.transcript_path : void 0;
4364
4482
  }
4365
4483
  function notice(raw_payload, root = process.cwd()) {
4366
- const transcript = is_candidate(raw_payload, root);
4484
+ const transcript = is_candidate2(raw_payload, root);
4367
4485
  if (transcript === void 0) return void 0;
4368
4486
  const target = STAMP.path(transcript);
4369
4487
  if (STAMP.last_ms(target) !== hook_decision.NEVER_MS) return void 0;