@joshuafolkken/kit 1.1166.0 → 1.1229.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 (437) 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 +9 -5
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +29 -9
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +7 -2
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +24 -15
  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 +5 -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 +1 -1
  20. package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
  21. package/.claude/skills/workflow-commands/pre-gate-cut.md +10 -5
  22. package/.claude/skills/workflow-commands/prerequisite.md +2 -2
  23. package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
  24. package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
  25. package/.codex/hooks.json +2 -2
  26. package/.cursorrules +6 -41
  27. package/.github/actions/setup-node/action.yml +1 -1
  28. package/.github/workflows/auto-tag.yml +3 -4
  29. package/.github/workflows/ci.yml +47 -33
  30. package/.github/workflows/dependabot-auto-merge.yml +13 -6
  31. package/.github/workflows/publish.yml +48 -4
  32. package/.github/workflows/sonar-qube.yml +3 -0
  33. package/.pnpmfile.mjs +4 -3
  34. package/.prettierignore +8 -2
  35. package/CLAUDE.md +18 -20
  36. package/README.md +26 -19
  37. package/SECURITY.md +15 -1
  38. package/dist/CLAUDE.md +18 -20
  39. package/dist/commands/document-section-cli.js +734 -0
  40. package/dist/hooks/audit-provision.js +346 -0
  41. package/dist/hooks/batch-guard.js +5 -4
  42. package/dist/hooks/{chunk-DYAVAM6S.js → chunk-55HHG5PX.js} +1405 -631
  43. package/dist/hooks/{chunk-A6P7PN7P.js → chunk-5VMKCCC4.js} +25 -10
  44. package/dist/hooks/chunk-5VNDSNG3.js +12 -0
  45. package/dist/hooks/{chunk-5KTCMCOT.js → chunk-6HJJXRBX.js} +4 -2
  46. package/dist/hooks/chunk-CDY4NFQG.js +15 -0
  47. package/dist/hooks/chunk-ELCN5LCH.js +135 -0
  48. package/dist/hooks/{chunk-ENA7P5FB.js → chunk-F56MFS4B.js} +3 -3
  49. package/dist/hooks/{chunk-ANJ2WGFD.js → chunk-FIVDHQGT.js} +7 -6
  50. package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
  51. package/dist/hooks/{chunk-GU5MATVE.js → chunk-JUDKDCDP.js} +499 -389
  52. package/dist/hooks/{chunk-ZDZ555YH.js → chunk-NV2JRZZO.js} +743 -909
  53. package/dist/hooks/{chunk-V2BTOZHT.js → chunk-RNWHN5BP.js} +3 -3
  54. package/dist/hooks/{chunk-URJA6XY4.js → chunk-TQNPDZNH.js} +4 -4
  55. package/dist/hooks/{chunk-GYNGP7HI.js → chunk-VC3PZPZO.js} +1 -1
  56. package/dist/hooks/codex-hook-adapter.js +14 -12
  57. package/dist/hooks/duplicate-read-guard.js +6 -5
  58. package/dist/hooks/format-edited.js +4 -3
  59. package/dist/hooks/inputs.json +232 -210
  60. package/dist/hooks/investigation-guard.js +6 -5
  61. package/dist/hooks/pretool-guard.js +12 -10
  62. package/dist/hooks/session-lang.js +2 -4
  63. package/dist/hooks/stop-guard.js +81 -59
  64. package/dist/josh.js +872 -75
  65. package/dist/version/index.d.ts +7 -2
  66. package/dist/version/index.js +152 -75
  67. package/eslint/base.js +23 -1
  68. package/eslint/rules/code-quality.js +3 -0
  69. package/eslint/rules/early-return-one-liner.js +98 -0
  70. package/eslint/rules/unicorn.js +16 -1
  71. package/package.json +7 -6
  72. package/pnpm-workspace.yaml +9 -4
  73. package/prettier/basic.d.ts +3 -1
  74. package/prettier/index.d.ts +3 -1
  75. package/prettier/static.d.ts +3 -1
  76. package/prompts/coding-standards.md +1 -1
  77. package/prompts/collaboration-workflow/file-edits.md +4 -4
  78. package/prompts/collaboration-workflow/glossary.md +1 -1
  79. package/prompts/collaboration-workflow/operating-rules.md +17 -9
  80. package/prompts/collaboration-workflow/overview.md +2 -2
  81. package/prompts/collaboration-workflow/principles.md +14 -12
  82. package/prompts/collaboration-workflow/report-format.md +1 -1
  83. package/prompts/collaboration-workflow/residency.md +1 -1
  84. package/prompts/collaboration-workflow/rule-delivery.md +6 -6
  85. package/prompts/collaboration-workflow/shell-body.md +6 -6
  86. package/prompts/collaboration-workflow/turn-batching.md +1 -1
  87. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
  88. package/prompts/collaboration-workflow/wip-cap.md +10 -10
  89. package/prompts/review-rubric.md +1 -2
  90. package/prompts/review.md +1 -1
  91. package/scripts/agent/agent-headless.ts +29 -0
  92. package/scripts/agent/agent-launch-environment.ts +6 -2
  93. package/scripts/agent/codex-agent-argv.ts +2 -1
  94. package/scripts/auto-ok/auto-ok-cli.ts +1 -1
  95. package/scripts/backlog/backlog-arrival.ts +3 -3
  96. package/scripts/backlog/backlog-budget.ts +1 -1
  97. package/scripts/backlog/backlog-drive-args.ts +135 -0
  98. package/scripts/backlog/backlog-drive-cli.ts +11 -137
  99. package/scripts/backlog/backlog-drive-launch.ts +39 -0
  100. package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
  101. package/scripts/backlog/backlog-drive-named.ts +3 -3
  102. package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
  103. package/scripts/backlog/backlog-drive-owner.ts +1 -1
  104. package/scripts/backlog/backlog-drive-restore.ts +1 -1
  105. package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
  106. package/scripts/backlog/backlog-drive.ts +1 -1
  107. package/scripts/backlog/backlog-next.ts +31 -9
  108. package/scripts/backlog/backlog-offer-cli.ts +6 -6
  109. package/scripts/backlog/backlog-offer.ts +1 -1
  110. package/scripts/backlog/backlog-plan-cli.ts +7 -6
  111. package/scripts/backlog/backlog-pool.ts +57 -11
  112. package/scripts/backlog/backlog-rank.ts +18 -9
  113. package/scripts/backlog/backlog-ready.ts +1 -1
  114. package/scripts/backlog/backlog-restructure.ts +152 -0
  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/backlog/backlog-waves.ts +15 -17
  119. package/scripts/build/build-codex-hooks.ts +3 -1
  120. package/scripts/build/build-commands.ts +66 -0
  121. package/scripts/build/build-hooks.ts +2 -1
  122. package/scripts/build/build-library.ts +12 -3
  123. package/scripts/build/build.ts +70 -0
  124. package/scripts/build/local-bin.ts +3 -13
  125. package/scripts/cost-runtime/context-cut-threshold.ts +1 -9
  126. package/scripts/cost-runtime/cost-verdict.ts +15 -28
  127. package/scripts/delegation/investigation-reads.ts +3 -1
  128. package/scripts/discovery/repo-origin.ts +5 -5
  129. package/scripts/doctor/doctor-consumer.ts +2 -2
  130. package/scripts/doctor/doctor-io.ts +2 -3
  131. package/scripts/document/capped-print-part.ts +30 -0
  132. package/scripts/document/capped-print.ts +3 -4
  133. package/scripts/document/document-byte-budget.ts +11 -2
  134. package/scripts/document/document-scan.ts +117 -0
  135. package/scripts/document/document-section.ts +143 -16
  136. package/scripts/document/entry-read-budget.ts +5 -3
  137. package/scripts/document/entry-read-set.ts +10 -89
  138. package/scripts/document/read-set-cost.ts +98 -0
  139. package/scripts/epic/epic-add-body.ts +1 -0
  140. package/scripts/epic/epic-audit-checks.ts +1 -1
  141. package/scripts/epic/epic-audit-rationale.ts +4 -3
  142. package/scripts/epic/epic-bundle-cli.ts +14 -3
  143. package/scripts/epic/epic-bundle-poll.ts +41 -0
  144. package/scripts/epic/epic-busy.ts +1 -1
  145. package/scripts/epic/epic-candidate-confirm.ts +3 -2
  146. package/scripts/epic/epic-classify.ts +1 -1
  147. package/scripts/epic/epic-cli-argv.ts +78 -46
  148. package/scripts/epic/epic-cli-remove.ts +11 -19
  149. package/scripts/epic/epic-cli.ts +74 -73
  150. package/scripts/epic/epic-close-comment.ts +1 -1
  151. package/scripts/epic/epic-close.ts +1 -1
  152. package/scripts/epic/epic-cross-repo.ts +5 -5
  153. package/scripts/epic/epic-graph.ts +1 -1
  154. package/scripts/epic/epic-index.ts +1 -1
  155. package/scripts/epic/epic-issue.ts +2 -6
  156. package/scripts/epic/epic-lane-offer.ts +2 -2
  157. package/scripts/epic/epic-next.ts +29 -42
  158. package/scripts/epic/epic-solo-stale.ts +1 -1
  159. package/scripts/epic/epic-triage.ts +8 -4
  160. package/scripts/epic/epic-validate.ts +1 -1
  161. package/scripts/followup/git-followup-finish.ts +2 -2
  162. package/scripts/followup/git-followup-label.ts +2 -2
  163. package/scripts/followup/git-followup-merged.ts +2 -2
  164. package/scripts/followup/git-followup-stages.ts +9 -0
  165. package/scripts/followup/git-pr-followup-checks.ts +142 -0
  166. package/scripts/followup/git-pr-followup.ts +5 -130
  167. package/scripts/gate/core-budget.ts +69 -1
  168. package/scripts/gate/gate-plan.ts +47 -7
  169. package/scripts/gate/gate-skip.ts +38 -7
  170. package/scripts/gate/hook-gate-reuse.ts +12 -10
  171. package/scripts/gate/pre-commit-type-check.ts +4 -1
  172. package/scripts/gate/project-checks.ts +6 -9
  173. package/scripts/gate/type-check-step.ts +2 -3
  174. package/scripts/gate/verification-gate.ts +27 -8
  175. package/scripts/gh/git-gh-api-path.ts +2 -2
  176. package/scripts/gh/git-gh-issue-rest.ts +3 -3
  177. package/scripts/gh/git-gh-issue-write.ts +53 -7
  178. package/scripts/gh/git-gh-issue.ts +5 -3
  179. package/scripts/gh/git-gh-pr-read.ts +68 -19
  180. package/scripts/gh/git-gh-pr-rest.ts +1 -1
  181. package/scripts/gh/git-gh-pr.ts +5 -1
  182. package/scripts/gh/git-pr-ai-review.ts +1 -1
  183. package/scripts/gh/git-pr-checks-eval.ts +9 -3
  184. package/scripts/gh/git-pr-checks-parse.ts +1 -1
  185. package/scripts/gh/git-pr-checks-watch.ts +6 -5
  186. package/scripts/gh/git-pr-checks.ts +18 -6
  187. package/scripts/gh/git-pr-coderabbit.ts +1 -1
  188. package/scripts/gh/git-pr-error.ts +1 -3
  189. package/scripts/gh/git-pr.ts +9 -13
  190. package/scripts/gh/github-issue-url.ts +2 -2
  191. package/scripts/git/constants.ts +12 -6
  192. package/scripts/git/git-command.ts +20 -218
  193. package/scripts/git/git-diff-reads.ts +225 -0
  194. package/scripts/git/git-error.ts +4 -12
  195. package/scripts/git/git-pre-push-hook.ts +139 -0
  196. package/scripts/git/git-prompt.ts +2 -6
  197. package/scripts/git/{schemas.ts → git-schemas.ts} +2 -0
  198. package/scripts/git/git-spawn-sync.ts +3 -1
  199. package/scripts/git/git-spawn.ts +1 -1
  200. package/scripts/git/git-staging.ts +4 -6
  201. package/scripts/git/git-status.ts +3 -9
  202. package/scripts/git/main-merge.ts +57 -21
  203. package/scripts/hooks/codex-hook-adapter.ts +1 -1
  204. package/scripts/hooks/edited-cspell.ts +2 -4
  205. package/scripts/hooks/format-edited-file.ts +3 -12
  206. package/scripts/hooks/pre-push-unit.ts +31 -9
  207. package/scripts/hooks/reserved-run.ts +2 -2
  208. package/scripts/hooks/run-hook.sh +5 -4
  209. package/scripts/hooks/stop-guard.ts +5 -3
  210. package/scripts/init/init-actions.ts +0 -5
  211. package/scripts/init/init-ai-copy.ts +13 -2
  212. package/scripts/init/init-logic-json-merge.ts +19 -0
  213. package/scripts/init/init-logic-secretlint.ts +7 -7
  214. package/scripts/init/init-logic-workspace.ts +41 -2
  215. package/scripts/init/init-logic.ts +16 -26
  216. package/scripts/init/init.ts +5 -15
  217. package/scripts/init/initial-commit.ts +4 -4
  218. package/scripts/init/kit-development-versions.ts +40 -0
  219. package/scripts/init/kit-setup-state.ts +2 -2
  220. package/scripts/init/start-exec.ts +68 -19
  221. package/scripts/init/start-setup-pr.ts +9 -9
  222. package/scripts/init/start-steps.ts +6 -10
  223. package/scripts/issue/defect-rate-cli.ts +1 -1
  224. package/scripts/issue/git-next-issues.ts +1 -1
  225. package/scripts/issue/issue-auto-ok.ts +1 -1
  226. package/scripts/issue/issue-backlinks-cli.ts +2 -2
  227. package/scripts/issue/issue-backlinks.ts +9 -9
  228. package/scripts/issue/issue-comment-cli.ts +19 -13
  229. package/scripts/issue/issue-file-cli.ts +45 -18
  230. package/scripts/issue/issue-file.ts +24 -1
  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 +11 -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 +11 -1
  238. package/scripts/josh/josh-commands-document.ts +1 -0
  239. package/scripts/josh/josh-commands-lane.ts +22 -0
  240. package/scripts/josh/josh-commands-maintenance.ts +4 -4
  241. package/scripts/josh/josh-commands-run.ts +12 -7
  242. package/scripts/josh/josh-in-process.ts +28 -9
  243. package/scripts/josh/josh-logic.ts +33 -15
  244. package/scripts/josh/process-identity.ts +1 -3
  245. package/scripts/josh/session-language-cli.ts +3 -4
  246. package/scripts/lane/lane-await.ts +5 -1
  247. package/scripts/lane/lane-child-invocation.ts +2 -2
  248. package/scripts/lane/lane-cli.ts +3 -3
  249. package/scripts/lane/lane-dispatch.ts +14 -10
  250. package/scripts/lane/lane-environment.ts +5 -1
  251. package/scripts/lane/lane-handoff.ts +1 -1
  252. package/scripts/lane/lane-install.ts +2 -13
  253. package/scripts/lane/lane-launch-cli.ts +35 -3
  254. package/scripts/lane/lane-ledger.ts +145 -0
  255. package/scripts/lane/lane-leftover.ts +1 -1
  256. package/scripts/lane/lane-load.ts +108 -0
  257. package/scripts/lane/lane-measure-cli.ts +149 -0
  258. package/scripts/lane/lane-occupancy.ts +3 -0
  259. package/scripts/lane/lane-reap.ts +1 -3
  260. package/scripts/lane/lane-relaunch.ts +1 -1
  261. package/scripts/lane/lane-stats.ts +224 -0
  262. package/scripts/lane/lane-vacant.ts +36 -0
  263. package/scripts/lane/openai-lane-ship.ts +2 -2
  264. package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
  265. package/scripts/lane/openai-lane-supervisor.ts +7 -165
  266. package/scripts/lane/openai-review-broker.ts +2 -4
  267. package/scripts/lib/ancestor-directories.ts +35 -0
  268. package/scripts/lib/animation-helpers.ts +2 -2
  269. package/scripts/lib/buffered-process.ts +3 -7
  270. package/scripts/lib/cli-flags.ts +71 -1
  271. package/scripts/lib/spawn-exit.ts +2 -2
  272. package/scripts/lib/timeouts.ts +35 -2
  273. package/scripts/lines/line-budget.ts +4 -6
  274. package/scripts/notify/telegram-notify.ts +26 -1
  275. package/scripts/notify/telegram-test.ts +6 -0
  276. package/scripts/observations/observation-ledger-line.ts +6 -6
  277. package/scripts/observations/observation-ledger-migrate.ts +55 -4
  278. package/scripts/observations/observation-ledger.ts +26 -7
  279. package/scripts/overrides/lockfile-overrides.ts +1 -1
  280. package/scripts/overrides/overrides-logic.ts +1 -1
  281. package/scripts/propagate/propagate-git.ts +1 -1
  282. package/scripts/propagate/propagate-publish.ts +2 -2
  283. package/scripts/propagate/propagate-steps.ts +20 -15
  284. package/scripts/propagate/propagate.ts +36 -27
  285. package/scripts/refactor/import-graph.ts +18 -1
  286. package/scripts/refactor/refactor-lint.ts +4 -4
  287. package/scripts/repo/repository-labels.ts +2 -2
  288. package/scripts/repo/required-checks-logic.ts +1 -3
  289. package/scripts/review/live-evidence.ts +2 -4
  290. package/scripts/review/review-brief-cli.ts +1 -1
  291. package/scripts/review/review-finding-ledger.ts +1 -1
  292. package/scripts/review/review-findings-cli.ts +1 -3
  293. package/scripts/rules/decision-oracle-batch.ts +128 -0
  294. package/scripts/rules/decision-oracle-tokens.ts +21 -0
  295. package/scripts/rules/decision-oracle.ts +8 -120
  296. package/scripts/rules/delivered-rules.ts +26 -182
  297. package/scripts/rules/direct-filing.ts +1 -1
  298. package/scripts/rules/early-heartbeat.ts +3 -3
  299. package/scripts/rules/git-argv.ts +1 -1
  300. package/scripts/rules/git-force.ts +1 -1
  301. package/scripts/rules/implementation-cut.ts +2 -2
  302. package/scripts/rules/index-guard.ts +1 -1
  303. package/scripts/rules/issue-comments.ts +118 -0
  304. package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
  305. package/scripts/rules/lane-background.ts +2 -2
  306. package/scripts/rules/lane-park.ts +18 -0
  307. package/scripts/rules/lane-split-park.ts +128 -0
  308. package/scripts/rules/oracle-firing.ts +1 -1
  309. package/scripts/rules/poll-loop.ts +1 -1
  310. package/scripts/rules/pre-gate-cut.ts +3 -3
  311. package/scripts/rules/protected-files.ts +59 -4
  312. package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
  313. package/scripts/rules/shell-body-trigger.ts +1 -1
  314. package/scripts/rules/stop-rules.ts +18 -7
  315. package/scripts/rules/test-declared-commit.ts +4 -4
  316. package/scripts/rules/wip-cap.ts +63 -0
  317. package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
  318. package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
  319. package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
  320. package/scripts/run/{run-carry.ts → carry/run-carry.ts} +12 -6
  321. package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
  322. package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +3 -3
  323. package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +2 -2
  324. package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
  325. package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
  326. package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
  327. package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
  328. package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
  329. package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +16 -2
  330. package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
  331. package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
  332. package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
  333. package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
  334. package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
  335. package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
  336. package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
  337. package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
  338. package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
  339. package/scripts/run/run-ending.ts +2 -2
  340. package/scripts/run/run-halfrun-resume.ts +6 -1
  341. package/scripts/run/run-headless.ts +1 -1
  342. package/scripts/run/run-invocation.ts +85 -84
  343. package/scripts/run/run-liveness.ts +1 -1
  344. package/scripts/run/run-parent-cut-hook.ts +1 -1
  345. package/scripts/run/run-preflight.ts +6 -6
  346. package/scripts/run/run-prep-cli.ts +1 -1
  347. package/scripts/run/run-prrun-resume.ts +1 -1
  348. package/scripts/run/run-report-cli.ts +4 -4
  349. package/scripts/run/run-report.ts +2 -2
  350. package/scripts/run/run-status-cli.ts +1 -1
  351. package/scripts/run/run-step-cli.ts +3 -3
  352. package/scripts/run/run-step.ts +2 -2
  353. package/scripts/run/run-stop-notify.ts +1 -1
  354. package/scripts/run/run-stranded-detect.ts +4 -4
  355. package/scripts/run/run-stranded.ts +2 -2
  356. package/scripts/run/run-watcher-guard-cli.ts +1 -1
  357. package/scripts/run/run-watcher-guard.ts +5 -3
  358. package/scripts/run/run-watcher-hook.ts +1 -1
  359. package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +29 -8
  360. package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
  361. package/scripts/run/ship/run-ship-lock.ts +50 -0
  362. package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +13 -0
  363. package/scripts/run/ship/run-ship-pre-detach.ts +66 -0
  364. package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
  365. package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
  366. package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +10 -6
  367. package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
  368. package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
  369. package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +13 -0
  370. package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +15 -8
  371. package/scripts/run/ship/run-ship-stop-text.ts +13 -0
  372. package/scripts/run/ship/run-ship-sync.ts +128 -0
  373. package/scripts/run/{run-ship.ts → ship/run-ship.ts} +6 -0
  374. package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
  375. package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +31 -9
  376. package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +3 -3
  377. package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +99 -17
  378. package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
  379. package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +8 -2
  380. package/scripts/run/wake/run-wake-restart.ts +58 -0
  381. package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
  382. package/scripts/run/{run-wake.ts → wake/run-wake.ts} +1 -1
  383. package/scripts/safe-chain/preinstall-command.ts +20 -21
  384. package/scripts/safe-chain/project-config.ts +19 -15
  385. package/scripts/sync/sync-ai-files.ts +1 -1
  386. package/scripts/sync/sync-configs.ts +2 -1
  387. package/scripts/sync/sync.ts +15 -2
  388. package/scripts/test/test-red-logic.ts +17 -2
  389. package/scripts/test/test-red.ts +8 -7
  390. package/scripts/test/unit-worker-share.ts +1 -0
  391. package/scripts/time-runtime/time-background.ts +5 -1
  392. package/scripts/time-runtime/time-instant.ts +3 -1
  393. package/scripts/time-runtime/time-spans.ts +6 -139
  394. package/scripts/time-runtime/time-tool-call.ts +160 -0
  395. package/scripts/time-runtime/time-transcript-line.ts +27 -1
  396. package/scripts/version/effective-upstream.ts +2 -20
  397. package/scripts/version/latest-corepack.ts +17 -17
  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 +2 -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/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
  419. /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
  420. /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
  421. /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
  422. /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
  423. /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
  424. /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
  425. /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
  426. /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
  427. /package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +0 -0
  428. /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
  429. /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
  430. /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
  431. /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
  432. /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
  433. /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
  434. /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
  435. /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
  436. /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
  437. /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-TQNPDZNH.js";
1
4
  import {
2
5
  ALREADY_DONE_LABEL,
3
6
  CONTEXT_CUT_THRESHOLD,
@@ -5,11 +8,9 @@ import {
5
8
  NEEDS_DECISION_LABEL,
6
9
  NEEDS_HUMAN_REVIEW_LABEL,
7
10
  PORCELAIN_FLAG,
8
- RECENT_REQUEST_WINDOW,
9
11
  agent_role_profile,
10
12
  bash_triggers,
11
13
  cli_flags,
12
- error_text,
13
14
  filing_cap,
14
15
  git_command,
15
16
  git_common_directory,
@@ -22,7 +23,6 @@ import {
22
23
  lane_background,
23
24
  lane_park,
24
25
  lane_registry,
25
- open_issue_schema,
26
26
  own_session,
27
27
  parse_json_object_safe,
28
28
  read_json_listing,
@@ -33,35 +33,35 @@ import {
33
33
  run_cut_handoff,
34
34
  run_headless,
35
35
  run_hold,
36
- run_tail,
36
+ run_tail_rule,
37
37
  shell_segments,
38
38
  tail_commands
39
- } from "./chunk-ZDZ555YH.js";
39
+ } from "./chunk-NV2JRZZO.js";
40
40
  import {
41
+ ancestor_directories,
41
42
  find_local_bin_upwards
42
- } from "./chunk-3V37IID6.js";
43
+ } from "./chunk-JBBMAPC4.js";
44
+ import {
45
+ error_text
46
+ } from "./chunk-CDY4NFQG.js";
43
47
  import {
44
48
  batch_outcome
45
- } from "./chunk-V2BTOZHT.js";
49
+ } from "./chunk-RNWHN5BP.js";
46
50
  import {
47
51
  investigation_refusal
48
- } from "./chunk-ENA7P5FB.js";
49
- import {
50
- duplicate_read_outcome
51
- } from "./chunk-URJA6XY4.js";
52
+ } from "./chunk-F56MFS4B.js";
52
53
  import {
53
54
  investigation_reads
54
- } from "./chunk-A6P7PN7P.js";
55
+ } from "./chunk-5VMKCCC4.js";
55
56
  import {
56
57
  lane_guard_policy
57
- } from "./chunk-GYNGP7HI.js";
58
+ } from "./chunk-VC3PZPZO.js";
58
59
  import {
59
60
  hook_decision,
60
61
  lane_child_marker,
61
62
  run_issue_number
62
- } from "./chunk-5KTCMCOT.js";
63
+ } from "./chunk-6HJJXRBX.js";
63
64
  import {
64
- PLATFORM_TEMP_ROOT,
65
65
  cost_attribute,
66
66
  cost_blocks,
67
67
  cost_transcript,
@@ -69,13 +69,19 @@ import {
69
69
  file_reader,
70
70
  json_value,
71
71
  lane_paths,
72
- stamp_file,
72
+ open_issue_schema,
73
73
  time_batch_guard,
74
+ time_density_hook,
74
75
  time_shell
75
- } from "./chunk-DYAVAM6S.js";
76
+ } from "./chunk-55HHG5PX.js";
77
+ import {
78
+ PLATFORM_TEMP_ROOT,
79
+ PROBE_TIMEOUT_MS,
80
+ stamp_file
81
+ } from "./chunk-ELCN5LCH.js";
76
82
 
77
83
  // scripts/rules/direct-filing.ts
78
- 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.**';
79
85
  function is_any_filing(command) {
80
86
  return bash_triggers.is_direct_filing(command) || bash_triggers.is_issue_filing(command);
81
87
  }
@@ -89,7 +95,7 @@ var ROW = {
89
95
  };
90
96
  var direct_filing = { DIRECT_FILING_REASON, ROW };
91
97
 
92
- // scripts/run/run-progress.ts
98
+ // scripts/run/progress/run-progress.ts
93
99
  var DEFAULT_INTERVAL_MINUTES = 20;
94
100
  var INTERVAL_KEY = "JOSH_PROGRESS_INTERVAL_MINUTES";
95
101
  var MS_PER_MINUTE = 6e4;
@@ -208,7 +214,7 @@ var run_progress = {
208
214
  observation_key
209
215
  };
210
216
 
211
- // scripts/run/run-progress-clock.ts
217
+ // scripts/run/progress/run-progress-clock.ts
212
218
  import { z } from "zod";
213
219
  var PROGRESS_PREFIX = "josh-run-progress-";
214
220
  var LIFE_PREFIX = "josh-run-progress-life-";
@@ -334,24 +340,14 @@ var run_progress_clock = {
334
340
  stamp_target_of
335
341
  };
336
342
 
337
- // scripts/run/run-progress-config.ts
343
+ // scripts/run/progress/run-progress-config.ts
338
344
  import { readFileSync } from "node:fs";
339
345
  import path from "node:path";
340
346
  import { z as z2 } from "zod";
341
347
  var PACKAGE_FILE = "package.json";
342
- var MAX_DEPTH = 10;
343
348
  var package_schema = z2.object({
344
349
  josh: z2.object({ progress_interval_minutes: z2.number().positive() }).optional()
345
350
  });
346
- function ancestors_of(start) {
347
- const directories = [];
348
- let current = path.resolve(start);
349
- while (directories.length < MAX_DEPTH && !directories.includes(current)) {
350
- directories.push(current);
351
- current = path.dirname(current);
352
- }
353
- return directories;
354
- }
355
351
  function read_declared_minutes(directory) {
356
352
  try {
357
353
  const raw = readFileSync(path.join(directory, PACKAGE_FILE), "utf8");
@@ -362,7 +358,7 @@ function read_declared_minutes(directory) {
362
358
  }
363
359
  }
364
360
  function read_package_minutes(start) {
365
- for (const directory of ancestors_of(start)) {
361
+ for (const directory of ancestor_directories.list(start)) {
366
362
  const minutes = read_declared_minutes(directory);
367
363
  if (minutes !== void 0) return minutes;
368
364
  }
@@ -552,48 +548,6 @@ var file_body = {
552
548
  carries_a_file_body
553
549
  };
554
550
 
555
- // scripts/rules/gh-api.ts
556
- var GH_FLAGS = String.raw`(?:-{1,2}[\w-]+(?:[= ][^\s]+)?\s+)*`;
557
- var GH_API_COMMAND = new RegExp(String.raw`^gh\s+${GH_FLAGS}api\s`, "u");
558
- var API_METHOD = /(?:^|\s)(?:--method|-X)[= ]([A-Za-z]+)/u;
559
- var API_FIELD = /(?:^|\s)(?:--raw-field|--field|--input|-F|-f)(?:[= ]|$)/u;
560
- var READ_METHOD = "GET";
561
- var REPO_PATH = /repos\/([^/\s'"?]+)\/([^/\s'"?]+)/u;
562
- var OWNER_GROUP = 1;
563
- var REPO_GROUP = 2;
564
- var OWNER_PLACEHOLDERS = /* @__PURE__ */ new Set(["{owner}", ":owner"]);
565
- function is_gh_api(segment) {
566
- return GH_API_COMMAND.test(segment);
567
- }
568
- function method_of(segment) {
569
- return API_METHOD.exec(segment)?.[1]?.toUpperCase();
570
- }
571
- function is_read(segment) {
572
- const method = method_of(segment);
573
- if (method !== void 0) return method.toUpperCase() === READ_METHOD;
574
- return !API_FIELD.test(segment);
575
- }
576
- function is_write(segment) {
577
- return !is_read(segment);
578
- }
579
- function repo_target(segment) {
580
- const match = REPO_PATH.exec(segment);
581
- if (match === null) return void 0;
582
- return { owner: match[OWNER_GROUP] ?? "", repo: match[REPO_GROUP] ?? "" };
583
- }
584
- function is_owner_placeholder(owner) {
585
- return OWNER_PLACEHOLDERS.has(owner);
586
- }
587
- var gh_api = {
588
- GH_FLAGS,
589
- is_gh_api,
590
- is_owner_placeholder,
591
- is_read,
592
- is_write,
593
- method_of,
594
- repo_target
595
- };
596
-
597
551
  // scripts/rules/git-argv.ts
598
552
  var ENV_ASSIGNMENT = /^[A-Za-z_]\w*=/u;
599
553
  var WRAPPER_WORDS = /* @__PURE__ */ new Set([
@@ -897,22 +851,15 @@ var cost_corpus = {
897
851
  var FAILURE_EXIT_CODE = 1;
898
852
  var OVER_VERDICT = "over";
899
853
  var UNDER_VERDICT = "under";
900
- function recent_requests(measurement2) {
901
- return measurement2.billed_input_per_request.slice(-RECENT_REQUEST_WINDOW);
902
- }
903
854
  function per_request_cost(measurement2) {
904
- const window = recent_requests(measurement2);
905
- if (window.length === 0) return 0;
906
- const total = window.reduce((sum, billed) => sum + billed, 0);
907
- return Math.round(total / window.length);
855
+ return measurement2.billed_input_per_request.at(-1) ?? 0;
908
856
  }
909
857
  function classify(measurement2, limit) {
910
858
  return per_request_cost(measurement2) > limit ? OVER_VERDICT : UNDER_VERDICT;
911
859
  }
912
860
  function over_summary(measurement2, limit) {
913
861
  const total = measurement2.billed_input_per_request.length;
914
- const window = recent_requests(measurement2).length;
915
- return `${String(per_request_cost(measurement2))} billed input tokens per request over the last ${String(window)} of ${String(total)} request(s); limit ${String(limit)}`;
862
+ return `${String(per_request_cost(measurement2))} billed input tokens per request, on the newest of ${String(total)} request(s); limit ${String(limit)}`;
916
863
  }
917
864
  function report_over(measurement2, limit) {
918
865
  if (measurement2 === void 0 || measurement2.billed_input_per_request.length === 0) {
@@ -1208,7 +1155,141 @@ var implementation_cut = {
1208
1155
  uncut_run_issue
1209
1156
  };
1210
1157
 
1211
- // 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
1212
1293
  var FOLD_NAMES = /* @__PURE__ */ new Set(["issue:fold"]);
1213
1294
  function runs_the_fold(command) {
1214
1295
  return shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, FOLD_NAMES));
@@ -1218,14 +1299,14 @@ function already_folded(tail, _call) {
1218
1299
  return tail_commands.prior_bash_commands(tail).some((command) => runs_the_fold(command));
1219
1300
  }
1220
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.';
1221
- var ROW5 = {
1302
+ var ROW6 = {
1222
1303
  id: "issue-fold",
1223
1304
  is_trigger: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
1224
1305
  reason: ISSUE_FOLD_REASON,
1225
1306
  already_satisfied: already_folded,
1226
1307
  keeps: bash_triggers.on_bash_command(runs_the_fold)
1227
1308
  };
1228
- 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 };
1229
1310
 
1230
1311
  // scripts/rules/josh-git-bare.ts
1231
1312
  var GIT_COMMAND2 = /* @__PURE__ */ new Set(["git"]);
@@ -1241,7 +1322,7 @@ function is_bare_josh_git(command) {
1241
1322
  return shell_segments.segments_of(command).some((segment) => is_bare_segment(segment));
1242
1323
  }
1243
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.';
1244
- var ROW6 = {
1325
+ var ROW7 = {
1245
1326
  id: "josh-git-bare",
1246
1327
  is_trigger: bash_triggers.on_bash_command(is_bare_josh_git),
1247
1328
  reason: JOSH_GIT_BARE_REASON,
@@ -1249,7 +1330,7 @@ var ROW6 = {
1249
1330
  };
1250
1331
  var josh_git_bare = {
1251
1332
  JOSH_GIT_BARE_REASON,
1252
- ROW: ROW6,
1333
+ ROW: ROW7,
1253
1334
  is_bare_josh_git
1254
1335
  };
1255
1336
 
@@ -1263,7 +1344,7 @@ function is_carry_conflict(command, is_lane_child = lane_child_marker.is_child_o
1263
1344
  return is_lane_child;
1264
1345
  }
1265
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.';
1266
- var ROW7 = {
1347
+ var ROW8 = {
1267
1348
  id: "lane-carry-conflict",
1268
1349
  is_trigger: bash_triggers.on_bash_command(is_carry_conflict),
1269
1350
  reason: LANE_CARRY_CONFLICT_REASON,
@@ -1271,7 +1352,7 @@ var ROW7 = {
1271
1352
  };
1272
1353
  var lane_carry_conflict = {
1273
1354
  LANE_CARRY_CONFLICT_REASON,
1274
- ROW: ROW7,
1355
+ ROW: ROW8,
1275
1356
  invokes_budget_command,
1276
1357
  is_carry_conflict
1277
1358
  };
@@ -1324,13 +1405,73 @@ function is_interactive_ask(call, is_lane_child = lane_child_marker.is_child_of(
1324
1405
  return is_lane_child;
1325
1406
  }
1326
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.';
1327
- var ROW8 = {
1408
+ var ROW9 = {
1328
1409
  id: "lane-interactive-ask",
1329
1410
  is_trigger: is_interactive_ask,
1330
1411
  reason: LANE_INTERACTIVE_ASK_REASON,
1331
1412
  decide: () => true
1332
1413
  };
1333
- 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
+ };
1334
1475
 
1335
1476
  // scripts/rules/lane-switch-main.ts
1336
1477
  var SWITCH_SUBCOMMAND = "switch";
@@ -1361,7 +1502,7 @@ function is_switch_away_from_lane(command, is_lane_child = lane_child_marker.is_
1361
1502
  return switch_targets(command).some((target) => target !== own_branch);
1362
1503
  }
1363
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.";
1364
- var ROW9 = {
1505
+ var ROW11 = {
1365
1506
  id: "lane-switch-main",
1366
1507
  is_trigger: bash_triggers.on_bash_command(is_switch_away_from_lane),
1367
1508
  reason: LANE_SWITCH_MAIN_REASON,
@@ -1369,12 +1510,138 @@ var ROW9 = {
1369
1510
  };
1370
1511
  var lane_switch_main = {
1371
1512
  LANE_SWITCH_MAIN_REASON,
1372
- ROW: ROW9,
1513
+ ROW: ROW11,
1373
1514
  is_switch_away_from_lane,
1374
1515
  switch_target,
1375
1516
  switch_targets
1376
1517
  };
1377
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
+
1378
1645
  // scripts/run/run-stage.ts
1379
1646
  var KICKOFF = "kickoff";
1380
1647
  var HALFRUN = "halfrun";
@@ -1417,7 +1684,7 @@ function state_of(facts) {
1417
1684
  if (facts.is_halfrun_stopped) return HALFRUN_STOPPED;
1418
1685
  return facts.is_planned ? PLANNED : FRESH;
1419
1686
  }
1420
- function decide3(state, command) {
1687
+ function decide4(state, command) {
1421
1688
  const is_reached = STATES.indexOf(state) > COMMANDS.indexOf(command);
1422
1689
  const start = is_reached ? REACHED : STARTS[state];
1423
1690
  return { state, command, start, end: ENDS[command], is_reached };
@@ -1434,7 +1701,7 @@ function format_decision(issue, decision) {
1434
1701
  return `stage #${issue} \u2014 ${fields.join(" \xB7 ")}`;
1435
1702
  }
1436
1703
  function cell(state, command) {
1437
- const decision = decide3(state, command);
1704
+ const decision = decide4(state, command);
1438
1705
  return decision.is_reached ? REACHED_CELL : `${decision.start}${CELL_ARROW}${decision.end}`;
1439
1706
  }
1440
1707
  function table_rows() {
@@ -1459,7 +1726,7 @@ var run_stage = {
1459
1726
  REACHED,
1460
1727
  START_TOKENS,
1461
1728
  STATES,
1462
- decide: decide3,
1729
+ decide: decide4,
1463
1730
  format_decision,
1464
1731
  next_commands,
1465
1732
  next_line,
@@ -1478,35 +1745,15 @@ var RUN_ENTRY_ORACLE = {
1478
1745
  };
1479
1746
 
1480
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;
1481
1749
  var CHAIN_RULE_MD = ".claude/skills/workflow-commands/chain-rule.md";
1482
1750
  var BACKLOGRUN_PROGRESS_MD = ".claude/skills/workflow-commands/backlogrun-progress.md";
1483
1751
  var SKILL_2E = ".claude/skills/workflow-commands/issue-scout.md";
1484
1752
  var SPLIT_ASSESSMENT_QUESTION = ".claude/skills/workflow-commands/split-assessment.md \u2192 The question";
1485
- var PRE_GATE_CUT_MD = ".claude/skills/workflow-commands/pre-gate-cut.md";
1486
- var BACKLOGRUN_MD = ".claude/skills/workflow-commands/backlogrun.md";
1487
- var RUN_MERGE_REFERENCE = "docs/josh-commands-automation.md \u2192 `josh run:merge`";
1488
- var EPIC_NEXT_REFERENCE = "docs/josh-commands-automation.md \u2192 `josh epic:next`";
1489
- var BACKLOG_BUDGET_REFERENCE = "docs/josh-commands-automation.md \u2192 `josh backlog:budget`";
1490
- var RUN_LIVENESS_REFERENCE = "docs/josh-commands-automation.md \u2192 `josh run:liveness`";
1491
- var AUTO_OK_NEXT_REFERENCE = "docs/josh-commands-automation.md \u2192 `josh auto-ok:next`";
1492
- var RUN_CUT_CMD = "run:cut";
1493
- var REQUIRED = "required";
1494
- var SKIP = "skip";
1495
- var STOP = "stop";
1496
- var WAIT = "wait";
1497
- var RETRY = "retry";
1498
- var OVER = "over";
1499
- var UNKNOWN = "unknown";
1500
- var NONE = "none";
1501
- var NOT_A_LANE = "not-a-lane";
1502
- var BUSY = "busy";
1503
- var HUMAN_REVIEW = "human-review";
1504
1753
  var LIVE = "live";
1505
1754
  var STOPPED = "stopped";
1506
1755
  var RECONCILED = "reconciled";
1507
1756
  var NOTHING_TO_RECONCILE = "nothing to reconcile";
1508
- var ISSUE_N_ARG = "<N>";
1509
- var EXCLUDE_ARG = "[--exclude <n>...]";
1510
1757
  var DECISION_ORACLES = [
1511
1758
  {
1512
1759
  name: "delegate",
@@ -1554,8 +1801,8 @@ var DECISION_ORACLES = [
1554
1801
  {
1555
1802
  name: "run:hold",
1556
1803
  decision: "Whether this working tree is free for a run to claim",
1557
- args: `[${ISSUE_N_ARG}]`,
1558
- vocabulary: ["hold", BUSY, UNKNOWN],
1804
+ args: `[${ISSUE_N_ARG2}]`,
1805
+ vocabulary: ["hold", BUSY2, UNKNOWN2],
1559
1806
  single_source: ".claude/skills/workflow-commands/working-tree-hold.md"
1560
1807
  },
1561
1808
  {
@@ -1563,20 +1810,20 @@ var DECISION_ORACLES = [
1563
1810
  command: "cost",
1564
1811
  decision: "Whether the session cost exceeds the hand-off threshold",
1565
1812
  args: "--cut",
1566
- vocabulary: [OVER, "under"],
1813
+ vocabulary: [OVER2, "under"],
1567
1814
  single_source: BACKLOGRUN_PROGRESS_MD
1568
1815
  },
1569
1816
  {
1570
1817
  name: "release:scope",
1571
1818
  decision: "Whether a release is owed after this merge",
1572
1819
  args: "",
1573
- vocabulary: [REQUIRED, SKIP, UNKNOWN],
1820
+ vocabulary: [REQUIRED, SKIP, UNKNOWN2],
1574
1821
  single_source: ".claude/skills/workflow-commands/followup.md"
1575
1822
  },
1576
1823
  {
1577
1824
  name: "epic:bundle",
1578
1825
  decision: "Which epic a newly filed issue belongs to",
1579
- args: ISSUE_N_ARG,
1826
+ args: ISSUE_N_ARG2,
1580
1827
  vocabulary: ["add_to_epic", "create_epic", "ask", "nothing_to_bundle"],
1581
1828
  single_source: SKILL_2E
1582
1829
  },
@@ -1612,100 +1859,17 @@ var DECISION_ORACLES = [
1612
1859
  name: "cases",
1613
1860
  decision: "Each boundary a change crosses (network, process, fs, time) and the abnormal cases it owes",
1614
1861
  args: "<path...>",
1615
- vocabulary: ["network", "process", "fs", "time", NONE],
1862
+ vocabulary: ["network", "process", "fs", "time", NONE2],
1616
1863
  single_source: "prompts/collaboration-workflow/report-format.md \u2192 \u5909\u66F4\u3068\u30C6\u30B9\u30C8"
1617
1864
  },
1618
1865
  {
1619
1866
  name: "issue:state",
1620
1867
  decision: "An issue's state, labels, and human-review flag",
1621
- args: ISSUE_N_ARG,
1868
+ args: ISSUE_N_ARG2,
1622
1869
  vocabulary: ["human_review: yes", "human_review: no"],
1623
1870
  single_source: ".claude/skills/workflow-commands/needs-human-review.md"
1624
1871
  },
1625
- {
1626
- name: "run:cut:resume",
1627
- command: RUN_CUT_CMD,
1628
- decision: "Whether a lane child is resuming prior work or starting fresh",
1629
- args: `--resume ${ISSUE_N_ARG}`,
1630
- vocabulary: ["fresh", "resume", NOT_A_LANE],
1631
- single_source: PRE_GATE_CUT_MD
1632
- },
1633
- {
1634
- name: "run:cut:gate",
1635
- command: RUN_CUT_CMD,
1636
- decision: "Whether a lane child should take its pre-gate cut now",
1637
- args: ISSUE_N_ARG,
1638
- vocabulary: ["cut", NOT_A_LANE],
1639
- single_source: PRE_GATE_CUT_MD
1640
- },
1641
- {
1642
- name: "backlog:budget",
1643
- decision: "Whether a backlogrun may start more work, keep watching, or finish",
1644
- args: "",
1645
- vocabulary: ["run", "watch", STOP],
1646
- single_source: BACKLOG_BUDGET_REFERENCE
1647
- },
1648
- {
1649
- name: "run:liveness",
1650
- decision: "Whether a delegated unit is still working or has stopped without reporting",
1651
- args: `${ISSUE_N_ARG} --output <path>`,
1652
- vocabulary: ["alive", "stopped", "settled", "undetermined"],
1653
- single_source: RUN_LIVENESS_REFERENCE
1654
- },
1655
- {
1656
- name: "stash:pop",
1657
- decision: "Whether a named stash entry exists and can be applied",
1658
- args: "<message>",
1659
- vocabulary: ["popped", "conflicted", "no-match", "ambiguous"],
1660
- single_source: ".claude/skills/workflow-commands/prerequisite.md"
1661
- },
1662
- {
1663
- name: "backlog:next",
1664
- decision: "Which issue the backlog offers next, or whether to wait or stop",
1665
- args: EXCLUDE_ARG,
1666
- vocabulary: [WAIT, STOP, RETRY, "error", NONE],
1667
- single_source: BACKLOGRUN_MD
1668
- },
1669
- {
1670
- name: "auto-ok:next",
1671
- decision: "The next opted-in issue outside any epic, or none",
1672
- args: EXCLUDE_ARG,
1673
- vocabulary: [NONE],
1674
- single_source: AUTO_OK_NEXT_REFERENCE
1675
- },
1676
- {
1677
- name: "epic:next",
1678
- decision: "The next runnable child of an epic, or the epic's status",
1679
- args: "<epic>",
1680
- vocabulary: [WAIT, STOP, "complete"],
1681
- single_source: EPIC_NEXT_REFERENCE
1682
- },
1683
- {
1684
- name: "run:merge",
1685
- decision: "The post-merge batch step: confirm child, advance, or report a stop condition",
1686
- args: ISSUE_N_ARG,
1687
- vocabulary: [OVER, HUMAN_REVIEW, STOP, RETRY, BUSY],
1688
- single_source: RUN_MERGE_REFERENCE
1689
- },
1690
- {
1691
- name: "run:step",
1692
- decision: "The run\u2019s next single action, computed from the event stream, carry and issue state",
1693
- args: ISSUE_N_ARG,
1694
- // The non-command answers: `run:step` prints a runnable command for a phase with one to run, and
1695
- // one of these verdicts otherwise. A command line is not a vocabulary token, exactly as an issue
1696
- // number is not one for `backlog:next`.
1697
- vocabulary: [
1698
- "implement",
1699
- HUMAN_REVIEW,
1700
- "update-deps",
1701
- "already-done",
1702
- "keep-work",
1703
- WAIT,
1704
- STOP,
1705
- UNKNOWN
1706
- ],
1707
- single_source: BACKLOGRUN_MD
1708
- },
1872
+ ...BATCH_ORACLES,
1709
1873
  {
1710
1874
  name: "refactor:scan",
1711
1875
  decision: "Whether the refactoring scope still holds high- or medium-priority candidates",
@@ -1732,7 +1896,7 @@ var DECISION_ORACLES = [
1732
1896
  decision: "Whether cross-file or cross-repository code duplication exists",
1733
1897
  args: "",
1734
1898
  vocabulary: ["clean", "clones:"],
1735
- 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"
1736
1900
  },
1737
1901
  {
1738
1902
  name: "epic:reconcile",
@@ -1740,14 +1904,14 @@ var DECISION_ORACLES = [
1740
1904
  decision: "Whether an epic's declaration matches its recorded relations, and the repair when it does not",
1741
1905
  args: "--reconcile <E>",
1742
1906
  vocabulary: [RECONCILED, NOTHING_TO_RECONCILE],
1743
- single_source: "docs/josh-commands-automation.md \u2192 `josh epic --reconcile`"
1907
+ single_source: "docs/josh-commands-backlog.md \u2192 `josh epic --reconcile`"
1744
1908
  },
1745
1909
  {
1746
1910
  name: "lane:list",
1747
1911
  decision: "Whether each in-progress issue holds a live lane, and the two-directional difference",
1748
1912
  args: "",
1749
- vocabulary: [LIVE, STOPPED, UNKNOWN],
1750
- 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"
1751
1915
  },
1752
1916
  RUN_ENTRY_ORACLE
1753
1917
  ];
@@ -1829,7 +1993,7 @@ function declaration_for(name) {
1829
1993
  function firing_oracles() {
1830
1994
  return decision_oracle.DECISION_ORACLES.flatMap((oracle) => {
1831
1995
  const firing_point = FIRING.get(oracle.name);
1832
- return firing_point === void 0 ? [] : [{ oracle, firing_point }];
1996
+ return firing_point === void 0 ? [] : { oracle, firing_point };
1833
1997
  });
1834
1998
  }
1835
1999
  var oracle_firing = {
@@ -1919,13 +2083,13 @@ function is_destructive_command(command) {
1919
2083
  return shell_segments.segments_of(command).some((segment) => is_destructive_segment2(segment));
1920
2084
  }
1921
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.**";
1922
- var ROW10 = {
2086
+ var ROW12 = {
1923
2087
  id: "destructive-command",
1924
2088
  is_trigger: bash_triggers.on_bash_command(is_destructive_command),
1925
2089
  reason: DESTRUCTIVE_COMMAND_REASON,
1926
2090
  decide: () => true
1927
2091
  };
1928
- var destructive_command = { DESTRUCTIVE_COMMAND_REASON, ROW: ROW10, is_destructive_command };
2092
+ var destructive_command = { DESTRUCTIVE_COMMAND_REASON, ROW: ROW12, is_destructive_command };
1929
2093
 
1930
2094
  // scripts/rules/direct-pr-create.ts
1931
2095
  var GH_COMMAND2 = "gh";
@@ -1980,13 +2144,13 @@ function is_direct_pr_create(command) {
1980
2144
  return shell_segments.segments_of(command).some((segment) => is_pr_create_segment(segment));
1981
2145
  }
1982
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.**";
1983
- var ROW11 = {
2147
+ var ROW13 = {
1984
2148
  id: "direct-pr-create",
1985
2149
  is_trigger: bash_triggers.on_bash_command(is_direct_pr_create),
1986
2150
  reason: DIRECT_PR_CREATE_REASON,
1987
2151
  decide: () => true
1988
2152
  };
1989
- 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 };
1990
2154
 
1991
2155
  // scripts/rules/worktree-guard.ts
1992
2156
  var CHECKOUT_SUBCOMMAND = "checkout";
@@ -2048,14 +2212,14 @@ function is_unauthorized_worktree_change(command) {
2048
2212
  return shell_segments.segments_of(command).some((segment) => is_unauthorized_segment(segment));
2049
2213
  }
2050
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.**';
2051
- var ROW12 = {
2215
+ var ROW14 = {
2052
2216
  id: "worktree-mutation",
2053
2217
  is_trigger: bash_triggers.on_bash_command(is_unauthorized_worktree_change),
2054
2218
  reason: WORKTREE_MUTATION_REASON,
2055
2219
  decide: () => true
2056
2220
  };
2057
2221
  var worktree_guard = {
2058
- ROW: ROW12,
2222
+ ROW: ROW14,
2059
2223
  WORKTREE_MUTATION_REASON,
2060
2224
  is_unauthorized_worktree_change,
2061
2225
  touches_worktree
@@ -2083,14 +2247,14 @@ function is_index_segment(segment) {
2083
2247
  function is_index_mutation(command) {
2084
2248
  return shell_segments.segments_of(command).some((segment) => is_index_segment(segment));
2085
2249
  }
2086
- 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.**';
2087
- 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 = {
2088
2252
  id: "index-mutation",
2089
2253
  is_trigger: bash_triggers.on_bash_command(is_index_mutation),
2090
2254
  reason: INDEX_MUTATION_REASON,
2091
2255
  decide: () => true
2092
2256
  };
2093
- var index_guard = { INDEX_MUTATION_REASON, ROW: ROW13, is_index_mutation };
2257
+ var index_guard = { INDEX_MUTATION_REASON, ROW: ROW15, is_index_mutation };
2094
2258
 
2095
2259
  // scripts/rules/protected-files.ts
2096
2260
  import path5 from "node:path";
@@ -2110,7 +2274,6 @@ var CLAUDE_MD = "CLAUDE.md";
2110
2274
  var PLUGIN_ID = "kit@kit";
2111
2275
  var CLAUDE_MD_POINTER = `@${NODE_MODULES}/${KIT_PACKAGE_NAME}/dist/${CLAUDE_MD}`;
2112
2276
  var SECRETLINT_BIN = path4.join(NODE_MODULES, ".bin", "secretlint");
2113
- var GIT_TIMEOUT_MS = 2e3;
2114
2277
  var manifest_schema = z6.object({
2115
2278
  name: z6.string().optional(),
2116
2279
  dependencies: z6.record(z6.string(), z6.string()).optional(),
@@ -2145,7 +2308,7 @@ function is_plugin_declared(root) {
2145
2308
  }
2146
2309
  function configured_hooks_path(root) {
2147
2310
  const value = git_spawn_sync.read(["-C", root, "config", "--get", "core.hooksPath"], {
2148
- timeout: GIT_TIMEOUT_MS
2311
+ timeout: PROBE_TIMEOUT_MS
2149
2312
  });
2150
2313
  return value === "" ? void 0 : value;
2151
2314
  }
@@ -2221,13 +2384,51 @@ var READ_TOOL = "Read";
2221
2384
  var EDIT_TOOLS2 = /* @__PURE__ */ new Set(["Edit", "Write"]);
2222
2385
  var ENVIRONMENT_FILE = ".env";
2223
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
+ ]);
2224
2410
  function file_path_of(call) {
2225
2411
  if (!json_value.is_record(call.input)) return "";
2226
2412
  const value = call.input["file_path"];
2227
2413
  return typeof value === "string" ? value : "";
2228
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
+ }
2229
2429
  function is_environment_read(call) {
2230
- 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);
2231
2432
  }
2232
2433
  function is_consumer_settings_edit(call, is_kit_consumer2) {
2233
2434
  if (!EDIT_TOOLS2.has(call.name)) return false;
@@ -2238,14 +2439,14 @@ function is_consumer_settings_edit(call, is_kit_consumer2) {
2238
2439
  function is_protected_file_call(call, is_kit_consumer2 = doctor_consumer.is_kit_consumer) {
2239
2440
  return is_environment_read(call) || is_consumer_settings_edit(call, is_kit_consumer2);
2240
2441
  }
2241
- 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.**';
2242
- 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 = {
2243
2444
  id: "protected-file",
2244
2445
  is_trigger: is_protected_file_call,
2245
2446
  reason: PROTECTED_FILE_REASON,
2246
2447
  decide: () => true
2247
2448
  };
2248
- var protected_files = { ROW: ROW14, is_protected_file_call };
2449
+ var protected_files = { ROW: ROW16, is_protected_file_call };
2249
2450
 
2250
2451
  // scripts/rules/permission-guards.ts
2251
2452
  var ROWS2 = [
@@ -2324,7 +2525,7 @@ function is_poll_loop(command) {
2324
2525
  return LOOP_KEYWORD2.test(plain) && SLEEP_COMMAND.test(plain);
2325
2526
  }
2326
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.**';
2327
- var ROW15 = {
2528
+ var ROW17 = {
2328
2529
  id: "poll-loop",
2329
2530
  is_trigger: bash_triggers.on_bash_command(is_poll_loop),
2330
2531
  reason: POLL_LOOP_REASON,
@@ -2332,7 +2533,7 @@ var ROW15 = {
2332
2533
  };
2333
2534
  var poll_loop = {
2334
2535
  POLL_LOOP_REASON,
2335
- ROW: ROW15,
2536
+ ROW: ROW17,
2336
2537
  is_poll_loop
2337
2538
  };
2338
2539
 
@@ -2396,69 +2597,19 @@ var pre_gate_cut = {
2396
2597
  uncut_lane_issue
2397
2598
  };
2398
2599
 
2399
- // scripts/rules/prior-comment-read.ts
2400
- var ISSUE_PATH_NUMBER = /issues\/(\d+)/gu;
2401
- var ISSUE_VIEW_NUMBER = /\bissue\s+view\s+(?:-{1,2}\S+\s+)*(\d+)/u;
2402
- var ISSUE_READ_ARGUMENTS = /\bissue:read\b([\s\d]*)/u;
2403
- var A_NUMBER = /^\d+$/u;
2404
- var COMMENTS_ENDPOINT = /issues\/\d+\/comments\b/u;
2405
- var VIEW_WITH_COMMENTS = /\bissue\s+view\b[^\n]*--comments\b/u;
2406
- var ISSUE_READ_COMMAND = /\bissue:read\b/u;
2407
- function add_issue_read_numbers(command, numbers) {
2408
- const match = ISSUE_READ_ARGUMENTS.exec(command);
2409
- if (match === null) return;
2410
- const tokens = (match[1] ?? "").split(/\s+/u);
2411
- for (const token of tokens) {
2412
- if (A_NUMBER.test(token)) numbers.add(Number(token));
2413
- }
2414
- }
2415
- function issue_numbers_of(command) {
2416
- const numbers = /* @__PURE__ */ new Set();
2417
- for (const match of command.matchAll(ISSUE_PATH_NUMBER)) numbers.add(Number(match[1]));
2418
- const view = ISSUE_VIEW_NUMBER.exec(command);
2419
- if (view !== null) numbers.add(Number(view[1]));
2420
- add_issue_read_numbers(command, numbers);
2421
- return numbers;
2422
- }
2423
- function fetches_comments(command) {
2424
- return COMMENTS_ENDPOINT.test(command) || VIEW_WITH_COMMENTS.test(command) || ISSUE_READ_COMMAND.test(command);
2425
- }
2426
- function reads_target_comments(command, target) {
2427
- if (!fetches_comments(command)) return false;
2428
- for (const number of issue_numbers_of(command)) {
2429
- if (target.has(number)) return true;
2430
- }
2431
- return false;
2432
- }
2433
- function already_read_comments_for(tail, command) {
2434
- const target = issue_numbers_of(command);
2435
- if (target.size === 0) return false;
2436
- return tail_commands.prior_bash_commands(tail).some((prior) => reads_target_comments(prior, target));
2437
- }
2438
- function already_read_for_call(tail, call) {
2439
- if (call.name !== cost_blocks.BASH_TOOL) return false;
2440
- return already_read_comments_for(tail, time_shell.bash_command(call.input));
2441
- }
2442
- var prior_comment_read = {
2443
- already_read_comments_for,
2444
- already_read_for_call,
2445
- fetches_comments,
2446
- issue_numbers_of
2447
- };
2448
-
2449
2600
  // scripts/rules/raw-field-body.ts
2450
2601
  var RAW_FIELD_BODY_AT = /(?:^|\s)(?:-f|--raw-field)[\s=]+['"]?body=['"]?@/u;
2451
2602
  function posts_at_path_literally(command) {
2452
2603
  return RAW_FIELD_BODY_AT.test(command);
2453
2604
  }
2454
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.";
2455
- var ROW16 = {
2606
+ var ROW18 = {
2456
2607
  id: "raw-field-body",
2457
2608
  is_trigger: bash_triggers.on_bash_command(posts_at_path_literally),
2458
2609
  reason: RAW_FIELD_BODY_REASON,
2459
2610
  decide: () => true
2460
2611
  };
2461
- 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 };
2462
2613
 
2463
2614
  // scripts/rules/rule-body-guard.ts
2464
2615
  var EDIT_TOOL = "Edit";
@@ -2529,7 +2680,7 @@ function runs_placement_oracle(command) {
2529
2680
  (names) => shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, names))
2530
2681
  );
2531
2682
  }
2532
- var ROW17 = {
2683
+ var ROW19 = {
2533
2684
  id: "rule-body",
2534
2685
  is_trigger: writes_rule_prose,
2535
2686
  reason: RULE_BODY_REASON,
@@ -2538,7 +2689,7 @@ var ROW17 = {
2538
2689
  };
2539
2690
  var rule_body_guard = {
2540
2691
  RULE_BODY_REASON,
2541
- ROW: ROW17,
2692
+ ROW: ROW19,
2542
2693
  answered_placement,
2543
2694
  is_rule_document,
2544
2695
  writes_rule_prose
@@ -2769,13 +2920,10 @@ var TYPE_CONFIGS = ["tsconfig.json", "jsconfig.json"];
2769
2920
  var DEPENDENCY_FIELDS = ["dependencies", "devDependencies"];
2770
2921
  var object_schema = z7.record(z7.string(), z7.unknown());
2771
2922
  function project_root(directory) {
2772
- let current = directory;
2773
- while (!existsSync4(path7.join(current, PACKAGE_JSON))) {
2774
- const parent = path7.dirname(current);
2775
- if (parent === current) return directory;
2776
- current = parent;
2777
- }
2778
- return current;
2923
+ return ancestor_directories.nearest(
2924
+ directory,
2925
+ (candidate) => existsSync4(path7.join(candidate, PACKAGE_JSON))
2926
+ ) ?? directory;
2779
2927
  }
2780
2928
  function is_ignored(entry) {
2781
2929
  return entry.split(/[/\\]/u).some((part) => IGNORED_DIRS.has(part));
@@ -2969,12 +3117,12 @@ var test_declared_changed = { current_verdict, path_of, read_changed_paths_sync,
2969
3117
 
2970
3118
  // scripts/rules/test-declared-commit.ts
2971
3119
  function is_untested_commit(call) {
2972
- if (!run_tail.is_push_step_call(call)) return false;
3120
+ if (!run_tail_rule.is_push_step_call(call)) return false;
2973
3121
  return test_declared_changed.current_verdict() === "required";
2974
3122
  }
2975
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.';
2976
- var ROW18 = { id: "test-declared", is_trigger: is_untested_commit, reason: REASON };
2977
- 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 };
2978
3126
 
2979
3127
  // scripts/rules/third-party-write.ts
2980
3128
  function is_third_party_write_segment(segment, session_owner, resolve_placeholder_owners) {
@@ -3012,50 +3160,21 @@ function writes_third_party(command, resolve_owner = repo_party.current_owner, r
3012
3160
  );
3013
3161
  }
3014
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.**';
3015
- var ROW19 = {
3163
+ var ROW21 = {
3016
3164
  id: "third-party-write",
3017
3165
  is_trigger: bash_triggers.on_bash_command((command) => writes_third_party(command)),
3018
3166
  reason: THIRD_PARTY_WRITE_REASON,
3019
3167
  decide: () => true
3020
3168
  };
3021
3169
  var third_party_write = {
3022
- ROW: ROW19,
3170
+ ROW: ROW21,
3023
3171
  THIRD_PARTY_WRITE_REASON,
3024
3172
  writes_third_party
3025
3173
  };
3026
3174
 
3027
- // scripts/rules/delivered-rules.ts
3028
- var SWITCH_ENV_KEY = "JOSH_RULE_GUARD";
3029
- var STAMP_PREFIX = "josh-rule-guard-";
3030
- var { is_issue_filing, on_bash_command } = bash_triggers;
3175
+ // scripts/rules/wip-cap.ts
3031
3176
  var WIP_CAP = 30;
3032
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.`;
3033
- var ISSUE_VIEW_COMMAND = new RegExp(String.raw`^gh\s+${gh_api.GH_FLAGS}issue\s+view\s`, "u");
3034
- var ISSUE_BODY_PATH = /repos\/[^\s'"]*\/issues\/\d+(?=$|["'\s])/u;
3035
- var FETCHES_COMMENTS = /--comments\b|--json\s[\w,]*\bcomments\b|\/comments\b/u;
3036
- var ISSUES_PATH = /repos\/[^\s'"]*\/issues\//u;
3037
- var FIELD_PROJECTION = /(?:--jq|--json)[= ]\s*('[^']*'|"[^"]*"|\S+)/u;
3038
- var NAMES_THE_BODY = /\bbody\b/u;
3039
- function projects_away_body(segment) {
3040
- const projection = FIELD_PROJECTION.exec(segment);
3041
- return projection !== null && !NAMES_THE_BODY.test(projection[1] ?? "");
3042
- }
3043
- function is_body_read_segment(segment) {
3044
- if (projects_away_body(segment)) return false;
3045
- if (ISSUE_VIEW_COMMAND.test(segment)) return true;
3046
- return gh_api.is_gh_api(segment) && ISSUE_BODY_PATH.test(segment) && gh_api.is_read(segment);
3047
- }
3048
- function fetches_issue_comments(segment) {
3049
- if (!FETCHES_COMMENTS.test(segment)) return false;
3050
- return ISSUE_VIEW_COMMAND.test(segment) || gh_api.is_gh_api(segment) && ISSUES_PATH.test(segment);
3051
- }
3052
- function is_body_only_issue_read(command) {
3053
- const segments = shell_segments.segments_of(command);
3054
- if (segments.some((segment) => fetches_issue_comments(segment))) return false;
3055
- return segments.some((segment) => is_body_read_segment(segment));
3056
- }
3057
- 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.";
3058
- 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;
3059
3178
  var ISSUE_LISTING = /^gh\s+(?:-{1,2}[\w-]+(?:[= ]\S+)?\s+)*issue\s+list\b/u;
3060
3179
  var ISSUES_QUERY = /repos\/[^\s'"]*\/issues\?[^\s'"]*state=open/u;
3061
3180
  var OPEN_STATE = /--state[= ]open|state=open/u;
@@ -3066,9 +3185,19 @@ function counts_open_issues(command) {
3066
3185
  return ISSUE_LISTING.test(segment) && OPEN_STATE.test(segment) || ISSUES_QUERY.test(segment);
3067
3186
  });
3068
3187
  }
3069
- function reads_issue_comments(command) {
3070
- return shell_segments.segments_of(command).some((segment) => fetches_issue_comments(segment));
3071
- }
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;
3072
3201
  var DELIVERED_RULES = [
3073
3202
  // **Listed first, ahead of the filing trigger it overlaps** (joshuafolkken/kit#2122). A `gh api`
3074
3203
  // that files an Issue into a repository we do not own trips both this row and `wip-cap` /
@@ -3083,12 +3212,7 @@ var DELIVERED_RULES = [
3083
3212
  // `epic:bundle` itself. It claims the direct call only, so the rows below — which trigger on the
3084
3213
  // `issue:file` call — never overlap it.
3085
3214
  direct_filing.ROW,
3086
- {
3087
- id: "wip-cap",
3088
- is_trigger: on_bash_command(is_issue_filing),
3089
- reason: WIP_CAP_REASON,
3090
- keeps: on_bash_command(counts_open_issues)
3091
- },
3215
+ wip_cap.ROW,
3092
3216
  // **Two more rows share the filing trigger** (joshuafolkken/kit#2119, joshuafolkken/kit#2213), listed
3093
3217
  // after `wip-cap` so the backlog count still speaks first: `filing-cap` refuses every filing past
3094
3218
  // the per-run ceiling, and `issue-fold` refuses a *second* filing the run has not folded. Each is a
@@ -3096,14 +3220,8 @@ var DELIVERED_RULES = [
3096
3220
  // delivery is still correct one reissue later — so an unfolded, over-cap second filing is delivered
3097
3221
  // `wip-cap`, then `filing-cap`, then `issue-fold` across its reissues.
3098
3222
  filing_cap.ROW,
3099
- issue_fold.ROW,
3100
- {
3101
- id: "issue-comments",
3102
- is_trigger: on_bash_command(is_body_only_issue_read),
3103
- reason: ISSUE_COMMENTS_REASON,
3104
- already_satisfied: prior_comment_read.already_read_for_call,
3105
- keeps: on_bash_command(reads_issue_comments)
3106
- },
3223
+ issue_fold_rule.ROW,
3224
+ issue_comments.ROW,
3107
3225
  {
3108
3226
  id: "shell-body",
3109
3227
  is_trigger: on_bash_command(is_shell_evaluated_body2),
@@ -3158,11 +3276,11 @@ var DELIVERED_RULES = [
3158
3276
  // second inside one `fullrun` when round 2 fixes a finding in place.
3159
3277
  {
3160
3278
  id: "run-tail",
3161
- is_trigger: run_tail.is_foreground_push_step,
3162
- reason: run_tail.RUN_TAIL_REASON,
3163
- decide: run_tail.decide,
3164
- keeps: run_tail.is_backgrounded_push_step,
3165
- 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
3166
3284
  },
3167
3285
  // **The one row whose trigger consults the world beside the command**, because "is this a lane that
3168
3286
  // has not cut" is not readable from the call: it is the working directory's own name and the cut
@@ -3205,22 +3323,14 @@ var DELIVERED_RULES = [
3205
3323
  // "is this a dispatched lane child" is the dispatch mark against this checkout's own issue, read
3206
3324
  // synchronously by `lane_child_marker.is_child_of`, and the command match runs first so an ordinary
3207
3325
  // run pays nothing for it.
3326
+ // Its once-per-run disposition is stated beside the row in `lane-park.ts`.
3208
3327
  //
3209
- // **Once per run rather than `decide`, and no `already_satisfied`.** The stop ends the turn, so it
3210
- // is not the recurring act joshuafolkken/kit#1570 wrote `decide` for. It carries no
3211
- // `already_satisfied` because the park it asks for is a GitHub label a synchronous guard cannot
3212
- // read, and the transcript-tail alternative would read a child that merely *read*
3213
- // `backlogrun-park.md` — whose prose carries the `labels[]=needs-decision` command — as compliant
3214
- // and fall silent on a real violation. `lane-park.ts` states why one wasted reissue on the compliant
3215
- // path is the safe direction. `keeps` reads an actual label-application `Bash` command, never the
3216
- // tail, so it is not fooled the same way; `reaches` stays `is_trigger`, since a compliant run issues
3217
- // the same confirmation notify and belongs in the denominator.
3218
- {
3219
- id: "lane-park",
3220
- is_trigger: on_bash_command(lane_park.is_unparked_stop),
3221
- reason: lane_park.LANE_PARK_REASON,
3222
- keeps: on_bash_command(lane_park.records_the_park)
3223
- },
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,
3224
3334
  // **joshuafolkken/kit#2034's rule, one tool-call earlier** (joshuafolkken/kit#2201). `lane-park`
3225
3335
  // fires on the `confirmation` notify a routed child reaches; a child that reaches for
3226
3336
  // `AskUserQuestion` never gets there, because the harness ends its turn at the refused ask. This
@@ -3362,12 +3472,12 @@ function is_first_delivery(tail, call, delivered_at_ms, run2) {
3362
3472
  return delivered_at_ms === hook_decision.NEVER_MS && !will_batch_guard_refuse(tail, call, run2);
3363
3473
  }
3364
3474
  function delivery_decision(rule) {
3365
- const { decide: decide6, already_satisfied } = rule;
3366
- 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;
3367
3477
  return function should_block(tail, call, delivered_at_ms, run2) {
3368
3478
  if (already_satisfied?.(tail, call) === true) return false;
3369
- if (decide6 === void 0) return true;
3370
- 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);
3371
3481
  };
3372
3482
  }
3373
3483
  function guard_of(rule) {
@@ -3400,8 +3510,8 @@ var delivered_rules = {
3400
3510
  DIRECT_FILING_REASON: direct_filing.DIRECT_FILING_REASON,
3401
3511
  FILING_CAP_REASON: filing_cap.FILING_CAP_REASON,
3402
3512
  GIT_FORCE_REASON: git_force.GIT_FORCE_REASON,
3403
- ISSUE_COMMENTS_REASON,
3404
- 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,
3405
3515
  LANE_INTERACTIVE_ASK_REASON: lane_interactive_ask.LANE_INTERACTIVE_ASK_REASON,
3406
3516
  LANE_PARK_REASON: lane_park.LANE_PARK_REASON,
3407
3517
  MEASURED_RULES,
@@ -3409,24 +3519,21 @@ var delivered_rules = {
3409
3519
  POLL_LOOP_REASON: poll_loop.POLL_LOOP_REASON,
3410
3520
  PRE_GATE_CUT_REASON: pre_gate_cut.PRE_GATE_CUT_REASON,
3411
3521
  RAW_FIELD_BODY_REASON: raw_field_body.RAW_FIELD_BODY_REASON,
3412
- RUN_TAIL_REASON: run_tail.RUN_TAIL_REASON,
3522
+ RUN_TAIL_REASON: run_tail_rule.RUN_TAIL_REASON,
3413
3523
  SHELL_BODY_REASON: SHELL_BODY_REASON2,
3414
3524
  SWITCH_ENV_KEY,
3415
3525
  THIRD_PARTY_WRITE_REASON: third_party_write.THIRD_PARTY_WRITE_REASON,
3416
- WIP_CAP,
3417
- WIP_CAP_REASON,
3526
+ WIP_CAP: wip_cap.WIP_CAP,
3527
+ WIP_CAP_REASON: wip_cap.WIP_CAP_REASON,
3418
3528
  WORKTREE_MUTATION_REASON: worktree_guard.WORKTREE_MUTATION_REASON,
3419
3529
  delivery,
3420
3530
  delivery_path,
3421
- is_body_only_issue_read,
3531
+ is_body_only_issue_read: issue_comments.is_body_only_issue_read,
3422
3532
  is_enabled,
3423
3533
  is_issue_filing
3424
3534
  };
3425
3535
 
3426
- // scripts/run/run-watcher-hook.ts
3427
- import { z as z12 } from "zod";
3428
-
3429
- // scripts/run/run-progress-read.ts
3536
+ // scripts/run/progress/run-progress-read.ts
3430
3537
  import { loadavg } from "node:os";
3431
3538
 
3432
3539
  // scripts/epic/epic-busy.ts
@@ -3774,7 +3881,7 @@ function to_advice(verdict, has_work_to_stash) {
3774
3881
  if (verdict === UNDETERMINED_VERDICT) return UNDETERMINED_ADVICE;
3775
3882
  return has_work_to_stash ? STOPPED_DIRTY_ADVICE : STOPPED_CLEAN_ADVICE;
3776
3883
  }
3777
- function decide4(traces) {
3884
+ function decide5(traces) {
3778
3885
  const verdict = to_verdict(traces);
3779
3886
  const has_work_to_stash = verdict === STOPPED_VERDICT && traces.is_tree_dirty;
3780
3887
  return {
@@ -3873,11 +3980,11 @@ async function read_traces(request) {
3873
3980
  }
3874
3981
  async function check(request) {
3875
3982
  run_issue_number.require_issue_number(request.issue);
3876
- return decide4(await read_traces(request));
3983
+ return decide5(await read_traces(request));
3877
3984
  }
3878
3985
  var run_liveness = {
3879
3986
  check,
3880
- decide: decide4,
3987
+ decide: decide5,
3881
3988
  describe_agent_state,
3882
3989
  read_child_settled,
3883
3990
  read_output_frozen,
@@ -4027,7 +4134,7 @@ function to_advice2(verdict, issue, tree) {
4027
4134
  if (verdict === PARK_VERDICT) return PARK_ADVICE;
4028
4135
  return CLEAN_ADVICE;
4029
4136
  }
4030
- function decide5(tree, child, issue) {
4137
+ function decide6(tree, child, issue) {
4031
4138
  const verdict = to_verdict2(tree, child);
4032
4139
  return {
4033
4140
  advice: to_advice2(verdict, issue, tree),
@@ -4128,8 +4235,8 @@ async function read_child_state(issue) {
4128
4235
  async function check2(issue) {
4129
4236
  require_issue_number(issue);
4130
4237
  const tree = await read_tree_state();
4131
- if (needs_reclaim(tree)) return decide5(tree, NO_CHILD_WORK, issue);
4132
- 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);
4133
4240
  }
4134
4241
  var run_preflight = {
4135
4242
  CLEAN_VERDICT,
@@ -4137,13 +4244,13 @@ var run_preflight = {
4137
4244
  MERGED_PR,
4138
4245
  STASH_LABEL_PREFIX,
4139
4246
  check: check2,
4140
- decide: decide5,
4247
+ decide: decide6,
4141
4248
  read_child_state,
4142
4249
  read_pr_state,
4143
4250
  to_pr_state
4144
4251
  };
4145
4252
 
4146
- // scripts/run/run-progress-read.ts
4253
+ // scripts/run/progress/run-progress-read.ts
4147
4254
  var { mark: mark2, parse_stamp: parse_stamp2, read_last_report: read_last_report2 } = run_progress_clock;
4148
4255
  var FIRST_LOAD_AVERAGE = 0;
4149
4256
  var IDLE_READ = { kind: "idle" };
@@ -4234,6 +4341,9 @@ var run_progress_read = {
4234
4341
  stamp_target
4235
4342
  };
4236
4343
 
4344
+ // scripts/run/run-watcher-hook.ts
4345
+ import { z as z12 } from "zod";
4346
+
4237
4347
  // scripts/run/run-watcher-guard.ts
4238
4348
  var WATCHER_TICK_MS = 3e4;
4239
4349
  var TICK_MULTIPLIER = 3;
@@ -4243,7 +4353,7 @@ var STALE_THRESHOLD_S = STALE_THRESHOLD_MS / MS_PER_SECOND3;
4243
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`;
4244
4354
  var OK_RESULT = { kind: "ok" };
4245
4355
  async function owes_watcher() {
4246
- if (!await run_headless.is_backlog_parent()) return false;
4356
+ if (run_headless.is_headless() || !await run_headless.is_backlog_parent()) return false;
4247
4357
  return await lane_registry.has_lanes_in_flight();
4248
4358
  }
4249
4359
  async function check3(life_target) {
@@ -4362,7 +4472,7 @@ function is_runtime_file(file_path, root) {
4362
4472
  if (relative.startsWith(PARENT_PREFIX) || path9.isAbsolute(relative)) return false;
4363
4473
  return !test_declared_logic.is_exempt(relative.split(path9.sep).join("/"));
4364
4474
  }
4365
- function is_candidate(raw_payload, root) {
4475
+ function is_candidate2(raw_payload, root) {
4366
4476
  const payload = hook_decision.parse_hook_payload(raw_payload);
4367
4477
  if (payload === void 0 || !EDIT_TOOLS3.has(payload.tool_name)) return void 0;
4368
4478
  const has_runtime_file = edited_paths(payload.tool_input).some(
@@ -4371,7 +4481,7 @@ function is_candidate(raw_payload, root) {
4371
4481
  return has_runtime_file ? payload.transcript_path : void 0;
4372
4482
  }
4373
4483
  function notice(raw_payload, root = process.cwd()) {
4374
- const transcript = is_candidate(raw_payload, root);
4484
+ const transcript = is_candidate2(raw_payload, root);
4375
4485
  if (transcript === void 0) return void 0;
4376
4486
  const target = STAMP.path(transcript);
4377
4487
  if (STAMP.last_ms(target) !== hook_decision.NEVER_MS) return void 0;