@joshuafolkken/kit 1.1455.0 → 1.1514.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (374) hide show
  1. package/.claude/settings.json +6 -6
  2. package/.claude/skills/dependency-update/SKILL.md +26 -55
  3. package/.claude/skills/epic-commands/SKILL.md +83 -156
  4. package/.claude/skills/epic-commands/epic-bundle.md +51 -79
  5. package/.claude/skills/epic-commands/execution-waves.md +43 -81
  6. package/.claude/skills/verify-ui/SKILL.md +24 -50
  7. package/.claude/skills/workflow-commands/SKILL.md +7 -9
  8. package/.claude/skills/workflow-commands/background-commands.md +31 -46
  9. package/.claude/skills/workflow-commands/backlogrun-child.md +65 -85
  10. package/.claude/skills/workflow-commands/backlogrun-lanes.md +86 -125
  11. package/.claude/skills/workflow-commands/backlogrun-park.md +68 -118
  12. package/.claude/skills/workflow-commands/backlogrun-progress.md +17 -28
  13. package/.claude/skills/workflow-commands/backlogrun-recovery.md +17 -26
  14. package/.claude/skills/workflow-commands/backlogrun-steps.md +35 -52
  15. package/.claude/skills/workflow-commands/backlogrun.md +31 -50
  16. package/.claude/skills/workflow-commands/chain-rule.md +31 -40
  17. package/.claude/skills/workflow-commands/delegation.md +12 -8
  18. package/.claude/skills/workflow-commands/entry-sequence.md +1 -3
  19. package/.claude/skills/workflow-commands/followup.md +55 -77
  20. package/.claude/skills/workflow-commands/fullrun-steps.md +3 -5
  21. package/.claude/skills/workflow-commands/fullrun.md +4 -6
  22. package/.claude/skills/workflow-commands/halfrun.md +3 -7
  23. package/.claude/skills/workflow-commands/into-target.md +1 -2
  24. package/.claude/skills/workflow-commands/issue-comments.md +43 -60
  25. package/.claude/skills/workflow-commands/issue-scout.md +3 -2
  26. package/.claude/skills/workflow-commands/kickoff.md +65 -74
  27. package/.claude/skills/workflow-commands/latest-gate.md +2 -6
  28. package/.claude/skills/workflow-commands/needs-human-review.md +2 -5
  29. package/.claude/skills/workflow-commands/observation-filing.md +59 -118
  30. package/.claude/skills/workflow-commands/observation-ledger.md +52 -112
  31. package/.claude/skills/workflow-commands/pre-gate-cut.md +88 -142
  32. package/.claude/skills/workflow-commands/prerequisite.md +11 -17
  33. package/.claude/skills/workflow-commands/progress-watcher.md +23 -33
  34. package/.claude/skills/workflow-commands/prrun.md +3 -5
  35. package/.claude/skills/workflow-commands/retrospective.md +35 -54
  36. package/.claude/skills/workflow-commands/split-assessment.md +32 -58
  37. package/.claude/skills/workflow-commands/target-repository.md +1 -2
  38. package/.claude/skills/workflow-commands/working-tree-hold.md +54 -73
  39. package/.codex/hooks.json +6 -6
  40. package/.github/actions/setup-node/action.yml +1 -1
  41. package/.github/workflows/ci.yml +1 -1
  42. package/.github/workflows/publish.yml +2 -2
  43. package/CLAUDE.md +7 -6
  44. package/cspell/index.yaml +7 -0
  45. package/dist/CLAUDE.md +7 -6
  46. package/dist/commands/document-section-cli.js +67 -34
  47. package/dist/hooks/batch-guard.js +6 -6
  48. package/dist/hooks/chunk-4KKD2UTK.js +66 -0
  49. package/dist/hooks/{chunk-GPLGZ6IC.js → chunk-6TS2JD6D.js} +14 -3
  50. package/dist/hooks/chunk-7DSBJVFH.js +50 -0
  51. package/dist/hooks/{chunk-5H4SSDSD.js → chunk-A65Y4YOR.js} +3 -3
  52. package/dist/hooks/{chunk-3C4XRYG6.js → chunk-APUO6U4J.js} +102 -19
  53. package/dist/hooks/{chunk-IX6TTTI6.js → chunk-EFXE6C37.js} +8 -22
  54. package/dist/hooks/chunk-GP4IEC4O.js +20 -0
  55. package/dist/hooks/chunk-JP45SOCJ.js +56 -0
  56. package/dist/hooks/{chunk-OLMO72ON.js → chunk-LAOV7NLM.js} +1 -1
  57. package/dist/hooks/chunk-M4HGZV7U.js +83 -0
  58. package/dist/hooks/{chunk-TBJFTSUY.js → chunk-O6GDRNJB.js} +9 -7
  59. package/dist/hooks/{chunk-HFGWYGUV.js → chunk-PF2NZL2A.js} +5 -5
  60. package/dist/hooks/{chunk-WNLVGT7E.js → chunk-PTICX4OL.js} +4 -5
  61. package/dist/hooks/{chunk-HHI6FO3R.js → chunk-RT27QRJW.js} +763 -1515
  62. package/dist/hooks/{chunk-7WQBPLUN.js → chunk-SYOTDYCI.js} +1149 -1095
  63. package/dist/hooks/{chunk-LIAFCXL7.js → chunk-TNUMY6NL.js} +1519 -1332
  64. package/dist/hooks/chunk-UPUZF3AN.js +279 -0
  65. package/dist/hooks/{chunk-Z4XI7CZS.js → chunk-WK6SMBIC.js} +1932 -784
  66. package/dist/hooks/codex-hook-adapter.js +17 -14
  67. package/dist/hooks/duplicate-read-guard.js +7 -7
  68. package/dist/hooks/format-edited.js +5 -4
  69. package/dist/hooks/inputs.json +302 -247
  70. package/dist/hooks/investigation-guard.js +8 -11
  71. package/dist/hooks/pretool-guard.js +16 -13
  72. package/dist/hooks/session-lang.js +372 -26
  73. package/dist/hooks/stop-guard.js +2593 -189
  74. package/dist/josh.js +2128 -282
  75. package/dist/managed-marker/index.js +1 -2
  76. package/dist/version/index.js +15 -12
  77. package/eslint/base.js +2 -1
  78. package/eslint/rules/unicorn.js +16 -0
  79. package/lefthook/base.yml +5 -3
  80. package/package.json +14 -12
  81. package/prompts/coding-standards.md +12 -22
  82. package/prompts/collaboration-workflow/file-edits.md +25 -45
  83. package/prompts/collaboration-workflow/gh-rest.md +19 -36
  84. package/prompts/collaboration-workflow/glossary.md +5 -7
  85. package/prompts/collaboration-workflow/issue-citation.md +6 -14
  86. package/prompts/collaboration-workflow/issue-template.md +40 -32
  87. package/prompts/collaboration-workflow/lane-instructions.md +35 -0
  88. package/prompts/collaboration-workflow/operating-rules.md +33 -46
  89. package/prompts/collaboration-workflow/output-bounds.md +15 -15
  90. package/prompts/collaboration-workflow/overview.md +14 -19
  91. package/prompts/collaboration-workflow/plan-comment.md +13 -13
  92. package/prompts/collaboration-workflow/principles.md +57 -54
  93. package/prompts/collaboration-workflow/proposal-request.md +1 -9
  94. package/prompts/collaboration-workflow/report-format.md +41 -49
  95. package/prompts/collaboration-workflow/residency.md +15 -11
  96. package/prompts/collaboration-workflow/rule-delivery.md +7 -15
  97. package/prompts/collaboration-workflow/shell-body.md +10 -20
  98. package/prompts/collaboration-workflow/turn-batching.md +8 -14
  99. package/prompts/collaboration-workflow/upstream-interrupt.md +26 -34
  100. package/prompts/collaboration-workflow/wip-cap.md +26 -32
  101. package/prompts/collaboration-workflow.md +25 -25
  102. package/prompts/refactoring.md +33 -43
  103. package/prompts/review-rubric.md +84 -129
  104. package/prompts/review.md +110 -141
  105. package/prompts/sonar-hotspot-handling.md +22 -50
  106. package/prompts/testing-guide.md +48 -99
  107. package/scripts/adopt/adopt-toolkits.ts +2 -4
  108. package/scripts/agent/agent-argv.ts +20 -4
  109. package/scripts/agent/agent-exit-record.ts +23 -1
  110. package/scripts/agent/agent-role-profile.ts +101 -45
  111. package/scripts/agent/agent-safe-text.ts +60 -0
  112. package/scripts/agent/claude-agent-argv.ts +14 -0
  113. package/scripts/agent/codex-agent-argv.ts +9 -2
  114. package/scripts/agent/lane-instructions.ts +144 -0
  115. package/scripts/backlog/backlog-budget-cli.ts +1 -2
  116. package/scripts/backlog/backlog-budget.ts +4 -4
  117. package/scripts/backlog/backlog-drive-carried.ts +26 -0
  118. package/scripts/backlog/backlog-drive-cli.ts +10 -5
  119. package/scripts/backlog/backlog-drive-named.ts +3 -5
  120. package/scripts/backlog/backlog-drive-restore.ts +28 -2
  121. package/scripts/backlog/backlog-drive-seats.ts +27 -0
  122. package/scripts/backlog/backlog-drive.ts +11 -6
  123. package/scripts/backlog/backlog-occupied.ts +56 -0
  124. package/scripts/backlog/backlog-ready.ts +59 -15
  125. package/scripts/behavior/behavior-rules.ts +3 -2
  126. package/scripts/cost-runtime/codex-usage.ts +4 -2
  127. package/scripts/cost-runtime/cost-cli.ts +1 -2
  128. package/scripts/delegation/duplicate-read-guard.ts +2 -2
  129. package/scripts/delegation/investigation-guard.ts +15 -65
  130. package/scripts/delegation/investigation-reads.ts +1 -2
  131. package/scripts/discovery/repo-discovery.ts +4 -2
  132. package/scripts/discovery/repo-origin.ts +2 -4
  133. package/scripts/doctor/doctor-logic.ts +6 -3
  134. package/scripts/document/document-byte-budget.ts +28 -27
  135. package/scripts/document/document-byte-check.ts +1 -2
  136. package/scripts/document/document-section-cli.ts +17 -5
  137. package/scripts/document/entry-read-budget.ts +3 -3
  138. package/scripts/document/read-set-cli.ts +4 -1
  139. package/scripts/epic/epic-audit-orphans.ts +5 -8
  140. package/scripts/epic/epic-audit-rationale.ts +8 -2
  141. package/scripts/epic/epic-audit.ts +52 -1
  142. package/scripts/epic/epic-bundle-cli.ts +1 -2
  143. package/scripts/epic/epic-bundle-evidence.ts +49 -26
  144. package/scripts/epic/epic-bundle-referenced.ts +14 -3
  145. package/scripts/epic/epic-bundle.ts +17 -9
  146. package/scripts/epic/epic-chains.ts +6 -2
  147. package/scripts/epic/epic-close-comment.ts +1 -2
  148. package/scripts/followup/git-followup-workflow.ts +19 -1
  149. package/scripts/gate/check-inputs.ts +129 -0
  150. package/scripts/gate/check-reuse.ts +162 -0
  151. package/scripts/gate/core-budget.ts +93 -44
  152. package/scripts/gate/core-wait.ts +130 -0
  153. package/scripts/gate/free-cores.ts +49 -0
  154. package/scripts/gate/gate-ledger.ts +17 -3
  155. package/scripts/gate/gate-plan.ts +23 -1
  156. package/scripts/gate/gate-report.ts +15 -2
  157. package/scripts/gate/gate-run-steps.ts +150 -0
  158. package/scripts/gate/held-mark.ts +55 -0
  159. package/scripts/gate/machine-capacity.ts +70 -11
  160. package/scripts/gate/pre-commit-type-check.ts +14 -1
  161. package/scripts/gate/verification-gate.ts +65 -127
  162. package/scripts/gh/git-gh-issue-rest.ts +1 -0
  163. package/scripts/gh/git-gh-issue-write.ts +24 -2
  164. package/scripts/gh/git-pr.ts +19 -3
  165. package/scripts/git/git-common-directory.ts +3 -3
  166. package/scripts/git/git-diff-reads.ts +16 -9
  167. package/scripts/git/git-execa-error.ts +1 -2
  168. package/scripts/git/main-merge-regenerate.ts +78 -0
  169. package/scripts/git/main-merge.ts +14 -5
  170. package/scripts/hooks/batch-guard.ts +5 -8
  171. package/scripts/hooks/format-edited-file.ts +10 -4
  172. package/scripts/hooks/pre-commit-lint.ts +91 -0
  173. package/scripts/hooks/pre-push-unit.ts +15 -4
  174. package/scripts/hooks/pretool-guard.ts +48 -79
  175. package/scripts/hooks/reserved-run.ts +1 -0
  176. package/scripts/hooks/run-hook.sh +10 -12
  177. package/scripts/hooks/step-zero-notice.ts +3 -2
  178. package/scripts/hooks/stop-actions.ts +204 -0
  179. package/scripts/hooks/stop-guard.ts +62 -21
  180. package/scripts/init/hook-command-rewrite.ts +6 -2
  181. package/scripts/init/hook-launch.ts +12 -7
  182. package/scripts/init/init-logic-deploy-vps.ts +4 -2
  183. package/scripts/issue/behavior-change-lint.ts +12 -6
  184. package/scripts/issue/expected-effect-lint.ts +59 -0
  185. package/scripts/issue/issue-cite-cli.ts +35 -15
  186. package/scripts/issue/issue-classification.ts +1 -1
  187. package/scripts/issue/issue-lint-cli.ts +5 -3
  188. package/scripts/issue/issue-scout-cli.ts +22 -16
  189. package/scripts/issue/markdown-section.ts +19 -5
  190. package/scripts/josh/command-usage.ts +110 -0
  191. package/scripts/josh/file-map-stamp.ts +1 -3
  192. package/scripts/josh/hook-decision.ts +17 -2
  193. package/scripts/josh/josh-command-map.ts +2 -0
  194. package/scripts/josh/josh-command-types.ts +55 -9
  195. package/scripts/josh/josh-commands-ai.ts +13 -0
  196. package/scripts/josh/josh-commands-development.ts +1 -1
  197. package/scripts/josh/josh-commands-hooks.ts +6 -0
  198. package/scripts/josh/josh-commands-lane.ts +10 -1
  199. package/scripts/josh/josh-commands-workflow.ts +5 -0
  200. package/scripts/josh/josh-logic.ts +6 -15
  201. package/scripts/josh/josh-usage-automation.ts +121 -0
  202. package/scripts/josh/josh-usage-backlog.ts +180 -0
  203. package/scripts/josh/josh-usage-developer.ts +230 -0
  204. package/scripts/josh/josh-usage-hooks.ts +38 -0
  205. package/scripts/josh/josh-usage-issue.ts +205 -0
  206. package/scripts/josh/josh-usage-review.ts +101 -0
  207. package/scripts/josh/josh-usage-run.ts +252 -0
  208. package/scripts/josh/josh-usage-session.ts +238 -0
  209. package/scripts/josh/josh-usage-workflow.ts +212 -0
  210. package/scripts/josh/josh.ts +14 -5
  211. package/scripts/josh/package-directory.ts +20 -0
  212. package/scripts/lane/lane-await.ts +3 -2
  213. package/scripts/lane/lane-capacity.ts +8 -2
  214. package/scripts/lane/lane-check-stats.ts +56 -0
  215. package/scripts/lane/lane-child-invocation.ts +49 -5
  216. package/scripts/lane/lane-dispatch.ts +3 -3
  217. package/scripts/lane/lane-guard-policy.ts +5 -5
  218. package/scripts/lane/lane-handoff.ts +8 -1
  219. package/scripts/lane/lane-install.ts +12 -4
  220. package/scripts/lane/lane-ledger.ts +52 -1
  221. package/scripts/lane/lane-limit-cli.ts +8 -9
  222. package/scripts/lane/lane-load.ts +9 -2
  223. package/scripts/lane/lane-measure-cli.ts +4 -0
  224. package/scripts/lane/lane-parked.ts +18 -0
  225. package/scripts/lane/lane-preserve.ts +29 -1
  226. package/scripts/lane/lane-process-load.ts +125 -0
  227. package/scripts/lane/lane-process-stats.ts +93 -0
  228. package/scripts/lane/lane-registry.ts +17 -10
  229. package/scripts/lane/lane-restart.ts +5 -26
  230. package/scripts/lane/lane-session-action.ts +146 -0
  231. package/scripts/lane/lane-session-broker.ts +68 -125
  232. package/scripts/lane/lane-spent.ts +14 -3
  233. package/scripts/lane/lane-stats.ts +1 -0
  234. package/scripts/lane/lane-withdraw-cli.ts +71 -0
  235. package/scripts/lane/lane-withdraw.ts +182 -0
  236. package/scripts/lint/format.ts +4 -4
  237. package/scripts/lint/lint-parallel.ts +10 -3
  238. package/scripts/lint/lint-related-scope.ts +4 -1
  239. package/scripts/managed-marker/managed-marker-logic.ts +1 -2
  240. package/scripts/notify/telegram-test.ts +10 -5
  241. package/scripts/overrides/lockfile-overrides.ts +1 -3
  242. package/scripts/propagate/propagate.ts +1 -2
  243. package/scripts/release/release-progress.ts +3 -2
  244. package/scripts/report/report-format-reference.ts +2 -0
  245. package/scripts/report/report-lint.ts +7 -4
  246. package/scripts/review/acceptance-map-check.ts +61 -0
  247. package/scripts/review/acceptance-map.ts +205 -0
  248. package/scripts/review/review-brief-cli.ts +1 -1
  249. package/scripts/review/review-record-cli.ts +6 -2
  250. package/scripts/rules/agent-model.ts +59 -0
  251. package/scripts/rules/delivered-rules.ts +11 -2
  252. package/scripts/rules/destructive-command.ts +6 -9
  253. package/scripts/rules/direct-filing.ts +3 -7
  254. package/scripts/rules/early-heartbeat.ts +7 -18
  255. package/scripts/rules/file-body.ts +6 -13
  256. package/scripts/rules/filing-cap.ts +4 -6
  257. package/scripts/rules/firing-point.ts +6 -5
  258. package/scripts/rules/git-force.ts +5 -9
  259. package/scripts/rules/guard-remedy.ts +117 -0
  260. package/scripts/rules/implementation-cut.ts +37 -35
  261. package/scripts/rules/index-guard.ts +4 -7
  262. package/scripts/rules/issue-comments.ts +67 -16
  263. package/scripts/rules/josh-git-bare.ts +3 -6
  264. package/scripts/rules/lane-background.ts +10 -21
  265. package/scripts/rules/lane-carry-conflict.ts +7 -17
  266. package/scripts/rules/lane-interactive-ask.ts +6 -11
  267. package/scripts/rules/lane-park.ts +5 -13
  268. package/scripts/rules/lane-split-park.ts +2 -7
  269. package/scripts/rules/lane-switch-main.ts +9 -12
  270. package/scripts/rules/last-prompt.ts +50 -5
  271. package/scripts/rules/oracle-consulted.ts +4 -5
  272. package/scripts/rules/oracle-list-cli.ts +7 -0
  273. package/scripts/rules/piped-verification.ts +84 -32
  274. package/scripts/rules/poll-loop.ts +7 -17
  275. package/scripts/rules/pre-gate-cut.ts +7 -21
  276. package/scripts/rules/prior-comment-read.ts +7 -5
  277. package/scripts/rules/protected-files.ts +5 -9
  278. package/scripts/rules/raw-field-body.ts +3 -7
  279. package/scripts/rules/rule-body-guard.ts +4 -11
  280. package/scripts/rules/rule-list.ts +4 -1
  281. package/scripts/rules/rule-registry-rows.ts +10 -0
  282. package/scripts/rules/rule-registry.ts +7 -4
  283. package/scripts/rules/rule-rewrite.ts +114 -0
  284. package/scripts/rules/rule-value.ts +7 -1
  285. package/scripts/rules/run-tail-rule.ts +6 -22
  286. package/scripts/rules/shell-body-trigger.ts +4 -6
  287. package/scripts/rules/shell-segments.ts +22 -0
  288. package/scripts/rules/stop-decision.ts +77 -0
  289. package/scripts/rules/stop-rules.ts +164 -77
  290. package/scripts/rules/tail-commands.ts +15 -1
  291. package/scripts/rules/test-declared-commit.ts +4 -10
  292. package/scripts/rules/third-party-write.ts +5 -10
  293. package/scripts/rules/worktree-guard.ts +5 -12
  294. package/scripts/run/board/run-board-cli.ts +2 -0
  295. package/scripts/run/board/run-board-closed.ts +8 -2
  296. package/scripts/run/board/run-board-github.ts +19 -3
  297. package/scripts/run/board/run-board-header.ts +4 -1
  298. package/scripts/run/board/run-board-idle.ts +31 -0
  299. package/scripts/run/board/run-board-labels.ts +2 -0
  300. package/scripts/run/board/run-board-layout.ts +1 -1
  301. package/scripts/run/board/run-board-read.ts +25 -12
  302. package/scripts/run/board/run-board-reason.ts +17 -4
  303. package/scripts/run/board/run-board-render-legend.ts +1 -0
  304. package/scripts/run/board/run-board-render-slot.ts +1 -1
  305. package/scripts/run/board/run-board-render-waits.ts +41 -0
  306. package/scripts/run/board/run-board-render.ts +6 -1
  307. package/scripts/run/board/run-board-row-yield.ts +1 -0
  308. package/scripts/run/board/run-board-state.ts +9 -0
  309. package/scripts/run/board/run-board-status.ts +89 -23
  310. package/scripts/run/board/run-board-thanks.ts +7 -2
  311. package/scripts/run/board/run-board-tick.ts +37 -8
  312. package/scripts/run/carry/run-carry-args.ts +1 -3
  313. package/scripts/run/carry/run-carry-conversation.ts +7 -2
  314. package/scripts/run/cut/run-cut-cli.ts +18 -22
  315. package/scripts/run/cut/run-cut-decline.ts +72 -0
  316. package/scripts/run/cut/run-cut-ready.ts +74 -0
  317. package/scripts/run/cut/run-cut-report.ts +29 -0
  318. package/scripts/run/cut/run-cut.ts +10 -1
  319. package/scripts/run/detached-launch.ts +23 -4
  320. package/scripts/run/entry/run-entry-cli.ts +8 -1
  321. package/scripts/run/event/run-event-render.ts +1 -0
  322. package/scripts/run/event/run-event-scope.ts +19 -1
  323. package/scripts/run/event/run-event-stream-emit.ts +13 -6
  324. package/scripts/run/event/run-event-stream.ts +4 -0
  325. package/scripts/run/hold/run-hold-cli.ts +14 -3
  326. package/scripts/run/hold/run-hold-session.ts +46 -0
  327. package/scripts/run/hold/run-hold.ts +13 -4
  328. package/scripts/run/launch-priority.ts +32 -0
  329. package/scripts/run/merge/run-merge-cli.ts +16 -42
  330. package/scripts/run/merge/run-merge-ship.ts +59 -1
  331. package/scripts/run/merge/run-merge-signals.ts +99 -0
  332. package/scripts/run/merge/run-merge-steps.ts +11 -1
  333. package/scripts/run/merge/run-merge.ts +22 -9
  334. package/scripts/run/run-ending.ts +31 -3
  335. package/scripts/run/run-liveness.ts +1 -1
  336. package/scripts/run/run-parent-cut-hook.ts +5 -10
  337. package/scripts/run/run-poll-hook.ts +157 -0
  338. package/scripts/run/run-preflight.ts +1 -2
  339. package/scripts/run/run-step-cli.ts +22 -15
  340. package/scripts/run/run-step-section.ts +85 -0
  341. package/scripts/run/run-stop-notify.ts +1 -2
  342. package/scripts/run/run-watcher-guard.ts +6 -3
  343. package/scripts/run/ship/run-ship-cli.ts +5 -12
  344. package/scripts/run/ship/run-ship-detach.ts +9 -0
  345. package/scripts/run/ship/run-ship-next.ts +10 -2
  346. package/scripts/run/ship/run-ship-overlap.ts +195 -0
  347. package/scripts/run/ship/run-ship-pre-detach.ts +33 -15
  348. package/scripts/run/ship/run-ship-preflight.ts +28 -2
  349. package/scripts/run/ship/run-ship-return.ts +31 -7
  350. package/scripts/run/ship/run-ship-review-steps.ts +25 -14
  351. package/scripts/run/ship/run-ship-review.ts +15 -2
  352. package/scripts/run/ship/run-ship-steps.ts +1 -1
  353. package/scripts/run/ship/run-ship-stop-context.ts +206 -0
  354. package/scripts/run/ship/run-ship-sync.ts +14 -6
  355. package/scripts/run/tidy/run-tidy-cli.ts +3 -3
  356. package/scripts/run/tidy/run-tidy-lanes.ts +3 -2
  357. package/scripts/safe-chain/project-config.ts +7 -2
  358. package/scripts/sync/sync-hook-safety.ts +8 -2
  359. package/scripts/test/test-e2e-guard.ts +30 -5
  360. package/scripts/test/test-unit-guard.ts +16 -1
  361. package/scripts/test/unit-worker-share.ts +26 -3
  362. package/scripts/time-runtime/time-batch-guard.ts +3 -3
  363. package/scripts/time-runtime/time-hook-transcript.ts +3 -2
  364. package/scripts/time-runtime/time-shell.ts +39 -4
  365. package/scripts/time-runtime/time-spans.ts +1 -0
  366. package/scripts/time-runtime/time-tool-call.ts +7 -0
  367. package/scripts/version/effective-upstream.ts +3 -4
  368. package/scripts/version/kit-version-config.ts +8 -2
  369. package/scripts/version/latest-corepack.ts +1 -2
  370. package/scripts/version/release-hold.ts +11 -4
  371. package/scripts/version/version-commands.ts +12 -4
  372. package/templates/gitignore +6 -2
  373. package/dist/hooks/chunk-D5QBTOCZ.js +0 -68
  374. package/dist/hooks/chunk-UT7YC3N2.js +0 -56
@@ -1,102 +1,110 @@
1
1
  import {
2
2
  duplicate_read_outcome
3
- } from "./chunk-HFGWYGUV.js";
3
+ } from "./chunk-PF2NZL2A.js";
4
4
  import {
5
5
  ALREADY_DONE_LABEL,
6
6
  CONTEXT_CUT_THRESHOLD,
7
7
  IN_PROGRESS_LABEL,
8
8
  NEEDS_DECISION_LABEL,
9
- NEEDS_HUMAN_REVIEW_LABEL,
10
9
  bash_triggers,
10
+ cost_cli,
11
+ cost_verdict,
11
12
  filing_cap,
12
- git_gh_api_path,
13
13
  git_gh_command,
14
- git_gh_exec,
15
14
  git_gh_issue_read,
16
- git_gh_pr_read,
17
15
  has_any_label,
18
- has_stdout_field,
16
+ interactive_ask,
19
17
  issue_watch_diff,
20
18
  lane_background,
21
19
  lane_park,
22
- parse_json_object_or_undefined,
23
- read_json_listing,
20
+ pre_gate_cut,
24
21
  repo_party,
25
22
  run_cut,
26
23
  run_cut_handoff,
27
- run_hold,
24
+ run_liveness,
28
25
  run_tail_rule,
29
- session_cite,
30
26
  shell_segments,
31
27
  tail_commands
32
- } from "./chunk-7WQBPLUN.js";
28
+ } from "./chunk-SYOTDYCI.js";
33
29
  import {
34
- ancestor_directories,
35
30
  find_local_bin_upwards,
36
31
  json_object_schema,
37
32
  string_array_schema,
38
33
  string_record_schema,
39
34
  vscode_settings_schema
40
- } from "./chunk-IX6TTTI6.js";
35
+ } from "./chunk-EFXE6C37.js";
36
+ import {
37
+ distributed_paths
38
+ } from "./chunk-M4HGZV7U.js";
41
39
  import {
42
- agent_role_profile,
43
- agent_session_role,
44
40
  git_common_directory,
41
+ git_gh_api_path,
42
+ git_gh_exec,
43
+ has_stdout_field,
45
44
  issue_cite,
46
45
  lane_registry,
46
+ read_json_listing,
47
47
  repo_origin,
48
48
  run_event_scope,
49
49
  run_event_stream,
50
50
  run_event_stream_emit,
51
51
  run_headless,
52
+ run_hold,
53
+ run_preflight,
54
+ session_cite,
52
55
  session_language
53
- } from "./chunk-Z4XI7CZS.js";
56
+ } from "./chunk-WK6SMBIC.js";
57
+ import {
58
+ ancestor_directories
59
+ } from "./chunk-GP4IEC4O.js";
54
60
  import {
55
61
  error_text
56
62
  } from "./chunk-CDY4NFQG.js";
57
63
  import {
58
64
  batch_outcome
59
- } from "./chunk-5H4SSDSD.js";
65
+ } from "./chunk-A65Y4YOR.js";
60
66
  import {
61
- investigation_refusal
62
- } from "./chunk-D5QBTOCZ.js";
67
+ investigation_outcome
68
+ } from "./chunk-7DSBJVFH.js";
63
69
  import {
64
70
  investigation_reads
65
- } from "./chunk-WNLVGT7E.js";
71
+ } from "./chunk-PTICX4OL.js";
72
+ import {
73
+ agent_role_profile,
74
+ agent_session_role
75
+ } from "./chunk-UPUZF3AN.js";
66
76
  import {
67
77
  lane_guard_policy
68
- } from "./chunk-UT7YC3N2.js";
78
+ } from "./chunk-JP45SOCJ.js";
69
79
  import {
70
80
  hook_decision
71
- } from "./chunk-GPLGZ6IC.js";
81
+ } from "./chunk-6TS2JD6D.js";
72
82
  import {
73
- cost_attribute,
74
83
  cost_blocks,
75
- open_issue_schema,
76
84
  time_batch_guard,
77
85
  time_density_hook,
78
- time_shell
79
- } from "./chunk-LIAFCXL7.js";
86
+ time_hook_transcript,
87
+ time_shell,
88
+ time_spans
89
+ } from "./chunk-TNUMY6NL.js";
80
90
  import {
81
91
  lane_child_marker
82
- } from "./chunk-OLMO72ON.js";
92
+ } from "./chunk-LAOV7NLM.js";
83
93
  import {
84
94
  GIT_LOCATION_VARIABLES,
85
95
  PORCELAIN_FLAG,
86
- cli_flags,
87
- cost_transcript,
88
- cost_usage,
96
+ UNTRACKED_FILES_FLAG,
89
97
  file_reader,
90
98
  git_command,
99
+ git_diff_reads,
91
100
  git_spawn_sync,
92
101
  issue_number_shape,
93
102
  json_value,
94
103
  lane_paths,
95
- own_session,
104
+ open_issue_schema,
96
105
  run_carry
97
- } from "./chunk-3C4XRYG6.js";
106
+ } from "./chunk-APUO6U4J.js";
98
107
  import {
99
- PLATFORM_TEMP_ROOT,
100
108
  PROBE_TIMEOUT_MS,
101
109
  PROJECT_ROOT,
102
110
  package_path,
@@ -114,9 +122,9 @@ var HEADER_BODY_SEPARATOR = /\r?\n\r?\n/u;
114
122
  var ETAG_HEADER = /^etag: *(?<etag>[^\r\n]+)/imu;
115
123
  var UNCHANGED = { kind: "unchanged" };
116
124
  var UNREADABLE = { kind: "unreadable" };
117
- function to_args(path16, etag) {
125
+ function to_args(path13, etag) {
118
126
  const header = etag === void 0 ? [] : [HEADER_FLAG, `${IF_NONE_MATCH}${etag}`];
119
- return ["api", INCLUDE_FLAG, ...header, path16];
127
+ return ["api", INCLUDE_FLAG, ...header, path13];
120
128
  }
121
129
  function to_changed(output) {
122
130
  const [headers = "", ...rest] = output.split(HEADER_BODY_SEPARATOR);
@@ -130,9 +138,9 @@ function failure_status(error) {
130
138
  const cause = error instanceof Error ? error.cause : void 0;
131
139
  return has_stdout_field(cause) ? git_gh_exec.parse_status_line(cause.stdout) : void 0;
132
140
  }
133
- async function read_conditional(path16, etag, timeout_ms) {
141
+ async function read_conditional(path13, etag, timeout_ms) {
134
142
  try {
135
- return to_read(await git_gh_exec.exec_gh_command(to_args(path16, etag), timeout_ms));
143
+ return to_read(await git_gh_exec.exec_gh_command(to_args(path13, etag), timeout_ms));
136
144
  } catch (error) {
137
145
  return failure_status(error) === NOT_MODIFIED_STATUS ? UNCHANGED : UNREADABLE;
138
146
  }
@@ -162,9 +170,9 @@ function read_record(root) {
162
170
  const raw = stamp_file.read_stamp_text(record_path(root));
163
171
  return raw === void 0 ? void 0 : json_value.parse_with(raw, record_schema);
164
172
  }
165
- function write_record(record, root) {
166
- stamp_file.replace_stamp(record_path(root), record);
167
- return record;
173
+ function write_record(record2, root) {
174
+ stamp_file.replace_stamp(record_path(root), record2);
175
+ return record2;
168
176
  }
169
177
  function clear(root) {
170
178
  stamp_file.remove_stamp(record_path(root));
@@ -204,44 +212,44 @@ async function record_baseline_here(issue) {
204
212
  async function clear_here() {
205
213
  clear(await git_command.repository_root());
206
214
  }
207
- function with_fetched(record, fetched, checked_at) {
208
- if (fetched.kind !== "read") return { ...record, checked_at };
209
- const pending = issue_watch_diff.unread_diff(record, fetched.snapshot);
210
- return { ...record, checked_at, etag: fetched.etag, pending };
215
+ function with_fetched(record2, fetched, checked_at) {
216
+ if (fetched.kind !== "read") return { ...record2, checked_at };
217
+ const pending = issue_watch_diff.unread_diff(record2, fetched.snapshot);
218
+ return { ...record2, checked_at, etag: fetched.etag, pending };
211
219
  }
212
- function is_due(record, options, now) {
213
- return now.getTime() - Date.parse(record.checked_at) >= options.interval_ms;
220
+ function is_due(record2, options, now) {
221
+ return now.getTime() - Date.parse(record2.checked_at) >= options.interval_ms;
214
222
  }
215
- async function refresh(record, options, now, root) {
223
+ async function refresh(record2, options, now, root) {
216
224
  const fetched = await fetch_issue({
217
- issue: record.issue,
218
- etag: record.etag,
225
+ issue: record2.issue,
226
+ etag: record2.etag,
219
227
  timeout_ms: options.timeout_ms
220
228
  });
221
229
  if (fetched.kind === "closed") {
222
230
  clear(root);
223
231
  return void 0;
224
232
  }
225
- return write_record(with_fetched(record, fetched, now.toISOString()), root);
233
+ return write_record(with_fetched(record2, fetched, now.toISOString()), root);
226
234
  }
227
235
  async function poll(options, root) {
228
- const record = read_record(root);
236
+ const record2 = read_record(root);
229
237
  const now = options.now ?? /* @__PURE__ */ new Date();
230
- if (record === void 0 || !is_due(record, options, now)) return record;
231
- return await refresh(record, options, now, root);
238
+ if (record2 === void 0 || !is_due(record2, options, now)) return record2;
239
+ return await refresh(record2, options, now, root);
232
240
  }
233
- function mark_shown(record, root) {
234
- return write_record({ ...record, shown: record.pending }, root);
241
+ function mark_shown(record2, root) {
242
+ return write_record({ ...record2, shown: record2.pending }, root);
235
243
  }
236
244
  async function acknowledge(root, now = /* @__PURE__ */ new Date()) {
237
- const record = read_record(root);
238
- if (record === void 0) return void 0;
239
- const fetched = await fetch_issue({ issue: record.issue });
245
+ const record2 = read_record(root);
246
+ if (record2 === void 0) return void 0;
247
+ const fetched = await fetch_issue({ issue: record2.issue });
240
248
  if (fetched.kind !== "read") return void 0;
241
- const diff = issue_watch_diff.unread_diff(record, fetched.snapshot);
242
- const acknowledged_at = diff === void 0 ? record.acknowledged_at : now.toISOString();
243
- write_record(fresh_record(record.issue, fetched, now, acknowledged_at), root);
244
- return { issue: record.issue, diff };
249
+ const diff = issue_watch_diff.unread_diff(record2, fetched.snapshot);
250
+ const acknowledged_at = diff === void 0 ? record2.acknowledged_at : now.toISOString();
251
+ write_record(fresh_record(record2.issue, fetched, now, acknowledged_at), root);
252
+ return { issue: record2.issue, diff };
245
253
  }
246
254
  var issue_watch = {
247
255
  acknowledge,
@@ -444,16 +452,16 @@ var EVERY_CALL_MS = 0;
444
452
  function ack_command(issue) {
445
453
  return `pnpm josh issue:ack ${issue}`;
446
454
  }
447
- function unread_message(record) {
455
+ function unread_message(record2) {
448
456
  return [
449
- `A person updated issue ${session_cite.issue(record.issue)} after this run read it, and the run has not taken the update in:`,
450
- record.pending ?? "",
451
- `Read it, compare it with the acceptance criteria, fix what it changes, then run \`${ack_command(record.issue)}\` to record that it was read.`
457
+ `A person updated issue ${session_cite.issue(record2.issue)} after this run read it, and the run has not taken the update in:`,
458
+ record2.pending ?? "",
459
+ `Read it, compare it with the acceptance criteria, fix what it changes, then run \`${ack_command(record2.issue)}\` to record that it was read.`
452
460
  ].join("\n\n");
453
461
  }
454
- function review_message(record, briefed_at) {
462
+ function review_message(record2, briefed_at) {
455
463
  return [
456
- `Issue ${session_cite.issue(record.issue)} was updated after the review was briefed (taken in at ${record.acknowledged_at}, briefed at ${briefed_at}), so the review read the earlier specification.`,
464
+ `Issue ${session_cite.issue(record2.issue)} was updated after the review was briefed (taken in at ${record2.acknowledged_at}, briefed at ${briefed_at}), so the review read the earlier specification.`,
457
465
  "Re-run `/code-review` with a fresh `pnpm josh review:brief`, so the review reads the change against the updated issue."
458
466
  ].join("\n");
459
467
  }
@@ -461,16 +469,16 @@ async function polled(root) {
461
469
  return await issue_watch.poll({ interval_ms: EVERY_CALL_MS }, root);
462
470
  }
463
471
  async function unread_refusal(root) {
464
- const record = await polled(root);
465
- return record?.pending === void 0 ? void 0 : unread_message(record);
472
+ const record2 = await polled(root);
473
+ return record2?.pending === void 0 ? void 0 : unread_message(record2);
466
474
  }
467
475
  async function review_refusal(root) {
468
- const record = await polled(root);
469
- if (record === void 0) return void 0;
470
- if (record.pending !== void 0) return unread_message(record);
476
+ const record2 = await polled(root);
477
+ if (record2 === void 0) return void 0;
478
+ if (record2.pending !== void 0) return unread_message(record2);
471
479
  const pointer = review_attest.read_pointer(root);
472
- if (pointer === void 0 || record.acknowledged_at <= pointer.taken_at) return void 0;
473
- return review_message(record, pointer.taken_at);
480
+ if (pointer === void 0 || record2.acknowledged_at <= pointer.taken_at) return void 0;
481
+ return review_message(record2, pointer.taken_at);
474
482
  }
475
483
  async function review_refusal_here() {
476
484
  return await review_refusal(await git_command.repository_root());
@@ -490,18 +498,18 @@ var issue_watch_gate = {
490
498
  // scripts/issue/issue-watch-hook.ts
491
499
  var POLL_INTERVAL_MS = 18e4;
492
500
  var POLL_TIMEOUT_MS = 5e3;
493
- function is_new_to_run(record) {
494
- return record?.pending !== void 0 && record.pending !== record.shown;
501
+ function is_new_to_run(record2) {
502
+ return record2?.pending !== void 0 && record2.pending !== record2.shown;
495
503
  }
496
504
  async function new_update_reason(now) {
497
- const record = await issue_watch.poll({
505
+ const record2 = await issue_watch.poll({
498
506
  interval_ms: POLL_INTERVAL_MS,
499
507
  timeout_ms: POLL_TIMEOUT_MS,
500
508
  now
501
509
  });
502
- if (!is_new_to_run(record)) return void 0;
503
- issue_watch.mark_shown(record);
504
- return issue_watch_gate.unread_message(record);
510
+ if (!is_new_to_run(record2)) return void 0;
511
+ issue_watch.mark_shown(record2);
512
+ return issue_watch_gate.unread_message(record2);
505
513
  }
506
514
  async function watch_reason(now = /* @__PURE__ */ new Date()) {
507
515
  try {
@@ -540,12 +548,41 @@ async function mark_implement(directory = process.cwd(), source = process.env) {
540
548
  }
541
549
  var lane_phase = { mark_implement };
542
550
 
551
+ // scripts/rules/agent-model.ts
552
+ var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["Agent", "Task"]);
553
+ var GENERAL_PURPOSE = "general-purpose";
554
+ function is_general_purpose(call) {
555
+ if (!SUBAGENT_TOOLS.has(call.name) || !json_value.is_record(call.input)) return false;
556
+ const type = call.input["subagent_type"];
557
+ return type === void 0 || type === GENERAL_PURPOSE;
558
+ }
559
+ function names_model(call) {
560
+ const model = json_value.is_record(call.input) ? call.input["model"] : void 0;
561
+ return typeof model === "string" && model.length > 0;
562
+ }
563
+ function is_unnamed_model(call) {
564
+ return is_general_purpose(call) && !names_model(call);
565
+ }
566
+ function keeps_model(call) {
567
+ return is_general_purpose(call) && names_model(call);
568
+ }
569
+ var AGENT_MODEL_REASON = "\u26D4 agent model: a `general-purpose` subagent with no `model` inherits the parent model. Reissue the call with `model` set \u2014 `sonnet` for routine work, `opus` for judgment such as a review. It fires on every occurrence (`.claude/skills/workflow-commands/delegation.md`).";
570
+ var ROW = {
571
+ id: "agent-model",
572
+ is_trigger: is_unnamed_model,
573
+ reason: AGENT_MODEL_REASON,
574
+ decide: () => true,
575
+ keeps: keeps_model,
576
+ reaches: is_general_purpose
577
+ };
578
+ var agent_model = { AGENT_MODEL_REASON, ROW, is_unnamed_model };
579
+
543
580
  // scripts/rules/direct-filing.ts
544
- 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.**';
581
+ var DIRECT_FILING_REASON = '\u26D4 direct filing: an Issue is filed with `pnpm josh issue:file "<title>" --body-file <path> --depth <0|1|2>`, never with `gh api \u2026/issues` or `gh issue create` \u2014 it runs the scout, the lint, the labels and `epic:bundle` itself. It fires on every occurrence (`docs/josh-commands-backlog.md` \u2192 "`josh issue:file`").';
545
582
  function is_any_filing(command) {
546
583
  return bash_triggers.is_direct_filing(command) || bash_triggers.is_issue_filing(command);
547
584
  }
548
- var ROW = {
585
+ var ROW2 = {
549
586
  id: "direct-filing",
550
587
  is_trigger: bash_triggers.on_bash_command(bash_triggers.is_direct_filing),
551
588
  reason: DIRECT_FILING_REASON,
@@ -553,7 +590,7 @@ var ROW = {
553
590
  keeps: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
554
591
  reaches: bash_triggers.on_bash_command(is_any_filing)
555
592
  };
556
- var direct_filing = { DIRECT_FILING_REASON, ROW };
593
+ var direct_filing = { DIRECT_FILING_REASON, ROW: ROW2 };
557
594
 
558
595
  // scripts/run/progress/run-progress.ts
559
596
  var DEFAULT_INTERVAL_MINUTES = 20;
@@ -890,17 +927,17 @@ function landing_ms(command, now_ms, last_report_ms) {
890
927
  const lands_ms = now_ms + duration_ms;
891
928
  return run_progress.is_due(last_report_ms, lands_ms, interval_ms) ? lands_ms : void 0;
892
929
  }
893
- function decide(call, run2, can_record) {
930
+ function decide(call, run, can_record) {
894
931
  const last_report_ms = run_progress_clock.read_last_report_sync();
895
932
  if (last_report_ms === void 0) return false;
896
- const target = ARM_RECORD.path(run2.transcript);
897
- if (is_armed(target, run2.now_ms)) return true;
898
- const lands_ms = landing_ms(time_shell.bash_command(call.input), run2.now_ms, last_report_ms);
933
+ const target = ARM_RECORD.path(run.transcript);
934
+ if (is_armed(target, run.now_ms)) return true;
935
+ const lands_ms = landing_ms(time_shell.bash_command(call.input), run.now_ms, last_report_ms);
899
936
  if (lands_ms === void 0) return true;
900
937
  if (can_record) ARM_RECORD.record(target, lands_ms);
901
938
  return false;
902
939
  }
903
- var EARLY_HEARTBEAT_REASON = "\u26D4 early heartbeat: this call arms a wait timer of its own, and the progress clock is not yours to keep. `pnpm josh run:progress --wait` keeps it for you (`JOSH_PROGRESS_INTERVAL_MINUTES`, or `josh.progress_interval_minutes` in `package.json`, default 20 minutes): start it once in the background and it reports the five labelled lines to the run's event stream on its own, without waking you \u2014 relay nothing, and start it again only after it exits, which it does when newly runnable work arrives. Two timers armed at once is what produced reports 3\u20135 minutes apart on a 15-minute setting (joshuafolkken/kit#1570): a new one was armed on the turn a timer fired and again on the turn a child's completion woke the run, and `--mark` recorded each report without anything refusing the early ones. **An explicit ask is not a heartbeat**: if the person asked for progress now, run `pnpm josh run:progress --once`, which prints the five labelled lines whatever the clock says \u2014 under `backlogrun`, `pnpm josh run:board --chat` instead. If you are waiting on something rather than on a clock, wait on the thing itself \u2014 `pnpm josh followup` waits for CI. The procedure is `.claude/skills/workflow-commands/backlogrun.md` \u2192 \"Progress while the run is quiet\". **This rule fires on every early arm, not once per run**, so reissuing the same sleep will be refused again.";
940
+ var EARLY_HEARTBEAT_REASON = "\u26D4 early heartbeat: this call arms a wait timer of its own. Start `pnpm josh run:progress --wait` once in the background: it reports to the run's event stream on its own \u2014 relay nothing. When asked, `pnpm josh run:progress --once` \u2014 under `backlogrun`, `pnpm josh run:board --chat`; for CI, `pnpm josh followup`. It fires on every occurrence (`.claude/skills/workflow-commands/progress-watcher.md`).";
904
941
  var PROGRESS_NAMES = /* @__PURE__ */ new Set(["run:progress"]);
905
942
  var WATCH_FLAG = /(?:^|\s)--(?:wait|once)(?=\s|$)/u;
906
943
  function is_progress_watch_segment(segment) {
@@ -998,8 +1035,8 @@ function carries_a_file_body(command, has_file = existsSync) {
998
1035
  (line) => is_heredoc_write(line, has_file) || has_body_interpreter(line)
999
1036
  );
1000
1037
  }
1001
- var FILE_BODY_REASON = "\u26D4 file body in a shell command: this carries a file's new text inline \u2014 a heredoc rewriting an existing file, an interpreter (`python3 - <<`, `node -e`) whose body writes, a script file (`cat > edit.py <<EOF`) whose body writes, or an in-place `perl -0pi -e`. `CLAUDE.md` forbids it because the command body is re-read on every later request of the run, so an editing script written early is billed for the rest of it (`file-edits.md` measured one run at ~2m45s spent retyping existing lines). Edit a region with the Edit tool (old / new); a change scattered so widely that the partial edits would carry more than the file is rewritten whole with the Write tool, never a heredoc. Pass a body by path (`--field body=@<path>`, `--body-file <path>`). A new-file creation (`cat > <new-file> <<EOF`), a read-only heredoc (`cat <<EOF`), and a short `sed -i` stay allowed. **This rule fires on every occurrence, not once per run.**";
1002
- var ROW2 = {
1038
+ var FILE_BODY_REASON = "\u26D4 file body in a shell command: this carries a file's new text inline, which is re-read on every later request. Edit a region with the Edit tool, rewrite a whole file with the Write tool, and pass a body by path (`--body-file <path>`); a new-file `cat > <new-file> <<EOF` stays allowed. This rule fires on every occurrence (`prompts/collaboration-workflow/file-edits.md`).";
1039
+ var ROW3 = {
1003
1040
  id: "file-body",
1004
1041
  is_trigger: bash_triggers.on_bash_command((command) => carries_a_file_body(command)),
1005
1042
  reason: FILE_BODY_REASON,
@@ -1007,7 +1044,7 @@ var ROW2 = {
1007
1044
  };
1008
1045
  var file_body = {
1009
1046
  FILE_BODY_REASON,
1010
- ROW: ROW2,
1047
+ ROW: ROW3,
1011
1048
  carries_a_file_body
1012
1049
  };
1013
1050
 
@@ -1109,8 +1146,8 @@ function is_destructive_segment(segment) {
1109
1146
  function is_force_or_delete(command) {
1110
1147
  return shell_segments.segments_of(command).some((segment) => is_destructive_segment(segment));
1111
1148
  }
1112
- var GIT_FORCE_REASON = "\u26D4 force push or branch delete: this rewrites or removes shared history, which is Tier C and needs explicit current-turn user instruction \u2014 never an AI decision, even to fix a problem you caused (`prompts/collaboration-workflow/operating-rules.md`). The `.claude/settings.json` deny list matches globs, so it misses this spelling: a combined short cluster (`git push -uf`), a `git -C <path>` prefix, and the colon-form delete (`git push origin :branch`) all pass it. This row reads the argv instead, so the spelling does not matter. For an ordinary push use `pnpm josh git`; a force push, a `git push --delete` / `:branch`, or a `git branch -d` / `-D` is the user's to run in their own terminal. **This rule fires on every occurrence, not once per run.**";
1113
- var ROW3 = {
1149
+ var GIT_FORCE_REASON = "\u26D4 force push or branch delete: this rewrites or removes shared history, which is Tier C and needs explicit current-turn user instruction. Push with `pnpm josh git`; leave a force push or branch delete to the user's own terminal. This rule fires on every occurrence (`prompts/collaboration-workflow/operating-rules.md`).";
1150
+ var ROW4 = {
1114
1151
  id: "git-force",
1115
1152
  is_trigger: bash_triggers.on_bash_command(is_force_or_delete),
1116
1153
  reason: GIT_FORCE_REASON,
@@ -1118,376 +1155,10 @@ var ROW3 = {
1118
1155
  };
1119
1156
  var git_force = {
1120
1157
  GIT_FORCE_REASON,
1121
- ROW: ROW3,
1158
+ ROW: ROW4,
1122
1159
  is_force_or_delete
1123
1160
  };
1124
1161
 
1125
- // scripts/cost-runtime/cost-cli.ts
1126
- import { fileURLToPath } from "node:url";
1127
-
1128
- // scripts/cost-runtime/codex-usage.ts
1129
- import { globSync, readFileSync as readFileSync2 } from "node:fs";
1130
- import path2 from "node:path";
1131
- import { z as z4 } from "zod";
1132
- var THREAD_ID_PATTERN = /^[A-Za-z0-9-]+$/u;
1133
- var SESSION_META_SCHEMA = z4.looseObject({
1134
- type: z4.literal("session_meta"),
1135
- payload: z4.looseObject({ id: z4.string(), cwd: z4.string() })
1136
- });
1137
- var TOTAL_USAGE_SCHEMA = z4.looseObject({ input_tokens: z4.number().nonnegative() });
1138
- var TOKEN_COUNT_SCHEMA = z4.looseObject({
1139
- type: z4.literal("event_msg"),
1140
- payload: z4.looseObject({
1141
- type: z4.literal("token_count"),
1142
- info: z4.looseObject({
1143
- total_token_usage: TOTAL_USAGE_SCHEMA,
1144
- last_token_usage: TOTAL_USAGE_SCHEMA
1145
- })
1146
- })
1147
- });
1148
- function same_project(cwd, target) {
1149
- return cost_transcript.session_cwd(cwd) === cost_transcript.session_cwd(target);
1150
- }
1151
- function billed_per_request(usages) {
1152
- return usages.map((usage) => usage.last_token_usage.input_tokens);
1153
- }
1154
- function measurement_from(content, target, thread_id) {
1155
- const values = content.split("\n").map((line) => json_value.parse_or_undefined(line));
1156
- const metadata = values.flatMap((value) => {
1157
- const parsed = SESSION_META_SCHEMA.safeParse(value);
1158
- return parsed.success ? [parsed.data] : [];
1159
- });
1160
- const usages = values.flatMap((value) => {
1161
- const parsed = TOKEN_COUNT_SCHEMA.safeParse(value);
1162
- return parsed.success ? [parsed.data.payload.info] : [];
1163
- });
1164
- const session = metadata.find((entry) => entry.payload.id === thread_id);
1165
- if (session === void 0 || usages.length === 0) return void 0;
1166
- if (!same_project(session.payload.cwd, target)) return void 0;
1167
- return { billed_input_per_request: billed_per_request(usages) };
1168
- }
1169
- function rollout_files(home, thread_id) {
1170
- if (!THREAD_ID_PATTERN.test(thread_id)) return [];
1171
- return globSync(`sessions/**/*${thread_id}.jsonl`, { cwd: home }).map(
1172
- (file) => path2.join(home, file)
1173
- );
1174
- }
1175
- function read_measurement(file, target, thread_id) {
1176
- try {
1177
- return measurement_from(readFileSync2(file, "utf8"), target, thread_id);
1178
- } catch {
1179
- return void 0;
1180
- }
1181
- }
1182
- function latest_measurement(files, target, thread_id) {
1183
- for (const file of files.toReversed()) {
1184
- const measured = read_measurement(file, target, thread_id);
1185
- if (measured !== void 0) return measured;
1186
- }
1187
- return void 0;
1188
- }
1189
- function measurement(target, default_home, environment = process.env) {
1190
- const thread_id = environment["CODEX_THREAD_ID"];
1191
- if (thread_id === void 0) return void 0;
1192
- const home = environment["CODEX_HOME"] ?? path2.join(default_home, ".codex");
1193
- return latest_measurement(rollout_files(home, thread_id), target, thread_id);
1194
- }
1195
- var codex_usage = { measurement };
1196
-
1197
- // scripts/cost-runtime/cost-corpus.ts
1198
- function accumulate_missing(sessions) {
1199
- return {
1200
- no_usage_lines: sessions.reduce((sum, session) => sum + session.no_usage_lines, 0),
1201
- malformed_lines: sessions.reduce((sum, session) => sum + session.malformed_lines, 0),
1202
- unreadable_sessions: sessions.filter((session) => !session.is_readable).length,
1203
- // Filled by the attribution, which is the only step that knows a unit could not follow its
1204
- // parent; a per-session tally cannot see it, so it is zero here and set for an issue scope below.
1205
- unattributed_sessions: 0
1206
- };
1207
- }
1208
- function load_corpus(cwd) {
1209
- const files = cost_transcript.list_sessions_across(cost_transcript.transcript_directories(cwd));
1210
- const sessions = files.map((file) => cost_transcript.read_session(file));
1211
- return { sessions, files, missing: accumulate_missing(sessions) };
1212
- }
1213
- function session_meta(corpus) {
1214
- return new Map(
1215
- corpus.files.map((file) => [
1216
- file.session_id,
1217
- { is_delegated: file.is_delegated, owner: cost_transcript.owning_session_id(file) }
1218
- ])
1219
- );
1220
- }
1221
- function issue_set(records) {
1222
- const issues = cost_attribute.group_by_issue(records).map((group) => group.issue).filter((issue) => issue !== cost_attribute.UNATTRIBUTED_KEY);
1223
- return new Set(issues);
1224
- }
1225
- function sole_of(issues) {
1226
- if (issues.size !== 1) return cost_attribute.UNATTRIBUTED_KEY;
1227
- return [...issues][0] ?? cost_attribute.UNATTRIBUTED_KEY;
1228
- }
1229
- function owner_sets(corpus, meta) {
1230
- const own = corpus.sessions.filter(
1231
- (session) => meta.get(session.session_id)?.is_delegated !== true
1232
- );
1233
- return new Map(own.map((session) => [session.session_id, issue_set(session.records)]));
1234
- }
1235
- function pairs_with_inherited(session, inherited, is_delegated) {
1236
- return cost_attribute.group_by_issue(session.records).flatMap(
1237
- (group) => group.records.map((record) => ({
1238
- record,
1239
- issue: group.issue === cost_attribute.UNATTRIBUTED_KEY ? inherited : group.issue,
1240
- baseline_tokens: session.baseline_tokens,
1241
- session_id: session.session_id,
1242
- is_delegated
1243
- }))
1244
- );
1245
- }
1246
- var NO_ISSUES = /* @__PURE__ */ new Set();
1247
- function parent_issues(session, meta, owners) {
1248
- const entry = meta.get(session.session_id);
1249
- if (entry?.is_delegated !== true) return NO_ISSUES;
1250
- return owners.get(entry.owner) ?? NO_ISSUES;
1251
- }
1252
- function pairs_of_session(session, meta, owners) {
1253
- const is_delegated = meta.get(session.session_id)?.is_delegated === true;
1254
- const candidates = parent_issues(session, meta, owners);
1255
- const inherited = is_delegated ? sole_of(candidates) : cost_attribute.UNATTRIBUTED_KEY;
1256
- const pairs = pairs_with_inherited(session, inherited, is_delegated);
1257
- const has_unattributed = pairs.some((pair) => pair.issue === cost_attribute.UNATTRIBUTED_KEY);
1258
- if (!is_delegated || !has_unattributed) return { pairs };
1259
- return { pairs, unattributed_candidates: candidates };
1260
- }
1261
- function attribute_corpus(corpus) {
1262
- const meta = session_meta(corpus);
1263
- const owners = owner_sets(corpus, meta);
1264
- const per_session = corpus.sessions.map((session) => pairs_of_session(session, meta, owners));
1265
- return {
1266
- pairs: per_session.flatMap((one) => one.pairs),
1267
- unattributed: per_session.flatMap(
1268
- (one) => one.unattributed_candidates === void 0 ? [] : [one.unattributed_candidates]
1269
- )
1270
- };
1271
- }
1272
- function floor_for_issue(unattributed, issue_number) {
1273
- return unattributed.filter((set) => set.size === 0 || set.has(issue_number)).length;
1274
- }
1275
- function is_better(candidate, existing) {
1276
- const { UNATTRIBUTED_KEY } = cost_attribute;
1277
- return existing.issue === UNATTRIBUTED_KEY && candidate.issue !== UNATTRIBUTED_KEY;
1278
- }
1279
- function values_of(best) {
1280
- const collected = [];
1281
- for (const [, pair] of best) collected.push(pair);
1282
- return collected;
1283
- }
1284
- function dedupe_across_sessions(pairs) {
1285
- const best = /* @__PURE__ */ new Map();
1286
- for (const pair of pairs) {
1287
- const existing = best.get(pair.record.request_id);
1288
- if (existing === void 0 || is_better(pair, existing)) {
1289
- best.set(pair.record.request_id, pair);
1290
- }
1291
- }
1292
- return values_of(best);
1293
- }
1294
- function attributed(corpus) {
1295
- return dedupe_across_sessions(attribute_corpus(corpus).pairs);
1296
- }
1297
- function mainline_records(pairs) {
1298
- const groups = /* @__PURE__ */ new Map();
1299
- for (const pair of pairs) {
1300
- if (pair.is_delegated) continue;
1301
- groups.set(pair.session_id, [...groups.get(pair.session_id) ?? [], pair.record]);
1302
- }
1303
- return [...groups.values()];
1304
- }
1305
- var cost_corpus = {
1306
- load_corpus,
1307
- attribute_corpus,
1308
- floor_for_issue,
1309
- attributed,
1310
- mainline_records
1311
- };
1312
-
1313
- // scripts/cost-runtime/cost-verdict.ts
1314
- var FAILURE_EXIT_CODE = 1;
1315
- var OVER_VERDICT = "over";
1316
- var UNDER_VERDICT = "under";
1317
- function per_request_cost(measurement2) {
1318
- return measurement2.billed_input_per_request.at(-1) ?? 0;
1319
- }
1320
- function classify(measurement2, limit) {
1321
- return per_request_cost(measurement2) > limit ? OVER_VERDICT : UNDER_VERDICT;
1322
- }
1323
- function over_summary(measurement2, limit) {
1324
- const total = measurement2.billed_input_per_request.length;
1325
- return `${String(per_request_cost(measurement2))} billed input tokens per request, on the newest of ${String(total)} request(s); limit ${String(limit)}`;
1326
- }
1327
- function report_over(measurement2, limit) {
1328
- if (measurement2 === void 0 || measurement2.billed_input_per_request.length === 0) {
1329
- console.error("No requests in this session; there is nothing to hand off.");
1330
- return FAILURE_EXIT_CODE;
1331
- }
1332
- console.info(classify(measurement2, limit));
1333
- console.error(over_summary(measurement2, limit));
1334
- return 0;
1335
- }
1336
- var cost_verdict = {
1337
- OVER_VERDICT,
1338
- UNDER_VERDICT,
1339
- classify,
1340
- per_request_cost,
1341
- report_over
1342
- };
1343
-
1344
- // scripts/cost-runtime/transcript-cwd.ts
1345
- import path3 from "node:path";
1346
- function resolve(target, cwd) {
1347
- return target === void 0 ? cwd : path3.resolve(target);
1348
- }
1349
- var transcript_cwd = { resolve };
1350
-
1351
- // scripts/cost-runtime/cost-cli.ts
1352
- var ARGV_OFFSET = 2;
1353
- var FAILURE_EXIT_CODE2 = 1;
1354
- var USAGE = "Usage: josh cost (--cut | --over <tokens-per-request>) [--path <dir>]";
1355
- var UNMEASURABLE_VERDICT = "unmeasurable";
1356
- function to_threshold(raw) {
1357
- if (raw === void 0) return void 0;
1358
- if (raw.trim() === "") return void 0;
1359
- const parsed = Number(raw);
1360
- return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
1361
- }
1362
- function is_unparsed(raw, parsed) {
1363
- return parsed === void 0 && raw !== void 0;
1364
- }
1365
- function selected_threshold(values) {
1366
- if (values.cut === true) return CONTEXT_CUT_THRESHOLD;
1367
- return to_threshold(values.over);
1368
- }
1369
- function threshold_option(over) {
1370
- return over === void 0 ? {} : { over };
1371
- }
1372
- function path_option(path16) {
1373
- return path16 === void 0 ? {} : { path: path16 };
1374
- }
1375
- function to_options(values) {
1376
- if (values.cut === true && values.over !== void 0) return void 0;
1377
- const over = selected_threshold(values);
1378
- if (is_unparsed(values.over, over)) return void 0;
1379
- return { ...threshold_option(over), ...path_option(values.path) };
1380
- }
1381
- var PARSE_ARGS_OPTIONS = {
1382
- cut: { type: "boolean" },
1383
- over: { type: "string" },
1384
- path: { type: "string" }
1385
- };
1386
- function parse_options(argv) {
1387
- const values = cli_flags.values_of(argv, PARSE_ARGS_OPTIONS);
1388
- return values === void 0 ? void 0 : to_options(values);
1389
- }
1390
- function report_empty(cwd, session_id) {
1391
- const searched = cost_transcript.searched_directories(cost_transcript.transcript_directories(cwd));
1392
- for (const line of cost_transcript.missing_message(searched, session_id)) console.error(line);
1393
- return FAILURE_EXIT_CODE2;
1394
- }
1395
- function provider_of(environment) {
1396
- const resolved = agent_role_profile.resolve_provider(environment);
1397
- if (resolved.kind !== "provider" || resolved.is_default === true) return void 0;
1398
- return resolved.provider;
1399
- }
1400
- function measurement_of(session) {
1401
- return {
1402
- billed_input_per_request: session.records.map(
1403
- (record) => cost_usage.billed_input(record.totals)
1404
- )
1405
- };
1406
- }
1407
- function anthropic_own(target, environment) {
1408
- const corpus = cost_corpus.load_corpus(target);
1409
- const selection = own_session.select_own_session(corpus.files, environment);
1410
- if (selection.kind === "absent") return { kind: "absent", session_id: selection.session_id };
1411
- const session = corpus.sessions[selection.index];
1412
- if (session === void 0) return { kind: "empty" };
1413
- return { kind: "measured", measurement: measurement_of(session) };
1414
- }
1415
- function anthropic_measurement(target, environment) {
1416
- const own = anthropic_own(target, environment);
1417
- return own.kind === "measured" ? own.measurement : void 0;
1418
- }
1419
- function same_openai_project(target, cwd) {
1420
- return cost_transcript.session_cwd(target) === cost_transcript.session_cwd(cwd);
1421
- }
1422
- function reject_openai_cross_project(context, target, cwd) {
1423
- if (context.provider !== "openai" || context.path === void 0 || same_openai_project(target, cwd)) {
1424
- return false;
1425
- }
1426
- console.error(
1427
- "OpenAI --path cannot select a thread from another project; run josh cost in that project."
1428
- );
1429
- return true;
1430
- }
1431
- function measure(target, provider, environment) {
1432
- return provider === "openai" ? codex_usage.measurement(target, cost_transcript.home_directory(), environment) : anthropic_measurement(target, environment);
1433
- }
1434
- function run_over_anthropic(own, target, limit) {
1435
- if (own.kind === "measured") return cost_verdict.report_over(own.measurement, limit);
1436
- return report_empty(target, own.kind === "absent" ? own.session_id : void 0);
1437
- }
1438
- function run_over_codex(target, limit, environment) {
1439
- const measurement2 = measure(target, "openai", environment);
1440
- if (measurement2 !== void 0) return cost_verdict.report_over(measurement2, limit);
1441
- console.error(`No Codex usage found for the current OpenAI thread under ${target}.`);
1442
- return FAILURE_EXIT_CODE2;
1443
- }
1444
- function run_over(target, limit, provider, environment) {
1445
- if (provider === "anthropic") {
1446
- return run_over_anthropic(anthropic_own(target, environment), target, limit);
1447
- }
1448
- return run_over_codex(target, limit, environment);
1449
- }
1450
- function verdict_of(measurement2) {
1451
- if (measurement2 === void 0 || measurement2.billed_input_per_request.length === 0) {
1452
- return UNMEASURABLE_VERDICT;
1453
- }
1454
- return cost_verdict.classify(measurement2, CONTEXT_CUT_THRESHOLD);
1455
- }
1456
- function session_verdict(cwd = process.cwd(), environment = process.env) {
1457
- const provider = provider_of(environment);
1458
- if (provider === void 0) return UNMEASURABLE_VERDICT;
1459
- return verdict_of(measure(cwd, provider, environment));
1460
- }
1461
- function run_context(argv, environment) {
1462
- const options = parse_options(argv);
1463
- if (options?.over === void 0) return void 0;
1464
- const provider = provider_of(environment);
1465
- if (provider === void 0) return void 0;
1466
- return { over: options.over, path: options.path, provider };
1467
- }
1468
- function run(argv, cwd = process.cwd(), environment = process.env) {
1469
- const context = run_context(argv, environment);
1470
- if (context === void 0) {
1471
- console.error(USAGE);
1472
- return FAILURE_EXIT_CODE2;
1473
- }
1474
- const target = transcript_cwd.resolve(context.path, cwd);
1475
- if (reject_openai_cross_project(context, target, cwd)) return FAILURE_EXIT_CODE2;
1476
- return run_over(target, context.over, context.provider, environment);
1477
- }
1478
- function main(argv) {
1479
- process.exitCode = run(argv);
1480
- }
1481
- var cost_cli = {
1482
- UNMEASURABLE_VERDICT,
1483
- USAGE,
1484
- parse_options,
1485
- to_threshold,
1486
- run,
1487
- session_verdict
1488
- };
1489
- if (process.argv[1] === fileURLToPath(import.meta.url)) main(process.argv.slice(ARGV_OFFSET));
1490
-
1491
1162
  // scripts/cost-runtime/cost-format.ts
1492
1163
  var USD_DECIMALS = 4;
1493
1164
  var PERCENT_SCALE = 100;
@@ -1508,6 +1179,69 @@ var cost_format = {
1508
1179
  format_share
1509
1180
  };
1510
1181
 
1182
+ // scripts/run/cut/run-cut-decline.ts
1183
+ import { z as z4 } from "zod";
1184
+ var DECLINE_PREFIX = "josh-run-cut-decline-";
1185
+ var decline_schema = z4.object({ issue: z4.string(), hold_taken_at: z4.string().optional() });
1186
+ function decline_path(git_directory) {
1187
+ return stamp_file.stamp_path(DECLINE_PREFIX, git_directory);
1188
+ }
1189
+ function record(git_directory, decline) {
1190
+ stamp_file.replace_stamp(decline_path(git_directory), decline);
1191
+ }
1192
+ function read(git_directory) {
1193
+ const raw = stamp_file.read_stamp_text(decline_path(git_directory));
1194
+ return raw === void 0 ? void 0 : json_value.parse_with(raw, decline_schema);
1195
+ }
1196
+ function matches(recorded, current) {
1197
+ return recorded?.issue === current.issue && recorded.hold_taken_at === current.hold_taken_at;
1198
+ }
1199
+ function is_declined_sync(issue, directory = run_cut.worktree_git_directory_sync()) {
1200
+ if (directory === void 0) return false;
1201
+ const hold_taken_at = run_cut.hold_of(directory)?.taken_at;
1202
+ return matches(read(directory), { issue, hold_taken_at });
1203
+ }
1204
+ async function declined(request2, state, code) {
1205
+ if (request2.phase !== run_cut.IMPLEMENTATION_PHASE) return code;
1206
+ const directory = await run_cut.worktree_directory();
1207
+ if (directory !== void 0) {
1208
+ record(directory, { issue: request2.issue, hold_taken_at: state.hold_taken_at });
1209
+ }
1210
+ return code;
1211
+ }
1212
+ var run_cut_decline = { decline_path, declined, is_declined_sync, read, record };
1213
+
1214
+ // scripts/run/cut/run-cut-ready.ts
1215
+ function is_ready(tree) {
1216
+ return tree.branch !== tree.default_branch && tree.is_dirty;
1217
+ }
1218
+ async function is_lane_branch(state) {
1219
+ const default_branch = await git_command.get_default_branch();
1220
+ return is_ready({ branch: state.branch, default_branch, is_dirty: state.is_dirty });
1221
+ }
1222
+ var STATUS_ARGUMENTS = ["status", "--porcelain=v2", "--branch", UNTRACKED_FILES_FLAG];
1223
+ var HEADER_PREFIX = "#";
1224
+ var BRANCH_HEADER = "# branch.head ";
1225
+ function tree_of(status, default_branch) {
1226
+ const lines = status.split("\n").filter((line) => line !== "");
1227
+ const header = lines.find((line) => line.startsWith(BRANCH_HEADER));
1228
+ if (header === void 0) return void 0;
1229
+ const branch = header.slice(BRANCH_HEADER.length);
1230
+ const is_dirty = lines.some((line) => !line.startsWith(HEADER_PREFIX));
1231
+ return { branch, default_branch, is_dirty };
1232
+ }
1233
+ function read_tree_sync() {
1234
+ const status = git_spawn_sync.read(STATUS_ARGUMENTS);
1235
+ if (status === void 0) return void 0;
1236
+ const reference = git_spawn_sync.read(git_diff_reads.DEFAULT_BRANCH_ARGUMENTS);
1237
+ return tree_of(status, git_diff_reads.default_branch_of(reference));
1238
+ }
1239
+ function is_ready_sync(read2 = read_tree_sync) {
1240
+ const tree = read2();
1241
+ return tree === void 0 || is_ready(tree);
1242
+ }
1243
+ var run_cut_ready = { is_lane_branch, is_ready, is_ready_sync, tree_of };
1244
+
1511
1245
  // scripts/rules/implementation-cut-verdict.ts
1512
1246
  import { z as z5 } from "zod";
1513
1247
  var VERDICT_CACHE_PREFIX = "josh-impl-cut-verdict-";
@@ -1555,6 +1289,9 @@ function is_impl_cut_segment(segment) {
1555
1289
  function takes_the_impl_cut(command) {
1556
1290
  return shell_segments.segments_of(command).some((segment) => is_impl_cut_segment(segment));
1557
1291
  }
1292
+ function can_cut_sync(issue) {
1293
+ return !run_cut_decline.is_declined_sync(issue) && run_cut_ready.is_ready_sync();
1294
+ }
1558
1295
  function current_state() {
1559
1296
  return {
1560
1297
  directory: process.cwd(),
@@ -1564,7 +1301,8 @@ function current_state() {
1564
1301
  // **Read through the short reuse window**. The row now fires per
1565
1302
  // threshold crossing rather than once per run, so this predicate is a candidate on every edit;
1566
1303
  // the window collapses one turn's burst of edits to a single whole-transcript price.
1567
- verdict: () => implementation_cut_verdict.reused_verdict(() => cost_cli.session_verdict())
1304
+ verdict: () => implementation_cut_verdict.reused_verdict(() => cost_cli.session_verdict()),
1305
+ can_cut: can_cut_sync
1568
1306
  };
1569
1307
  }
1570
1308
  function lane_child_issue(state) {
@@ -1590,29 +1328,36 @@ function warrants_the_cut(state) {
1590
1328
  return verdict !== cost_verdict.UNDER_VERDICT;
1591
1329
  }
1592
1330
  function is_over_threshold_edit(call, state = current_state()) {
1593
- if (!is_edit_tool(call.name) || uncut_run_issue(state) === void 0) return false;
1594
- return warrants_the_cut(state);
1331
+ if (!is_edit_tool(call.name)) return false;
1332
+ const issue = uncut_run_issue(state);
1333
+ if (issue === void 0) return false;
1334
+ return warrants_the_cut(state) && state.can_cut(issue);
1595
1335
  }
1596
1336
  var THRESHOLD_TEXT = `${cost_format.format_tokens(CONTEXT_CUT_THRESHOLD)}-token`;
1597
- var IMPLEMENTATION_CUT_REASON = `\u26D4 implementation-phase cut: this checkout is a lane dispatched for this issue, or a run holds it for this issue (joshuafolkken/kit#2760), and its recent-context cost has crossed the shared ${THRESHOLD_TEXT} threshold mid-implementation, so the thinking accumulated so far is now re-read on every later request. Take the cut before this edit. First write a handoff file at \`${run_cut_handoff.HANDOFF_PATH}\` (ignored, so it is never committed) with the Write tool \u2014 the user\u2019s instruction verbatim, what you have completed, what remains, and what you deliberately did not touch, as ${run_cut_handoff.HANDOFF_FORMAT} (Markdown is refused) \u2014 and pass it as \`--handoff <path>\`, so the fresh process resumes on the original instruction rather than the working tree alone (joshuafolkken/kit#2354); a resume that finds no instruction is refused \`incomplete\` rather than continuing blind. \`pnpm josh run:cut --impl <N> --handoff <path>\` ends this process and relaunches a fresh one that resumes back into implementation (\`resume-impl\`), dropping the accumulated context rather than carrying it \u2014 joshuafolkken/kit#1933 built the boundary and joshuafolkken/kit#2310 measured it firing 0 times because the verdict was only ever read at session entry, where the context has not yet grown. The verdict is \`pnpm josh cost --cut\`'s exactly \u2014 the same per-request billed-input statistic against the same threshold, never a second measurement. Issue \`pnpm josh run:cut --impl <N> --handoff <path>\` now and read the verdict: on \`cut\`, read what the command printed. Where it relaunched a fresh process, **end the turn immediately** \u2014 that process owns the run and continues implementing, so it must not be waited for, and **no Telegram is owed**: a cut that resumes itself leaves a person nothing to act on. Where it printed that nothing was relaunched, do what it prints, then end the turn; on \`not-a-lane\`, \`unready\`, \`busy\`, \`failed\` or \`unknown\`, this process carries the run on and the edit is simply the next call. Never relaunch a second process after \`busy\`. This fired because the dispatch mark names this lane or the run hold names this issue; a person working here carries no mark and holds no run, so sees no refusal, so there is no human-or-child judgement left to make. The procedure is \`.claude/skills/workflow-commands/pre-gate-cut.md\`. Reissue this edit once the cut has answered \u2014 an edit reissued right after this refusal passes, so \`busy\` / \`failed\` cannot wedge the run edit after edit; this fires again on the next threshold crossing rather than once per run.`;
1337
+ var IMPLEMENTATION_CUT_REASON = `\u26D4 implementation-phase cut: refused on every edit past the ${THRESHOLD_TEXT} line. Write \`${run_cut_handoff.HANDOFF_PATH}\` as ${run_cut_handoff.HANDOFF_FORMAT}, run \`pnpm josh run:cut --impl <N> --handoff <path>\`; \`cut\`: end the turn immediately, no Telegram is owed; else reissue the edit (\`.claude/skills/workflow-commands/pre-gate-cut.md\`).`;
1598
1338
  var REISSUE_WINDOW_MS = 9e4;
1599
1339
  function is_reissued_refusal(now_ms, delivered_at_ms) {
1600
1340
  return now_ms - delivered_at_ms < REISSUE_WINDOW_MS;
1601
1341
  }
1602
- function decide2(_call, run2, _can_record, delivered_at_ms) {
1603
- return !is_reissued_refusal(run2.now_ms, delivered_at_ms);
1342
+ function decide2(_call, run, _can_record, delivered_at_ms) {
1343
+ return !is_reissued_refusal(run.now_ms, delivered_at_ms);
1604
1344
  }
1605
- var ROW4 = {
1345
+ var ROW5 = {
1606
1346
  id: "implementation-cut",
1607
1347
  is_trigger: is_over_threshold_edit,
1608
1348
  reason: IMPLEMENTATION_CUT_REASON,
1609
1349
  decide: decide2,
1610
- keeps: bash_triggers.on_bash_command(takes_the_impl_cut)
1350
+ keeps: bash_triggers.on_bash_command(takes_the_impl_cut),
1351
+ // The opening the reason carried before it was cut to the action and the next command.
1352
+ former_reasons: [
1353
+ "\u26D4 implementation-phase cut: this checkout is a lane dispatched",
1354
+ "\u26D4 implementation-phase cut: this run's context crossed the"
1355
+ ]
1611
1356
  };
1612
1357
  var implementation_cut = {
1613
1358
  IMPLEMENTATION_CUT_REASON,
1614
1359
  REISSUE_WINDOW_MS,
1615
- ROW: ROW4,
1360
+ ROW: ROW5,
1616
1361
  decide: decide2,
1617
1362
  is_over_threshold_edit,
1618
1363
  is_reissued_refusal,
@@ -1665,10 +1410,10 @@ var gh_api = {
1665
1410
  // scripts/rules/prior-comment-read.ts
1666
1411
  var ISSUE_PATH_NUMBER = /issues\/(\d+)/gu;
1667
1412
  var ISSUE_VIEW_NUMBER = /\bissue\s+view\s+(?:-{1,2}\S+\s+)*(\d+)/u;
1668
- var ISSUE_READ_ARGUMENTS = /\bissue:read\b([\s\d]*)/u;
1413
+ var ISSUE_READ_ARGUMENTS = /\b(?:issue:read|run:entry|run:prep)\b([\s\d]*)/u;
1669
1414
  var COMMENTS_ENDPOINT = /issues\/\d+\/comments\b/u;
1670
1415
  var VIEW_WITH_COMMENTS = /\bissue\s+view\b[^\n]*--comments\b/u;
1671
- var ISSUE_READ_COMMAND = /\bissue:read\b/u;
1416
+ var ISSUE_READ_COMMAND = /\b(?:issue:read|run:entry|run:prep)\b/u;
1672
1417
  function add_issue_read_numbers(command, numbers) {
1673
1418
  const match = ISSUE_READ_ARGUMENTS.exec(command);
1674
1419
  if (match === null) return;
@@ -1739,19 +1484,48 @@ function is_body_only_issue_read(command) {
1739
1484
  function reads_issue_comments(command) {
1740
1485
  return shell_segments.segments_of(command).some((segment) => fetches_issue_comments(segment));
1741
1486
  }
1742
- var ISSUE_COMMENTS_REASON = "\u26D4 issue comments: pnpm josh issue:read <N>\nRun that in place of this read: <N> is the Issue it names, and an Issue's comments are part of the Issue, so the body and every comment come back together. Without josh, add `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.\nWhy: 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. 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.";
1743
- var ROW5 = {
1487
+ var RULE_DOC = "`.claude/skills/workflow-commands/issue-comments.md`";
1488
+ var COMMENTS_FALLBACK = "gh api repos/{owner}/{repo}/issues/<N>/comments";
1489
+ var ISSUE_COMMENTS_REASON = `\u26D4 issue comments: pnpm josh issue:read <N>
1490
+ Run that in place of this read \u2014 an Issue's comments are part of it, and a later comment supersedes the body it contradicts; refused until that command is on the transcript. Without josh, read \`${COMMENTS_FALLBACK}\` beside the body (${RULE_DOC}).`;
1491
+ var COMMENTS_JQ = String.raw`'.[] | "— \(.user.login) \(.created_at)\n\(.body)\n"'`;
1492
+ var COMMENTS_NOTE = `\u21BB issue comments: this read returned only the Issue's body, so the Issue's comments were fetched on the same line \u2014 a later comment supersedes the body it contradicts (${RULE_DOC}).`;
1493
+ function rewritable_path(command) {
1494
+ const read2 = shell_segments.first_of(command);
1495
+ if (read2 === void 0 || ISSUE_VIEW_COMMAND.test(read2)) return void 0;
1496
+ return is_body_read_segment(read2) ? ISSUE_BODY_PATH.exec(read2)?.[0] : void 0;
1497
+ }
1498
+ function comments_command(command) {
1499
+ if (!is_body_only_issue_read(command)) return void 0;
1500
+ const path13 = rewritable_path(command);
1501
+ if (path13 === void 0) return void 0;
1502
+ return `gh api --paginate ${path13}/comments --jq ${COMMENTS_JQ}
1503
+ ${command}`;
1504
+ }
1505
+ function comments_input(call, tail) {
1506
+ if (call.name !== cost_blocks.BASH_TOOL || !json_value.is_record(call.input)) return void 0;
1507
+ const command = comments_command(time_shell.bash_command(call.input));
1508
+ if (command === void 0 || prior_comment_read.already_read_for_call(tail(), call)) {
1509
+ return void 0;
1510
+ }
1511
+ return { ...call.input, command };
1512
+ }
1513
+ var ROW6 = {
1744
1514
  id: "issue-comments",
1745
1515
  is_trigger: bash_triggers.on_bash_command(is_body_only_issue_read),
1746
1516
  reason: ISSUE_COMMENTS_REASON,
1747
1517
  already_satisfied: prior_comment_read.already_read_for_call,
1748
1518
  keeps: bash_triggers.on_bash_command(reads_issue_comments),
1519
+ rewrite: comments_input,
1520
+ rewrite_note: COMMENTS_NOTE,
1749
1521
  // The opening the reason carried before it led with the command.
1750
1522
  former_reasons: ["\u26D4 an Issue's comments are part of the Issue: read them before implementing"]
1751
1523
  };
1752
1524
  var issue_comments = {
1525
+ COMMENTS_NOTE,
1753
1526
  ISSUE_COMMENTS_REASON,
1754
- ROW: ROW5,
1527
+ ROW: ROW6,
1528
+ comments_input,
1755
1529
  is_body_only_issue_read
1756
1530
  };
1757
1531
 
@@ -1768,8 +1542,8 @@ function is_bare_segment(segment) {
1768
1542
  function is_bare_josh_git(command) {
1769
1543
  return shell_segments.segments_of(command).some((segment) => is_bare_segment(segment));
1770
1544
  }
1771
- 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.';
1772
- var ROW6 = {
1545
+ 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. Run `pnpm josh git -y "<title> #<N>"`, or `pnpm josh git -y --skip-commit --skip-push` to open the PR for a push that already landed. It fires on every occurrence.';
1546
+ var ROW7 = {
1773
1547
  id: "josh-git-bare",
1774
1548
  is_trigger: bash_triggers.on_bash_command(is_bare_josh_git),
1775
1549
  reason: JOSH_GIT_BARE_REASON,
@@ -1777,7 +1551,7 @@ var ROW6 = {
1777
1551
  };
1778
1552
  var josh_git_bare = {
1779
1553
  JOSH_GIT_BARE_REASON,
1780
- ROW: ROW6,
1554
+ ROW: ROW7,
1781
1555
  is_bare_josh_git
1782
1556
  };
1783
1557
 
@@ -1801,8 +1575,8 @@ function is_carry_conflict(command, is_lane_child = lane_child_marker.is_child_o
1801
1575
  if (!invokes_budget_command(command)) return false;
1802
1576
  return is_lane_child;
1803
1577
  }
1804
- 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". Live evidence of `run:carry` is taken with a fresh `JOSH_TEMP_ROOT` prefixed to the call, which this rule lets through (joshuafolkken/kit#3458); `run:merge` is refused under any root, because it also syncs main, closes lanes and writes to GitHub. This rule fires on every occurrence, not once per run.';
1805
- var ROW7 = {
1578
+ var LANE_CARRY_CONFLICT_REASON = "\u26D4 lane child carry conflict: this session is a dispatched lane child, and `run:merge` / `run:carry` are the parent budget commands; the live record belongs to the parent, not to a competing run. Continue implementing this Issue: no `needs-decision`, no Telegram. Live `run:carry` evidence takes a fresh `JOSH_TEMP_ROOT`; `run:merge` is refused under any root, on every occurrence.";
1579
+ var ROW8 = {
1806
1580
  id: "lane-carry-conflict",
1807
1581
  is_trigger: bash_triggers.on_bash_command(is_carry_conflict),
1808
1582
  reason: LANE_CARRY_CONFLICT_REASON,
@@ -1810,66 +1584,24 @@ var ROW7 = {
1810
1584
  };
1811
1585
  var lane_carry_conflict = {
1812
1586
  LANE_CARRY_CONFLICT_REASON,
1813
- ROW: ROW7,
1587
+ ROW: ROW8,
1814
1588
  invokes_budget_command,
1815
1589
  is_carry_conflict
1816
1590
  };
1817
1591
 
1818
- // scripts/agent/interactive-ask.ts
1819
- import { z as z6 } from "zod";
1820
- var INTERACTIVE_TOOLS = /* @__PURE__ */ new Set(["AskUserQuestion"]);
1821
- function is_interactive_tool(name) {
1822
- return INTERACTIVE_TOOLS.has(name);
1823
- }
1824
- var OPTION_SCHEMA = z6.looseObject({ label: z6.string().optional() });
1825
- var QUESTION_SCHEMA = z6.looseObject({
1826
- question: z6.string().optional(),
1827
- options: z6.array(OPTION_SCHEMA).optional()
1828
- });
1829
- var ASK_INPUT_SCHEMA = z6.looseObject({ questions: z6.array(QUESTION_SCHEMA).optional() });
1830
- var DENIAL_SCHEMA = z6.looseObject({
1831
- tool_name: z6.string().optional(),
1832
- tool_input: z6.unknown().optional()
1833
- });
1834
- var UNKNOWN_QUESTION = "(question text unavailable)";
1835
- var OPTION_SEPARATOR = " / ";
1836
- var QUESTION_SEPARATOR = "; ";
1837
- function labels_of(question) {
1838
- return (question.options ?? []).map((option) => option.label ?? "").filter((label) => label !== "");
1839
- }
1840
- function render_question(question) {
1841
- const labels = labels_of(question);
1842
- const text = question.question ?? UNKNOWN_QUESTION;
1843
- return labels.length === 0 ? text : `${text} [${labels.join(OPTION_SEPARATOR)}]`;
1844
- }
1845
- function interactive_input(raw) {
1846
- const denial = DENIAL_SCHEMA.safeParse(raw);
1847
- if (!denial.success || !is_interactive_tool(denial.data.tool_name ?? "")) return void 0;
1848
- const input = ASK_INPUT_SCHEMA.safeParse(denial.data.tool_input);
1849
- return input.success ? input.data : void 0;
1850
- }
1851
- function refused_ask_of(permission_denials) {
1852
- if (!Array.isArray(permission_denials)) return void 0;
1853
- const input = permission_denials.map((raw) => interactive_input(raw)).find((one) => one !== void 0);
1854
- const questions = input?.questions ?? [];
1855
- if (questions.length === 0) return void 0;
1856
- return questions.map((question) => render_question(question)).join(QUESTION_SEPARATOR);
1857
- }
1858
- var interactive_ask = { is_interactive_tool, refused_ask_of };
1859
-
1860
1592
  // scripts/rules/lane-interactive-ask.ts
1861
1593
  function is_interactive_ask(call, is_lane_child = lane_child_marker.is_child_of(process.cwd())) {
1862
1594
  if (!interactive_ask.is_interactive_tool(call.name)) return false;
1863
1595
  return is_lane_child;
1864
1596
  }
1865
- 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.';
1866
- var ROW8 = {
1597
+ var LANE_INTERACTIVE_ASK_REASON = "\u26D4 lane child interactive ask: this session is a dispatched lane child and cannot ask a person, so park instead: `gh api repos/{owner}/{repo}/issues/<N>/labels -f 'labels[]=needs-decision'`, `pnpm josh issue:comment <N> --body-file <path>`, the `confirmation` Telegram, stop. It fires on every occurrence (`.claude/skills/workflow-commands/pre-gate-cut.md`, `backlogrun-park.md`).";
1598
+ var ROW9 = {
1867
1599
  id: "lane-interactive-ask",
1868
1600
  is_trigger: is_interactive_ask,
1869
1601
  reason: LANE_INTERACTIVE_ASK_REASON,
1870
1602
  decide: () => true
1871
1603
  };
1872
- var lane_interactive_ask = { LANE_INTERACTIVE_ASK_REASON, ROW: ROW8, is_interactive_ask };
1604
+ var lane_interactive_ask = { LANE_INTERACTIVE_ASK_REASON, ROW: ROW9, is_interactive_ask };
1873
1605
 
1874
1606
  // scripts/rules/lane-split-park.ts
1875
1607
  var EPIC_COMMANDS = /* @__PURE__ */ new Set(["epic"]);
@@ -1913,13 +1645,13 @@ function is_candidate(command) {
1913
1645
  if (!is_park_of(command, issue) && !is_notify_of(command, issue)) return false;
1914
1646
  return lane_child_marker.is_child_of(process.cwd());
1915
1647
  }
1916
- function decide3(_call, run2) {
1648
+ function decide3(_call, run) {
1917
1649
  const issue = lane_child_marker.marked_issue();
1918
- const commands = tail_commands.prior_bash_commands(time_density_hook.read_tail(run2.transcript));
1650
+ const commands = tail_commands.prior_bash_commands(time_density_hook.read_tail(run.transcript));
1919
1651
  return issue !== void 0 && promoted_issues(commands).has(issue);
1920
1652
  }
1921
- 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.';
1922
- var ROW9 = {
1653
+ 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: no `needs-decision` on the epic, no `confirmation` Telegram, on every occurrence. Park a child issue if the rest needs a person, else end the turn (`.claude/skills/workflow-commands/backlogrun-park.md` \u2192 "Splitting a child mid-run").';
1654
+ var ROW10 = {
1923
1655
  id: "lane-split-park",
1924
1656
  is_trigger: bash_triggers.on_bash_command(is_candidate),
1925
1657
  reason: LANE_SPLIT_PARK_REASON,
@@ -1927,7 +1659,7 @@ var ROW9 = {
1927
1659
  };
1928
1660
  var lane_split_park = {
1929
1661
  LANE_SPLIT_PARK_REASON,
1930
- ROW: ROW9,
1662
+ ROW: ROW10,
1931
1663
  promoted_issues
1932
1664
  };
1933
1665
 
@@ -1944,8 +1676,9 @@ var NON_COLLIDING_FLAGS = /* @__PURE__ */ new Set([
1944
1676
  var FLAG_PREFIX2 = "-";
1945
1677
  function switch_target(segment) {
1946
1678
  const call = git_argv.parse(segment);
1947
- if (call?.subcommand !== SWITCH_SUBCOMMAND) return void 0;
1948
- if (call.args.some((argument) => NON_COLLIDING_FLAGS.has(argument))) return void 0;
1679
+ if (call?.subcommand !== SWITCH_SUBCOMMAND || call.args.some((argument) => NON_COLLIDING_FLAGS.has(argument))) {
1680
+ return void 0;
1681
+ }
1949
1682
  return call.args.find((argument) => !argument.startsWith(FLAG_PREFIX2));
1950
1683
  }
1951
1684
  function switch_targets(command) {
@@ -1959,8 +1692,8 @@ function is_switch_away_from_lane(command, is_lane_child = lane_child_marker.is_
1959
1692
  if (!is_lane_child || own_branch === void 0) return false;
1960
1693
  return switch_targets(command).some((target) => target !== own_branch);
1961
1694
  }
1962
- 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 `pnpm josh ms` 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.";
1963
- var ROW10 = {
1695
+ 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 (`already used by worktree`: the primary checkout holds it). Skip this step and keep implementing on the current branch; `pnpm josh main:merge` brings the default in during the gate. Refused on every occurrence (`backlogrun-lanes.md`, `backlogrun-child.md`).";
1696
+ var ROW11 = {
1964
1697
  id: "lane-switch-main",
1965
1698
  is_trigger: bash_triggers.on_bash_command(is_switch_away_from_lane),
1966
1699
  reason: LANE_SWITCH_MAIN_REASON,
@@ -1968,7 +1701,7 @@ var ROW10 = {
1968
1701
  };
1969
1702
  var lane_switch_main = {
1970
1703
  LANE_SWITCH_MAIN_REASON,
1971
- ROW: ROW10,
1704
+ ROW: ROW11,
1972
1705
  is_switch_away_from_lane,
1973
1706
  switch_target,
1974
1707
  switch_targets
@@ -2471,7 +2204,7 @@ function command_line(oracle) {
2471
2204
  return oracle.args ? `pnpm josh ${command} ${oracle.args}` : `pnpm josh ${command}`;
2472
2205
  }
2473
2206
  function reason_for(oracle, firing_point) {
2474
- return `\u26D4 decision oracle not consulted: this is ${firing_point.describes}, but the run has not run \`${command_line(oracle)}\` first \u2014 the oracle that decides ${oracle.decision.toLowerCase()}. It answers ${oracle.vocabulary.join(" | ")}; single source ${oracle.single_source}. Reissue after running it and reading its verdict. Every such call is refused until that command is on the transcript.`;
2207
+ return `\u26D4 decision oracle not consulted: this is ${firing_point.describes}. Run \`${command_line(oracle)}\` first \u2014 it decides ${oracle.decision.toLowerCase()} and answers ${oracle.vocabulary.join(" | ")} (${oracle.single_source}). Every such call is refused until that command is on the transcript.`;
2475
2208
  }
2476
2209
  function row_for(oracle, firing_point) {
2477
2210
  const names = command_names(oracle);
@@ -2537,14 +2270,16 @@ function is_destructive_segment2(segment) {
2537
2270
  function is_destructive_command(command) {
2538
2271
  return shell_segments.segments_of(command).some((segment) => is_destructive_segment2(segment));
2539
2272
  }
2540
- 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.**";
2541
- var ROW11 = {
2273
+ var DESTRUCTIVE_COMMAND_REASON = "\u26D4 destructive command: a recursive forced `rm`, a `gh repo delete` / `archive` / visibility edit, `gh pr close` or a `gh api -X DELETE` cannot be undone, which is Tier C. Remove one path at a time without `-f`, or ask the user to run it. It fires on every occurrence (`prompts/collaboration-workflow/operating-rules.md`).";
2274
+ var ROW12 = {
2542
2275
  id: "destructive-command",
2543
2276
  is_trigger: bash_triggers.on_bash_command(is_destructive_command),
2544
2277
  reason: DESTRUCTIVE_COMMAND_REASON,
2545
- decide: () => true
2278
+ decide: () => true,
2279
+ // The opening the reason carried before it was cut to the action and the next command.
2280
+ former_reasons: ["\u26D4 destructive command: a recursive forced `rm` (`rm -rf`"]
2546
2281
  };
2547
- var destructive_command = { DESTRUCTIVE_COMMAND_REASON, ROW: ROW11, is_destructive_command };
2282
+ var destructive_command = { DESTRUCTIVE_COMMAND_REASON, ROW: ROW12, is_destructive_command };
2548
2283
 
2549
2284
  // scripts/rules/direct-pr-create.ts
2550
2285
  var GH_COMMAND2 = "gh";
@@ -2599,13 +2334,13 @@ function is_direct_pr_create(command) {
2599
2334
  return shell_segments.segments_of(command).some((segment) => is_pr_create_segment(segment));
2600
2335
  }
2601
2336
  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.**";
2602
- var ROW12 = {
2337
+ var ROW13 = {
2603
2338
  id: "direct-pr-create",
2604
2339
  is_trigger: bash_triggers.on_bash_command(is_direct_pr_create),
2605
2340
  reason: DIRECT_PR_CREATE_REASON,
2606
2341
  decide: () => true
2607
2342
  };
2608
- var direct_pr_create = { DIRECT_PR_CREATE_REASON, ROW: ROW12, is_direct_pr_create };
2343
+ var direct_pr_create = { DIRECT_PR_CREATE_REASON, ROW: ROW13, is_direct_pr_create };
2609
2344
 
2610
2345
  // scripts/rules/worktree-guard.ts
2611
2346
  var CHECKOUT_SUBCOMMAND = "checkout";
@@ -2666,15 +2401,15 @@ function is_unauthorized_segment(segment) {
2666
2401
  function is_unauthorized_worktree_change(command) {
2667
2402
  return shell_segments.segments_of(command).some((segment) => is_unauthorized_segment(segment));
2668
2403
  }
2669
- 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. A plan that restores a handed-over stash with `git stash apply` / `git stash pop` is read as `pnpm josh stash:pop "<message>"`: apply it that way, continue, and record the substitution on the Issue \u2014 Tier A, never a `needs-decision` park unless the plan says why the owner keeps the stash: then stop and confirm (`operating-rules.md` \u2192 "no-self-staging"). **This rule fires on every occurrence, not once per run.**';
2670
- var ROW13 = {
2404
+ var WORKTREE_MUTATION_REASON = '\u26D4 unauthorized working-tree change: `git checkout --`, `git restore`, `git clean -f`, `git stash`. A documented flow stashes with `git stash push -u -m "<message>"`; a handed-over stash is `pnpm josh stash:pop "<message>"`; record the substitution on the Issue (Tier A) unless the plan says why the owner keeps the stash: then stop and confirm. It fires on every occurrence (`operating-rules.md`).';
2405
+ var ROW14 = {
2671
2406
  id: "worktree-mutation",
2672
2407
  is_trigger: bash_triggers.on_bash_command(is_unauthorized_worktree_change),
2673
2408
  reason: WORKTREE_MUTATION_REASON,
2674
2409
  decide: () => true
2675
2410
  };
2676
2411
  var worktree_guard = {
2677
- ROW: ROW13,
2412
+ ROW: ROW14,
2678
2413
  WORKTREE_MUTATION_REASON,
2679
2414
  is_unauthorized_worktree_change,
2680
2415
  touches_worktree
@@ -2702,46 +2437,46 @@ function is_index_segment(segment) {
2702
2437
  function is_index_mutation(command) {
2703
2438
  return shell_segments.segments_of(command).some((segment) => is_index_segment(segment));
2704
2439
  }
2705
- 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.**';
2706
- var ROW14 = {
2440
+ 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. Commit with `pnpm josh git -y`; any other staging is the user's, on explicit instruction. It fires on every occurrence (`prompts/collaboration-workflow/operating-rules.md` \u2192 \"no-self-staging\").";
2441
+ var ROW15 = {
2707
2442
  id: "index-mutation",
2708
2443
  is_trigger: bash_triggers.on_bash_command(is_index_mutation),
2709
2444
  reason: INDEX_MUTATION_REASON,
2710
2445
  decide: () => true
2711
2446
  };
2712
- var index_guard = { INDEX_MUTATION_REASON, ROW: ROW14, is_index_mutation };
2447
+ var index_guard = { INDEX_MUTATION_REASON, ROW: ROW15, is_index_mutation };
2713
2448
 
2714
2449
  // scripts/rules/protected-files.ts
2715
- import path5 from "node:path";
2450
+ import path3 from "node:path";
2716
2451
 
2717
2452
  // scripts/doctor/doctor-consumer.ts
2718
2453
  import { existsSync as existsSync2 } from "node:fs";
2719
- import path4 from "node:path";
2454
+ import path2 from "node:path";
2720
2455
 
2721
2456
  // scripts/version/kit-descriptor.ts
2722
2457
  var KIT_PACKAGE_NAME = "@joshuafolkken/kit";
2723
2458
 
2724
2459
  // scripts/doctor/doctor-consumer.ts
2725
- import { z as z7 } from "zod";
2460
+ import { z as z6 } from "zod";
2726
2461
  var NODE_MODULES = "node_modules";
2727
- var SETTINGS_PATH = path4.join(".claude", "settings.json");
2462
+ var SETTINGS_PATH = path2.join(".claude", "settings.json");
2728
2463
  var CLAUDE_MD = "CLAUDE.md";
2729
2464
  var PLUGIN_ID = "kit@kit";
2730
2465
  var CLAUDE_MD_POINTER = `@${NODE_MODULES}/${KIT_PACKAGE_NAME}/dist/${CLAUDE_MD}`;
2731
- var SECRETLINT_BIN = path4.join(NODE_MODULES, ".bin", "secretlint");
2732
- var manifest_schema = z7.object({
2733
- name: z7.string().optional(),
2734
- dependencies: z7.record(z7.string(), z7.string()).optional(),
2735
- devDependencies: z7.record(z7.string(), z7.string()).optional()
2466
+ var SECRETLINT_BIN = path2.join(NODE_MODULES, ".bin", "secretlint");
2467
+ var manifest_schema = z6.object({
2468
+ name: z6.string().optional(),
2469
+ dependencies: z6.record(z6.string(), z6.string()).optional(),
2470
+ devDependencies: z6.record(z6.string(), z6.string()).optional()
2736
2471
  });
2737
- var settings_schema = z7.object({
2738
- enabledPlugins: z7.record(z7.string(), z7.boolean()).optional()
2472
+ var settings_schema = z6.object({
2473
+ enabledPlugins: z6.record(z6.string(), z6.boolean()).optional()
2739
2474
  });
2740
2475
  var STATE_OK = "ok";
2741
2476
  var STATE_TARGET_MISSING = "target-missing";
2742
2477
  var STATE_MISSING = "missing";
2743
2478
  function parse_manifest(root) {
2744
- const content = file_reader.read_if_readable(path4.join(root, "package.json"));
2479
+ const content = file_reader.read_if_readable(path2.join(root, "package.json"));
2745
2480
  if (content === void 0) return void 0;
2746
2481
  const parsed = manifest_schema.safeParse(json_value.parse_or_undefined(content));
2747
2482
  return parsed.success ? parsed.data : void 0;
@@ -2753,10 +2488,10 @@ function lists_kit_dependency(manifest) {
2753
2488
  function is_kit_consumer(root) {
2754
2489
  const manifest = parse_manifest(root);
2755
2490
  if (manifest?.name === KIT_PACKAGE_NAME) return false;
2756
- return lists_kit_dependency(manifest) || existsSync2(path4.join(root, NODE_MODULES, KIT_PACKAGE_NAME));
2491
+ return lists_kit_dependency(manifest) || existsSync2(path2.join(root, NODE_MODULES, KIT_PACKAGE_NAME));
2757
2492
  }
2758
2493
  function is_plugin_declared(root) {
2759
- const content = file_reader.read_if_readable(path4.join(root, SETTINGS_PATH));
2494
+ const content = file_reader.read_if_readable(path2.join(root, SETTINGS_PATH));
2760
2495
  if (content === void 0) return false;
2761
2496
  const parsed = settings_schema.safeParse(json_value.parse_or_undefined(content));
2762
2497
  return parsed.success && parsed.data.enabledPlugins?.[PLUGIN_ID] === true;
@@ -2768,13 +2503,13 @@ function configured_hooks_path(root) {
2768
2503
  return value === "" ? void 0 : value;
2769
2504
  }
2770
2505
  function claude_md_state(root) {
2771
- const content = file_reader.read_if_readable(path4.join(root, CLAUDE_MD));
2506
+ const content = file_reader.read_if_readable(path2.join(root, CLAUDE_MD));
2772
2507
  if (content === void 0) return STATE_MISSING;
2773
2508
  if (!content.includes(CLAUDE_MD_POINTER)) return STATE_OK;
2774
- return existsSync2(path4.join(root, NODE_MODULES, KIT_PACKAGE_NAME, "dist", CLAUDE_MD)) ? STATE_OK : STATE_TARGET_MISSING;
2509
+ return existsSync2(path2.join(root, NODE_MODULES, KIT_PACKAGE_NAME, "dist", CLAUDE_MD)) ? STATE_OK : STATE_TARGET_MISSING;
2775
2510
  }
2776
2511
  function is_secretlint_runnable(root) {
2777
- return existsSync2(path4.join(root, SECRETLINT_BIN));
2512
+ return existsSync2(path2.join(root, SECRETLINT_BIN));
2778
2513
  }
2779
2514
  function status_line(is_ok, ok_text, warn_text) {
2780
2515
  return is_ok ? ` \u2713 ${ok_text}` : ` \u26A0 ${warn_text}`;
@@ -2840,7 +2575,7 @@ var EDIT_TOOLS2 = /* @__PURE__ */ new Set(["Edit", "Write"]);
2840
2575
  var SECRET_FILES = /* @__PURE__ */ new Set([".env", ".dev.vars"]);
2841
2576
  var ENVIRONMENT_VARIANT_PREFIX = ".env.";
2842
2577
  var TRACKED_VARIANTS = /* @__PURE__ */ new Set([".env.example", ".env.test"]);
2843
- var SETTINGS_FILE = path5.join(".claude", "settings.json");
2578
+ var SETTINGS_FILE = path3.join(".claude", "settings.json");
2844
2579
  var WHITESPACE3 = /\s+/u;
2845
2580
  var TOKEN_QUOTES = /["']/gu;
2846
2581
  var INPUT_REDIRECT = "<";
@@ -2870,7 +2605,7 @@ function file_path_of(call) {
2870
2605
  return typeof value === "string" ? value : "";
2871
2606
  }
2872
2607
  function is_environment_path(token) {
2873
- const name = path5.basename(token.replaceAll(TOKEN_QUOTES, ""));
2608
+ const name = path3.basename(token.replaceAll(TOKEN_QUOTES, ""));
2874
2609
  if (TRACKED_VARIANTS.has(name)) return false;
2875
2610
  return SECRET_FILES.has(name) || name.startsWith(ENVIRONMENT_VARIANT_PREFIX);
2876
2611
  }
@@ -2891,21 +2626,23 @@ function is_environment_read(call) {
2891
2626
  }
2892
2627
  function is_consumer_settings_edit(call, is_kit_consumer2) {
2893
2628
  if (!EDIT_TOOLS2.has(call.name)) return false;
2894
- const file_path = path5.resolve(file_path_of(call));
2895
- if (!file_path.endsWith(`${path5.sep}${SETTINGS_FILE}`)) return false;
2896
- return is_kit_consumer2(path5.dirname(path5.dirname(file_path)));
2629
+ const file_path = path3.resolve(file_path_of(call));
2630
+ if (!file_path.endsWith(`${path3.sep}${SETTINGS_FILE}`)) return false;
2631
+ return is_kit_consumer2(path3.dirname(path3.dirname(file_path)));
2897
2632
  }
2898
2633
  function is_protected_file_call(call, is_kit_consumer2 = doctor_consumer.is_kit_consumer) {
2899
2634
  return is_environment_read(call) || is_consumer_settings_edit(call, is_kit_consumer2);
2900
2635
  }
2901
- var PROTECTED_FILE_REASON = '\u26D4 protected file: `.env` holds the Telegram bot token, and a `.env.*` variant or `.dev.vars` holds the same kind of secret, so reading one \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 (the tracked `.env.example` and `.env.test` stay readable). `.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 these files alone, or ask the user. **This rule fires on every occurrence, not once per run.**';
2902
- var ROW15 = {
2636
+ var PROTECTED_FILE_REASON = "\u26D4 protected file: `.env`, a `.env.*` variant and `.dev.vars` hold secrets, and a consumer `.claude/settings.json` is owned by `josh sync` (a change belongs upstream in kit). Leave these files alone, or ask the user. It fires on every occurrence.";
2637
+ var ROW16 = {
2903
2638
  id: "protected-file",
2904
2639
  is_trigger: is_protected_file_call,
2905
2640
  reason: PROTECTED_FILE_REASON,
2906
- decide: () => true
2641
+ decide: () => true,
2642
+ // The opening the reason carried before it was cut to the action and the next command.
2643
+ former_reasons: ["\u26D4 protected file: `.env` holds the Telegram bot token"]
2907
2644
  };
2908
- var protected_files = { ROW: ROW15, is_protected_file_call };
2645
+ var protected_files = { ROW: ROW16, is_protected_file_call };
2909
2646
 
2910
2647
  // scripts/rules/permission-guards.ts
2911
2648
  var ROWS2 = [
@@ -2940,7 +2677,7 @@ function is_verification_command(segment) {
2940
2677
  function is_masked_verification(command) {
2941
2678
  return time_shell.discarded_commands(command).some((segment) => is_verification_command(segment));
2942
2679
  }
2943
- var PIPED_VERIFICATION_REASON = "\u26D4 piped verification: a pipeline exits with its last command's status, so `pnpm josh gate | tail` reports success on a gate that failed, and the verdict is discarded before anything reads it. Run the check without the pipe \u2014 josh prints its verdict line last, so the harness output cap keeps it even when the middle is elided. Where the output genuinely has to be narrowed, redirect it to a file and read ranges from that file, or prefix `set -o pipefail` so the pipeline carries the check's status. Read the printed verdict either way, never the exit code alone. Read-only listings are untouched \u2014 this fires only on a command whose result means pass or fail. The rule is in `prompts/collaboration-workflow/output-bounds.md`. Reissue this call with no pipe \u2014 it fires once per run and cannot repeat on the call in hand.";
2680
+ var PIPED_VERIFICATION_REASON = "\u26D4 piped verification: a pipeline exits with its last command's status, so `pnpm josh gate | tail` reports success on a gate that failed. Reissue this call with no pipe (josh prints its verdict last), or redirect it to a file and read ranges from that file, or prefix `set -o pipefail`; read the verdict line, never the exit code alone (`prompts/collaboration-workflow/output-bounds.md`).";
2944
2681
  function pipelines_of(command) {
2945
2682
  return shell_segments.segments_of(time_shell.unquoted(command)).map((segment) => segment.split("|").map((piece) => piece.trim()));
2946
2683
  }
@@ -2953,12 +2690,15 @@ function runs_verification(command) {
2953
2690
  function keeps_verdict_intact(command) {
2954
2691
  return runs_verification(command) && !is_masked_verification(command);
2955
2692
  }
2956
- var PIPEFAIL_PREFIX = "set -o pipefail; ";
2957
- var FULL_READING_FILTER = /^(?:tail|grep)\b/u;
2693
+ var PIPEFAIL_OPEN = "(set -o pipefail; ";
2694
+ var PIPEFAIL_CLOSE = ")";
2695
+ var COMMENT_START = /(?:^|\s)#/u;
2696
+ var FULL_READING_FILTER = /^(?:tail|grep|cut|sort|uniq|wc)\b/u;
2958
2697
  var EARLY_EXIT_GREP = /^grep\b.*\s(?:-[a-zA-Z]*[lLmq]|--(?:quiet|silent|max-count|files-with(?:out)?-match))/u;
2959
2698
  var ALLOWED_OPERATORS = /2>&1|&&/gu;
2960
2699
  var SIDE_EFFECT_SYNTAX = /[&<>`]|\$\(/u;
2961
- var PIPEFAIL_NOTE = "\u21BB piped verification: the josh check was piped, so `set -o pipefail;` was prefixed and the call ran with the check's status carried to the exit code. Read the printed verdict, never the exit code alone \u2014 `prompts/collaboration-workflow/output-bounds.md` (joshuafolkken/kit#3570).";
2700
+ var GROUP_SYNTAX = /[()]/u;
2701
+ var PIPEFAIL_NOTE = "\u21BB piped verification: the josh check was piped, so its pipeline was wrapped in `(set -o pipefail; \u2026)` to carry the check's status, and the rest of the line ran as typed. Read the printed verdict, never the exit code alone \u2014 `prompts/collaboration-workflow/output-bounds.md`.";
2962
2702
  function reads_to_the_end(piece) {
2963
2703
  return FULL_READING_FILTER.test(piece) && !EARLY_EXIT_GREP.test(piece);
2964
2704
  }
@@ -2969,14 +2709,40 @@ function is_filtered_check(pipeline) {
2969
2709
  function has_side_effect_syntax(command) {
2970
2710
  return SIDE_EFFECT_SYNTAX.test(command.replaceAll(ALLOWED_OPERATORS, " "));
2971
2711
  }
2972
- function is_pipefail_rewritable(command) {
2973
- return is_masked_verification(command) && !has_side_effect_syntax(command) && pipelines_of(command).every((pipeline) => is_filtered_check(pipeline));
2712
+ function is_rewritable_part(raw, judged) {
2713
+ const pipeline = judged.split("|").map((piece) => piece.trim());
2714
+ return !has_side_effect_syntax(raw) && !GROUP_SYNTAX.test(judged) && is_filtered_check(pipeline);
2715
+ }
2716
+ function code_end(judged) {
2717
+ const comment = COMMENT_START.exec(judged);
2718
+ return comment === null ? judged.length : comment.index;
2719
+ }
2720
+ function wrapped(raw, judged) {
2721
+ const code = raw.slice(0, code_end(judged));
2722
+ const core = code.trim();
2723
+ const lead = code.slice(0, code.indexOf(core));
2724
+ return `${lead}${PIPEFAIL_OPEN}${core}${PIPEFAIL_CLOSE}${raw.slice(lead.length + core.length)}`;
2725
+ }
2726
+ function rewritten_part(raw, judged) {
2727
+ if (!is_masked_verification(judged)) return raw;
2728
+ return is_rewritable_part(raw, judged) ? wrapped(raw, judged) : void 0;
2729
+ }
2730
+ function pipefail_command(command) {
2731
+ const judged = time_shell.blanked(command);
2732
+ if (judged === void 0 || !is_masked_verification(command)) return void 0;
2733
+ const offset = { at: 0 };
2734
+ const parts = shell_segments.parts_of(judged).map((part) => {
2735
+ const raw = command.slice(offset.at, offset.at + part.length);
2736
+ offset.at += part.length;
2737
+ return rewritten_part(raw, part);
2738
+ });
2739
+ const rewritten = parts.includes(void 0) ? void 0 : parts.join("");
2740
+ return rewritten === command ? void 0 : rewritten;
2974
2741
  }
2975
2742
  function pipefail_input(call) {
2976
2743
  if (call.name !== cost_blocks.BASH_TOOL || !json_value.is_record(call.input)) return void 0;
2977
- const command = time_shell.bash_command(call.input);
2978
- if (!is_pipefail_rewritable(command)) return void 0;
2979
- return { ...call.input, command: `${PIPEFAIL_PREFIX}${command}` };
2744
+ const command = pipefail_command(time_shell.bash_command(call.input));
2745
+ return command === void 0 ? void 0 : { ...call.input, command };
2980
2746
  }
2981
2747
  var piped_verification = {
2982
2748
  PIPED_VERIFICATION_REASON,
@@ -3013,8 +2779,8 @@ function is_poll_loop(command) {
3013
2779
  if (is_state_wait(plain)) return true;
3014
2780
  return LOOP_KEYWORD2.test(plain) && SLEEP_COMMAND.test(plain);
3015
2781
  }
3016
- 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.**';
3017
- var ROW16 = {
2782
+ 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, and the loop does not know when the command finished. Issue the long command with `run_in_background` set and end the turn \u2014 its exit re-invokes you; for CI, `pnpm josh followup` (`.claude/skills/workflow-commands/background-commands.md`). It fires on every occurrence.";
2783
+ var ROW17 = {
3018
2784
  id: "poll-loop",
3019
2785
  is_trigger: bash_triggers.on_bash_command(is_poll_loop),
3020
2786
  reason: POLL_LOOP_REASON,
@@ -3022,87 +2788,23 @@ var ROW16 = {
3022
2788
  };
3023
2789
  var poll_loop = {
3024
2790
  POLL_LOOP_REASON,
3025
- ROW: ROW16,
2791
+ ROW: ROW17,
3026
2792
  is_poll_loop
3027
2793
  };
3028
2794
 
3029
- // scripts/rules/pre-gate-cut.ts
3030
- var GATE_COMMANDS = /* @__PURE__ */ new Set(["gate"]);
3031
- var CUT_COMMANDS2 = /* @__PURE__ */ new Set(["run:cut"]);
3032
- var CUT_MODE_FLAG = /(?:^|\s)--(?:resume|end|json)(?:[=\s]|$)/u;
3033
- var HOLD_COMMANDS = /* @__PURE__ */ new Set(["run:hold"]);
3034
- var HOLD_MODE_FLAG = /(?:^|\s)--release(?:[=\s]|$)/u;
3035
- function invokes_josh(command, names, mode_flag) {
3036
- return shell_segments.segments_of(command).some(
3037
- (segment) => shell_segments.is_josh_command(segment, names) && mode_flag?.test(segment) !== true
3038
- );
3039
- }
3040
- function runs_the_gate(command) {
3041
- return invokes_josh(command, GATE_COMMANDS);
3042
- }
3043
- function takes_the_cut(command) {
3044
- return invokes_josh(command, CUT_COMMANDS2, CUT_MODE_FLAG);
3045
- }
3046
- function claims_the_hold(command) {
3047
- return invokes_josh(command, HOLD_COMMANDS, HOLD_MODE_FLAG);
3048
- }
3049
- function asks_about_the_cut(command) {
3050
- return invokes_josh(command, CUT_COMMANDS2);
3051
- }
3052
- function current_state2() {
3053
- return {
3054
- directory: process.cwd(),
3055
- carried: run_cut.carried_cut_sync,
3056
- marked_issue: lane_child_marker.marked_issue(),
3057
- is_reviewer: agent_session_role.is_reviewer(),
3058
- context_verdict: cost_cli.session_verdict
3059
- };
3060
- }
3061
- function dispatched_issue(state) {
3062
- return state.is_reviewer ? void 0 : state.marked_issue;
3063
- }
3064
- function uncut_lane_issue(state) {
3065
- const issue = lane_paths.lane_issue_of(state.directory);
3066
- if (issue === void 0) return void 0;
3067
- if (dispatched_issue(state) !== issue) return void 0;
3068
- return state.carried()?.issue === issue ? void 0 : issue;
3069
- }
3070
- function warrants_the_cut2(verdict) {
3071
- return verdict !== cost_verdict.UNDER_VERDICT;
3072
- }
3073
- function is_uncut_gate(command, state = current_state2()) {
3074
- if (!runs_the_gate(command) || uncut_lane_issue(state) === void 0) return false;
3075
- return warrants_the_cut2(state.context_verdict());
3076
- }
3077
- var PRE_GATE_CUT_REASON = "\u26D4 pre-gate cut: this checkout is a lane dispatched for this issue and the gate is the boundary to cut at, so take the cut before running it. `pnpm josh run:cut <N>` ends this process and relaunches a fresh one that resumes from the gate, dropping the thinking built up while implementing rather than carrying it on every later request \u2014 joshuafolkken/kit#1839 measured 176K of 204K output riding across that boundary, and joshuafolkken/kit#1864 measured the step itself firing 0 times in 6 lane children while it was carried as prose. Issue `pnpm josh run:cut <N>` now, and read the verdict: on `cut`, **end the turn immediately** \u2014 the fresh process owns the run from there and must not be waited for, and **no Telegram is owed** for a cut that resumes itself; on `not-a-lane`, `unready`, `busy`, `failed` or `unknown`, this process carries the run on and the gate is simply the next call. Never relaunch a second process after `busy`. This fired because the dispatch mark names this lane; a person working here carries no mark and sees no refusal, so there is no human-or-child judgement left to make. The procedure is `.claude/skills/workflow-commands/pre-gate-cut.md`. Reissue the gate once the cut has answered \u2014 this fires once per run, so it cannot repeat on the call in hand.";
3078
- function reaches_the_pre_gate_boundary(call, _turn, run2) {
3079
- if (!bash_triggers.on_bash_command(asks_about_the_cut)(call)) return false;
3080
- return run2.some((issued) => bash_triggers.on_bash_command(claims_the_hold)(issued));
3081
- }
3082
- var pre_gate_cut = {
3083
- PRE_GATE_CUT_REASON,
3084
- asks_about_the_cut,
3085
- claims_the_hold,
3086
- is_uncut_gate,
3087
- reaches_the_pre_gate_boundary,
3088
- runs_the_gate,
3089
- takes_the_cut,
3090
- uncut_lane_issue
3091
- };
3092
-
3093
2795
  // scripts/rules/raw-field-body.ts
3094
2796
  var RAW_FIELD_BODY_AT = /(?:^|\s)(?:-f|--raw-field)[\s=]+['"]?body=['"]?@/u;
3095
2797
  function posts_at_path_literally(command) {
3096
2798
  return RAW_FIELD_BODY_AT.test(command);
3097
2799
  }
3098
- 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.";
3099
- var ROW17 = {
2800
+ 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 posts the string `@<path>` rather than the file. Post the comment with `pnpm josh issue:comment <N> --body-file <path>`; this rule fires on every occurrence (`prompts/collaboration-workflow/shell-body.md`).";
2801
+ var ROW18 = {
3100
2802
  id: "raw-field-body",
3101
2803
  is_trigger: bash_triggers.on_bash_command(posts_at_path_literally),
3102
2804
  reason: RAW_FIELD_BODY_REASON,
3103
2805
  decide: () => true
3104
2806
  };
3105
- var raw_field_body = { RAW_FIELD_BODY_REASON, ROW: ROW17, posts_at_path_literally };
2807
+ var raw_field_body = { RAW_FIELD_BODY_REASON, ROW: ROW18, posts_at_path_literally };
3106
2808
 
3107
2809
  // scripts/rules/rule-body-guard.ts
3108
2810
  var EDIT_TOOL = "Edit";
@@ -3154,7 +2856,7 @@ function writes_rule_prose(call, read2 = read_existing) {
3154
2856
  const { old_text, new_text } = texts_of(call.name, input, read2);
3155
2857
  return adds_rule_prose(old_text, new_text);
3156
2858
  }
3157
- var RULE_BODY_REASON = "\u26D4 rule written into prose: this edit adds rule text to CLAUDE.md, a `prompts/**` doc or a skill body. Before it lands, run the residency questions the addition skips. A rule whose answer is computable from mechanically readable inputs should be a decision oracle, not prose \u2014 question 0; `pnpm josh oracle:list` lists the existing oracles a new one joins (single source `scripts/rules/decision-oracle.ts`). A rule that decides *when* or *in what order* to act belongs in the run driver, not prose \u2014 the ordering question; `pnpm josh run:step` is the state machine a sequencing rule moves into. Only a rule that is neither \u2014 not computable, not ordering \u2014 stays prose, and then as a trigger-plus-pointer line rather than a second copy of its procedure. The criterion is `prompts/collaboration-workflow/residency.md` (question 0 and the ordering question); the delivery enumeration is `prompts/collaboration-workflow/rule-delivery.md`. Every such edit is refused until both commands are on the transcript \u2014 run them, confirm the rule can be neither an oracle nor an ordering step, then reissue this edit.";
2859
+ var RULE_BODY_REASON = "\u26D4 rule written into prose: this edit adds rule text to CLAUDE.md, a `prompts/**` doc or a skill body. Answer question 0 (`pnpm josh oracle:list`) and the ordering question (`pnpm josh run:step`) of `prompts/collaboration-workflow/residency.md`; refused until both commands are on the transcript, then reissue this as a trigger-plus-pointer line (`prompts/collaboration-workflow/rule-delivery.md`).";
3158
2860
  var PLACEMENT_ORACLES = [
3159
2861
  /* @__PURE__ */ new Set(["oracle:list"]),
3160
2862
  /* @__PURE__ */ new Set(["run:step", "run:next"])
@@ -3173,7 +2875,7 @@ function runs_placement_oracle(command) {
3173
2875
  (names) => shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, names))
3174
2876
  );
3175
2877
  }
3176
- var ROW18 = {
2878
+ var ROW19 = {
3177
2879
  id: "rule-body",
3178
2880
  is_trigger: writes_rule_prose,
3179
2881
  reason: RULE_BODY_REASON,
@@ -3182,7 +2884,7 @@ var ROW18 = {
3182
2884
  };
3183
2885
  var rule_body_guard = {
3184
2886
  RULE_BODY_REASON,
3185
- ROW: ROW18,
2887
+ ROW: ROW19,
3186
2888
  answered_placement,
3187
2889
  is_rule_document,
3188
2890
  writes_rule_prose
@@ -3250,7 +2952,7 @@ function carries_a_body(command) {
3250
2952
  if (BODY_FLAG.test(command.replaceAll(QUOTED_SPAN, " "))) return true;
3251
2953
  return inline_body_literals(command).length > 0;
3252
2954
  }
3253
- var SHELL_BODY_REASON = "\u26D4 shell-evaluated body: this command carries a body inline in double quotes, and that body contains a backtick or a `$`, so the shell runs it \u2014 the text is executed rather than merely mangled (joshuafolkken/kit#1198). Write the body to a file and pass it by path: `pnpm josh issue:comment <N> --body-file <path>`, `pnpm josh followup --notify-message-file <path>`, or `--body-file <path>` wherever offered. `$'\u2026'` quoting is the other safe form; the rule and the trigger's blind spots are in `prompts/collaboration-workflow/shell-body.md`. Reissue this call once the body is in a file \u2014 it fires once per run and cannot repeat on the call in hand.";
2955
+ var SHELL_BODY_REASON = "\u26D4 shell-evaluated body: this command carries a body inline in double quotes, and that body contains a backtick or a `$`, so the shell executes it. Reissue this call once the body is in a file, passed by path (`pnpm josh issue:comment <N> --body-file <path>`, `--notify-message-file <path>`), or use `$'\u2026'` quoting (`prompts/collaboration-workflow/shell-body.md`).";
3254
2956
  var shell_body_trigger = {
3255
2957
  SHELL_BODY_REASON,
3256
2958
  carries_a_body,
@@ -3259,12 +2961,12 @@ var shell_body_trigger = {
3259
2961
  };
3260
2962
 
3261
2963
  // scripts/gate/project-checks.ts
3262
- import { existsSync as existsSync7, globSync as globSync2, readFileSync as readFileSync6 } from "node:fs";
3263
- import path13 from "node:path";
2964
+ import { existsSync as existsSync7, globSync, readFileSync as readFileSync5 } from "node:fs";
2965
+ import path11 from "node:path";
3264
2966
 
3265
2967
  // scripts/init/own-formatter.ts
3266
2968
  import { existsSync as existsSync3 } from "node:fs";
3267
- import path6 from "node:path";
2969
+ import path4 from "node:path";
3268
2970
  var OWN_FORMATTER_CONFIGS = [
3269
2971
  "biome.json",
3270
2972
  "biome.jsonc",
@@ -3274,18 +2976,18 @@ var OWN_FORMATTER_CONFIGS = [
3274
2976
  ".dprint.jsonc"
3275
2977
  ];
3276
2978
  function find_config(root) {
3277
- return OWN_FORMATTER_CONFIGS.find((name) => existsSync3(path6.join(root, name)));
2979
+ return OWN_FORMATTER_CONFIGS.find((name) => existsSync3(path4.join(root, name)));
3278
2980
  }
3279
2981
  var own_formatter = { find_config };
3280
2982
 
3281
2983
  // scripts/init/project-profile.ts
3282
- import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync5 } from "node:fs";
3283
- import path12 from "node:path";
2984
+ import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync4 } from "node:fs";
2985
+ import path10 from "node:path";
3284
2986
 
3285
2987
  // scripts/safe-chain/pack-hook.ts
3286
- import { z as z8 } from "zod";
2988
+ import { z as z7 } from "zod";
3287
2989
  var PACK_HOOK_FILE = ".pnpmfile.mjs";
3288
- var manifest_schema2 = z8.looseObject({ private: z8.boolean().optional() });
2990
+ var manifest_schema2 = z7.looseObject({ private: z7.boolean().optional() });
3289
2991
  var MESSAGES = {
3290
2992
  written: ` \u2714 synced ${PACK_HOOK_FILE} (run \`pnpm install\` to refresh pnpmfileChecksum)`,
3291
2993
  unchanged: ` \u2714 unchanged ${PACK_HOOK_FILE}`,
@@ -3321,15 +3023,15 @@ var yaml_document = { is_mapping_document, parse_yaml, parse_yaml_documents };
3321
3023
 
3322
3024
  // scripts/version/release-age.ts
3323
3025
  import semver from "semver";
3324
- import { z as z9 } from "zod";
3325
- var workspace_age_schema = z9.object({ minimumReleaseAge: z9.number().int().nonnegative() });
3326
- var release_times_schema = z9.record(z9.string(), z9.string());
3026
+ import { z as z8 } from "zod";
3027
+ var workspace_age_schema = z8.object({ minimumReleaseAge: z8.number().int().nonnegative() });
3028
+ var release_times_schema = z8.record(z8.string(), z8.string());
3327
3029
 
3328
3030
  // scripts/safe-chain/project-config.ts
3329
3031
  import { dump } from "js-yaml";
3330
- import { z as z10 } from "zod";
3032
+ import { z as z9 } from "zod";
3331
3033
  var PROJECT_CONFIG_FILE = ".aikido";
3332
- var workspace_schema = z10.object({ minimumReleaseAgeExclude: z10.array(z10.string()).optional() });
3034
+ var workspace_schema = z9.object({ minimumReleaseAgeExclude: z9.array(z9.string()).optional() });
3333
3035
 
3334
3036
  // scripts/config-merge/json-format.ts
3335
3037
  var PRINT_WIDTH = 100;
@@ -3374,9 +3076,9 @@ function inline_short_arrays(text) {
3374
3076
  const output = [];
3375
3077
  let index = 0;
3376
3078
  while (index < lines.length) {
3377
- const rewrite = rewrite_at(lines, index, lines[index] ?? "");
3378
- output.push(...rewrite.lines);
3379
- index = rewrite.next;
3079
+ const rewrite3 = rewrite_at(lines, index, lines[index] ?? "");
3080
+ output.push(...rewrite3.lines);
3081
+ index = rewrite3.next;
3380
3082
  }
3381
3083
  return output.join("\n");
3382
3084
  }
@@ -3411,86 +3113,6 @@ var safe_chain_preinstall = {
3411
3113
  migrate_preinstall
3412
3114
  };
3413
3115
 
3414
- // scripts/init/distributed-paths.ts
3415
- var KIT_PACKAGE_PATH_PREFIX = "node_modules/@joshuafolkken/kit/";
3416
- var PROMPTS_PACKAGE_PREFIX = `${KIT_PACKAGE_PATH_PREFIX}prompts/`;
3417
- var ESLINT_PACKAGE_PREFIX = `${KIT_PACKAGE_PATH_PREFIX}eslint/`;
3418
- var GITHUB_REPO_BLOB_BASE = "https://github.com/joshuafolkken/kit/blob/main/";
3419
- var GITHUB_REPO_TREE_BASE = "https://github.com/joshuafolkken/kit/tree/main/";
3420
- var CLAUDE_MD_IMPORT_LINE = "@node_modules/@joshuafolkken/kit/dist/CLAUDE.md";
3421
- var OLD_CLAUDE_MD_BOOTSTRAP = "> Fresh checkout: if kit is not installed, run `pnpm install` first, then reread this file before doing any other work.";
3422
- var CLAUDE_MD_BOOTSTRAP = '> Fresh checkout: before `pnpm install`, run `pnpm config get "@joshuafolkken:registry"` from the project root to check the effective registry, including user-level settings. If it points to GitHub Packages, [set up authentication](https://github.com/joshuafolkken/kit/blob/main/docs/authentication.md#1-get-a-token-from-the-gh-cli) first. Then run `pnpm install` and reread this file before doing any other work.';
3423
- function transform_prompt_paths(content) {
3424
- return content.replaceAll(
3425
- /`prompts\/([^`*]+)`/gu,
3426
- (_match, prompt_path) => `\`${PROMPTS_PACKAGE_PREFIX}${prompt_path}\``
3427
- );
3428
- }
3429
- function transform_eslint_paths(content) {
3430
- return content.replaceAll(
3431
- /`eslint\/([^`*]+)`/gu,
3432
- (_match, eslint_path) => `\`${ESLINT_PACKAGE_PREFIX}${eslint_path}\``
3433
- );
3434
- }
3435
- function transform_test_file_paths(content) {
3436
- return content.replaceAll(
3437
- /`([^`*]+\.(?:test|spec)\.ts)`/gu,
3438
- (_match, test_path) => `\`${GITHUB_REPO_BLOB_BASE}${test_path}\``
3439
- );
3440
- }
3441
- function github_url_base(reference_path) {
3442
- return reference_path.endsWith("/") ? GITHUB_REPO_TREE_BASE : GITHUB_REPO_BLOB_BASE;
3443
- }
3444
- function transform_documentation_paths(content) {
3445
- return content.replaceAll(
3446
- /`(docs\/[^`*]*)`/gu,
3447
- (_match, documentation_path) => `\`${github_url_base(documentation_path)}${documentation_path}\``
3448
- );
3449
- }
3450
- function transform_distributed_paths(content) {
3451
- const with_prompts = transform_prompt_paths(content);
3452
- const with_eslint = transform_eslint_paths(with_prompts);
3453
- const with_tests = transform_test_file_paths(with_eslint);
3454
- return transform_documentation_paths(with_tests);
3455
- }
3456
- function ensure_claude_md_import(existing) {
3457
- if (existing === void 0) return `${CLAUDE_MD_BOOTSTRAP}
3458
-
3459
- ${CLAUDE_MD_IMPORT_LINE}
3460
- `;
3461
- const updated = existing.replace(OLD_CLAUDE_MD_BOOTSTRAP, () => CLAUDE_MD_BOOTSTRAP);
3462
- if (updated.includes(CLAUDE_MD_IMPORT_LINE)) {
3463
- if (updated.includes(CLAUDE_MD_BOOTSTRAP)) return updated;
3464
- return `${CLAUDE_MD_BOOTSTRAP}
3465
-
3466
- ${updated}`;
3467
- }
3468
- return `${CLAUDE_MD_BOOTSTRAP}
3469
-
3470
- ${CLAUDE_MD_IMPORT_LINE}
3471
-
3472
- ${updated}`;
3473
- }
3474
- var FULL_CLAUDE_MD_LINES = /* @__PURE__ */ new Set([
3475
- CLAUDE_MD_BOOTSTRAP,
3476
- OLD_CLAUDE_MD_BOOTSTRAP,
3477
- CLAUDE_MD_IMPORT_LINE
3478
- ]);
3479
- function remove_claude_md_import(content) {
3480
- const lines = content.split("\n");
3481
- return lines.filter((line, index) => {
3482
- if (FULL_CLAUDE_MD_LINES.has(line)) return false;
3483
- return line !== "" || !FULL_CLAUDE_MD_LINES.has(lines[index - 1] ?? "");
3484
- }).join("\n");
3485
- }
3486
- var distributed_paths = {
3487
- transform_prompt_paths,
3488
- transform_distributed_paths,
3489
- ensure_claude_md_import,
3490
- remove_claude_md_import,
3491
- CLAUDE_MD_IMPORT_LINE
3492
- };
3493
-
3494
3116
  // scripts/init/init-logic-deploy-vps.ts
3495
3117
  import semver2 from "semver";
3496
3118
  var PNPM_INSTALL_PATTERN = /npm install -g pnpm@\d[\d.]{0,19}/gu;
@@ -3518,8 +3140,9 @@ function patch_deploy_vps_pnpm(content) {
3518
3140
  PNPM_INSTALL_PATTERN,
3519
3141
  (command) => replace_outdated_install(command)
3520
3142
  );
3521
- if (with_pnpm12.includes(VERSION_CHECK_MARKER)) return with_pnpm12;
3522
- if (!OLD_PNPM_CHECK_PATTERN.test(with_pnpm12)) return with_pnpm12;
3143
+ if (with_pnpm12.includes(VERSION_CHECK_MARKER) || !OLD_PNPM_CHECK_PATTERN.test(with_pnpm12)) {
3144
+ return with_pnpm12;
3145
+ }
3523
3146
  return with_pnpm12.replace(OLD_MAJOR_LINE_PATTERN, "").replace(OLD_PNPM_CHECK_PATTERN, (_, indent) => build_version_check(indent));
3524
3147
  }
3525
3148
  var init_logic_deploy_vps = { patch_deploy_vps_pnpm };
@@ -3771,10 +3394,10 @@ function set_json_key(content, key, value) {
3771
3394
  const indented = fit_value(content, value_text, line_indent(content, existing.offset));
3772
3395
  return with_trailing_newline(replace_range(content, existing, indented));
3773
3396
  }
3774
- function resolve_parent(content, path16) {
3397
+ function resolve_parent(content, path13) {
3775
3398
  const root = parse_root(content);
3776
3399
  if (root === void 0) return void 0;
3777
- const parent = path16.length > 1 ? findNodeAtLocation(root, path16.slice(0, -1)) : root;
3400
+ const parent = path13.length > 1 ? findNodeAtLocation(root, path13.slice(0, -1)) : root;
3778
3401
  return parent?.type === "object" ? parent : void 0;
3779
3402
  }
3780
3403
  function cut_ranges(content, parent, ranges) {
@@ -3785,24 +3408,24 @@ function cut_ranges(content, parent, ranges) {
3785
3408
  }
3786
3409
  return next;
3787
3410
  }
3788
- function rebalance_new_last(content, path16) {
3789
- const parent = resolve_parent(content, path16);
3411
+ function rebalance_new_last(content, path13) {
3412
+ const parent = resolve_parent(content, path13);
3790
3413
  const last = parent?.children?.at(-1);
3791
3414
  if (parent === void 0 || last === void 0) return content;
3792
3415
  return rebalance(content, last, has_separator_after(content, parent.offset, parent.offset));
3793
3416
  }
3794
- function cut_property(content, parent, path16) {
3795
- const index = property_index(parent, path16.at(-1) ?? "");
3417
+ function cut_property(content, parent, path13) {
3418
+ const index = property_index(parent, path13.at(-1) ?? "");
3796
3419
  const ranges = removal_ranges(content, parent, index);
3797
3420
  if (ranges.length === 0) return content;
3798
3421
  const cut = cut_ranges(content, parent, ranges);
3799
3422
  const did_end_object = index === (parent.children ?? []).length - 1;
3800
- return did_end_object ? rebalance_new_last(cut, path16) : cut;
3423
+ return did_end_object ? rebalance_new_last(cut, path13) : cut;
3801
3424
  }
3802
- function remove_json_path(content, path16) {
3803
- const parent = resolve_parent(content, path16);
3425
+ function remove_json_path(content, path13) {
3426
+ const parent = resolve_parent(content, path13);
3804
3427
  if (parent === void 0) return content;
3805
- const next = cut_property(content, parent, path16);
3428
+ const next = cut_property(content, parent, path13);
3806
3429
  return next === content ? content : with_trailing_newline(next);
3807
3430
  }
3808
3431
  function remove_json_key(content, key) {
@@ -4033,13 +3656,13 @@ var kit_base_preset = {
4033
3656
 
4034
3657
  // scripts/init/tsconfig-preset-migration.ts
4035
3658
  import { existsSync as existsSync4 } from "node:fs";
4036
- import path7 from "node:path";
3659
+ import path5 from "node:path";
4037
3660
  var LEGACY_PRESET_ENTRY = /@joshuafolkken\/[^/]+\/tsconfig\/[^/]+\.jsonc$/u;
4038
3661
  var LEGACY_SUFFIX = ".jsonc";
4039
3662
  var CURRENT_SUFFIX = ".json";
4040
3663
  var EXTENDS_FIELD = "extends";
4041
3664
  function is_migration_target_present(base_directory, entry) {
4042
- return existsSync4(path7.resolve(base_directory, entry));
3665
+ return existsSync4(path5.resolve(base_directory, entry));
4043
3666
  }
4044
3667
  function migrate_entry(base_directory, entry) {
4045
3668
  if (!LEGACY_PRESET_ENTRY.test(entry)) return entry;
@@ -4271,15 +3894,15 @@ var init_logic_json_merge = {
4271
3894
  };
4272
3895
 
4273
3896
  // scripts/init/kit-development-versions.ts
4274
- import { readFileSync as readFileSync3 } from "node:fs";
4275
- import { z as z11 } from "zod";
3897
+ import { readFileSync as readFileSync2 } from "node:fs";
3898
+ import { z as z10 } from "zod";
4276
3899
  var PACKAGE_JSON = "package.json";
4277
- var kit_manifest_schema = z11.object({
4278
- peerDependencies: z11.record(z11.string(), z11.string()).default({}),
4279
- devDependencies: z11.record(z11.string(), z11.string())
3900
+ var kit_manifest_schema = z10.object({
3901
+ peerDependencies: z10.record(z10.string(), z10.string()).default({}),
3902
+ devDependencies: z10.record(z10.string(), z10.string())
4280
3903
  });
4281
3904
  function read_kit_manifest() {
4282
- const content = readFileSync3(package_path(PACKAGE_JSON), "utf8");
3905
+ const content = readFileSync2(package_path(PACKAGE_JSON), "utf8");
4283
3906
  return kit_manifest_schema.parse(JSON.parse(content));
4284
3907
  }
4285
3908
  function versions_of(names) {
@@ -4440,9 +4063,9 @@ function merge_sonar_properties(existing, template_content) {
4440
4063
  return key !== void 0 && !existing_keys.has(key);
4441
4064
  });
4442
4065
  if (lines_to_add.length === 0) return merged_existing;
4443
- const normalized2 = merged_existing.endsWith("\n") ? merged_existing : `${merged_existing}
4066
+ const normalized = merged_existing.endsWith("\n") ? merged_existing : `${merged_existing}
4444
4067
  `;
4445
- return `${normalized2}
4068
+ return `${normalized}
4446
4069
  ${lines_to_add.join("\n")}
4447
4070
  `;
4448
4071
  }
@@ -4455,8 +4078,8 @@ var init_logic_sonar = {
4455
4078
  };
4456
4079
 
4457
4080
  // scripts/init/init-logic-templates.ts
4458
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
4459
- import path8 from "node:path";
4081
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
4082
+ import path6 from "node:path";
4460
4083
  var ESLINT_VANILLA = `import { create_vanilla_config } from '@joshuafolkken/kit/eslint/vanilla'
4461
4084
 
4462
4085
  export default create_vanilla_config({
@@ -4541,7 +4164,7 @@ function merge_eslint_config(existing) {
4541
4164
  return apply_rules_template(rules_blocks.join(",\n"));
4542
4165
  }
4543
4166
  function detect_tailwind_stylesheet(project_root2) {
4544
- return existsSync5(path8.join(project_root2, DEFAULT_TAILWIND_STYLESHEET)) ? DEFAULT_TAILWIND_STYLESHEET : void 0;
4167
+ return existsSync5(path6.join(project_root2, DEFAULT_TAILWIND_STYLESHEET)) ? DEFAULT_TAILWIND_STYLESHEET : void 0;
4545
4168
  }
4546
4169
  function generate_prettier_config(stylesheet) {
4547
4170
  const stylesheet_line = stylesheet === void 0 ? "" : ` tailwindStylesheet: '${stylesheet}',
@@ -4555,14 +4178,14 @@ ${stylesheet_line}}
4555
4178
  }
4556
4179
  function existing_stylesheet(existing, project_root2) {
4557
4180
  const named = TAILWIND_STYLESHEET_PATTERN.exec(existing)?.[1];
4558
- return named !== void 0 && existsSync5(path8.resolve(project_root2, named)) ? named : void 0;
4181
+ return named !== void 0 && existsSync5(path6.resolve(project_root2, named)) ? named : void 0;
4559
4182
  }
4560
4183
  function merge_prettier_config(existing, project_root2) {
4561
4184
  const stylesheet = existing_stylesheet(existing, project_root2) ?? detect_tailwind_stylesheet(project_root2);
4562
4185
  return generate_prettier_config(stylesheet);
4563
4186
  }
4564
4187
  function generate_playwright_config() {
4565
- return readFileSync4(PLAYWRIGHT_TEMPLATE_PATH, "utf8");
4188
+ return readFileSync3(PLAYWRIGHT_TEMPLATE_PATH, "utf8");
4566
4189
  }
4567
4190
  var init_logic_templates = {
4568
4191
  generate_eslint_config,
@@ -4634,9 +4257,9 @@ function extract_yaml_block(content, key) {
4634
4257
  }
4635
4258
  function append_user_blocks(base, user_keys, existing) {
4636
4259
  const user_blocks = user_keys.map((k) => extract_yaml_block(existing, k)).filter(Boolean).join("\n");
4637
- const normalized2 = base.endsWith("\n") ? base : `${base}
4260
+ const normalized = base.endsWith("\n") ? base : `${base}
4638
4261
  `;
4639
- return `${normalized2}
4262
+ return `${normalized}
4640
4263
  ${user_blocks}
4641
4264
  `;
4642
4265
  }
@@ -4729,9 +4352,9 @@ function carry_npmrc_settings(template, npmrc) {
4729
4352
  }
4730
4353
  function merge_workspace_yaml(existing, template) {
4731
4354
  if (!existing.trim()) return template;
4732
- const normalized2 = existing.endsWith("\n") ? existing : `${existing}
4355
+ const normalized = existing.endsWith("\n") ? existing : `${existing}
4733
4356
  `;
4734
- const answered = answer_build_placeholders(remove_deprecated_yaml_keys(normalized2), template);
4357
+ const answered = answer_build_placeholders(remove_deprecated_yaml_keys(normalized), template);
4735
4358
  const cleaned = add_missing_build_entries(answered, template);
4736
4359
  if (!cleaned.trim()) return template;
4737
4360
  const existing_keys = new Set(extract_yaml_top_level_keys(cleaned));
@@ -5070,23 +4693,23 @@ var AI_COPY_DIRECTORIES_LIST = "AI_COPY_DIRECTORIES";
5070
4693
  var SYNCED_PATHS_LIST = "SYNCED_PATHS";
5071
4694
  var PLUGIN_SKILL_DIRECTORIES_LIST = "PLUGIN_SKILL_DIRECTORIES";
5072
4695
  var PATH_SEPARATOR = "/";
5073
- function is_inside(path16, directory) {
5074
- return path16 === directory || path16.startsWith(`${directory}${PATH_SEPARATOR}`);
4696
+ function is_inside(path13, directory) {
4697
+ return path13 === directory || path13.startsWith(`${directory}${PATH_SEPARATOR}`);
5075
4698
  }
5076
- function matches_file(path16) {
5077
- return init_logic.get_ai_copy_files().includes(path16);
4699
+ function matches_file(path13) {
4700
+ return init_logic.get_ai_copy_files().includes(path13);
5078
4701
  }
5079
- function matches_mapping(path16) {
5080
- return init_logic.get_ai_copy_file_mappings().some((mapping) => mapping.src === path16 || mapping.dest === path16);
4702
+ function matches_mapping(path13) {
4703
+ return init_logic.get_ai_copy_file_mappings().some((mapping) => mapping.src === path13 || mapping.dest === path13);
5081
4704
  }
5082
- function matches_directory(path16) {
5083
- return init_logic.get_ai_copy_directories().some((directory) => is_inside(path16, directory));
4705
+ function matches_directory(path13) {
4706
+ return init_logic.get_ai_copy_directories().some((directory) => is_inside(path13, directory));
5084
4707
  }
5085
- function matches_plugin_skill(path16) {
5086
- return PLUGIN_SKILL_DIRECTORIES.some((directory) => is_inside(path16, directory));
4708
+ function matches_plugin_skill(path13) {
4709
+ return PLUGIN_SKILL_DIRECTORIES.some((directory) => is_inside(path13, directory));
5087
4710
  }
5088
- function matches_synced_path(path16) {
5089
- return synced_paths.get_synced_paths().includes(path16);
4711
+ function matches_synced_path(path13) {
4712
+ return synced_paths.get_synced_paths().includes(path13);
5090
4713
  }
5091
4714
  var MATCHERS = [
5092
4715
  { list: AI_COPY_FILES_LIST, matches: matches_file },
@@ -5095,18 +4718,18 @@ var MATCHERS = [
5095
4718
  { list: PLUGIN_SKILL_DIRECTORIES_LIST, matches: matches_plugin_skill },
5096
4719
  { list: SYNCED_PATHS_LIST, matches: matches_synced_path }
5097
4720
  ];
5098
- function find_list(path16) {
5099
- return MATCHERS.find((matcher) => matcher.matches(path16))?.list;
4721
+ function find_list(path13) {
4722
+ return MATCHERS.find((matcher) => matcher.matches(path13))?.list;
5100
4723
  }
5101
- function to_hit(path16) {
5102
- const list = find_list(path16);
5103
- return list === void 0 ? void 0 : { path: path16, list };
4724
+ function to_hit(path13) {
4725
+ const list = find_list(path13);
4726
+ return list === void 0 ? void 0 : { path: path13, list };
5104
4727
  }
5105
4728
  function find_managed_paths(paths) {
5106
- return paths.flatMap((path16) => to_hit(path16) ?? []);
4729
+ return paths.flatMap((path13) => to_hit(path13) ?? []);
5107
4730
  }
5108
4731
  function has_managed_path(paths) {
5109
- return paths.some((path16) => find_list(path16) !== void 0);
4732
+ return paths.some((path13) => find_list(path13) !== void 0);
5110
4733
  }
5111
4734
  function format_hit(hit) {
5112
4735
  return `${hit.path} (${hit.list})`;
@@ -5122,8 +4745,8 @@ var managed_config_scope = {
5122
4745
  };
5123
4746
 
5124
4747
  // scripts/init/session-language-environment.ts
5125
- import path9 from "node:path";
5126
- var CLAUDE_SETTINGS_PATH = path9.join(".claude", "settings.json");
4748
+ import path7 from "node:path";
4749
+ var CLAUDE_SETTINGS_PATH = path7.join(".claude", "settings.json");
5127
4750
  var KEY_LINE_PATTERN = new RegExp(
5128
4751
  String.raw`^\s*(?:export\s+)?${session_language.ENV_KEY}\s*=`,
5129
4752
  "mu"
@@ -5133,8 +4756,8 @@ var KEY_LINE_PATTERN = new RegExp(
5133
4756
  import { findNodeAtLocation as findNodeAtLocation2, getNodeValue as getNodeValue2, parseTree as parseTree3 } from "jsonc-parser";
5134
4757
 
5135
4758
  // scripts/init/vscode-tasks.ts
5136
- import { z as z12 } from "zod";
5137
- var task_array_schema = z12.array(json_object_schema);
4759
+ import { z as z11 } from "zod";
4760
+ var task_array_schema = z11.array(json_object_schema);
5138
4761
 
5139
4762
  // scripts/init/init-actions.ts
5140
4763
  var BASIC_PRETTIER_CONFIG = "prettier.config.mjs";
@@ -5144,11 +4767,11 @@ var MARKER_TOKEN = "josh-managed-workflow:";
5144
4767
  var MARKER_PREFIX = `# ${MARKER_TOKEN}`;
5145
4768
 
5146
4769
  // scripts/init/claude-plugin-config.ts
5147
- import path10 from "node:path";
4770
+ import path8 from "node:path";
5148
4771
  var MARKETPLACE_NAME = "kit";
5149
4772
  var PLUGIN_ID2 = "kit@kit";
5150
4773
  var MARKETPLACE_PATH = "./node_modules/@joshuafolkken/kit";
5151
- var CLAUDE_SETTINGS_DESTINATION = path10.join(".claude", "settings.json");
4774
+ var CLAUDE_SETTINGS_DESTINATION = path8.join(".claude", "settings.json");
5152
4775
  var PLUGIN_SETTINGS = {
5153
4776
  extraKnownMarketplaces: {
5154
4777
  [MARKETPLACE_NAME]: { source: { source: "directory", path: MARKETPLACE_PATH } }
@@ -5172,9 +4795,9 @@ var claude_plugin_config = {
5172
4795
  // scripts/init/hook-launch.ts
5173
4796
  var HOOK_DIST_DIR = "dist/hooks";
5174
4797
  var RUN_HOOK_SCRIPT = "scripts/hooks/run-hook.sh";
5175
- var UNSET_OPTIONS = GIT_LOCATION_VARIABLES.map((name) => `-u ${name}`).join(" ");
4798
+ var LOCATION_VARIABLES = GIT_LOCATION_VARIABLES.join(" ");
5176
4799
  var ROOT_LOOKUP = "git rev-parse --show-toplevel";
5177
- var PROJECT_ROOT_PREFIX = `project_root="$(env ${UNSET_OPTIONS} ${ROOT_LOOKUP} 2>/dev/null || ${ROOT_LOOKUP})" && cd "$project_root" && `;
4800
+ var PROJECT_ROOT_PREFIX = `if project_root="$(unset ${LOCATION_VARIABLES}; ${ROOT_LOOKUP} 2>/dev/null)"; then unset ${LOCATION_VARIABLES}; else project_root="$(${ROOT_LOOKUP})"; fi && cd "$project_root" && `;
5178
4801
  function hook_launch_command(name) {
5179
4802
  return `${PROJECT_ROOT_PREFIX}sh ${RUN_HOOK_SCRIPT} ${name}`;
5180
4803
  }
@@ -5210,7 +4833,7 @@ var PLAIN_KIT_COMMANDS = new Set(
5210
4833
  var PROJECT_ROOT_PREFIX2 = JSON.stringify(hook_launch.PROJECT_ROOT_PREFIX).slice(1, -1);
5211
4834
 
5212
4835
  // scripts/init/security-policy.ts
5213
- import path11 from "node:path";
4836
+ import path9 from "node:path";
5214
4837
  var SECURITY_MD = "SECURITY.md";
5215
4838
  var SECURITY_TEMPLATE = "templates/SECURITY.md";
5216
4839
  var REPOSITORY_PLACEHOLDER = "{{REPOSITORY}}";
@@ -5226,15 +4849,15 @@ function apply_repository(content, repository) {
5226
4849
  return content.replaceAll(REPOSITORY_PLACEHOLDER, () => repository);
5227
4850
  }
5228
4851
  function apply_repository_for_destination(destination_path, content) {
5229
- if (path11.basename(destination_path) !== SECURITY_MD) return content;
5230
- const repository = repository_at(path11.dirname(destination_path));
4852
+ if (path9.basename(destination_path) !== SECURITY_MD) return content;
4853
+ const repository = repository_at(path9.dirname(destination_path));
5231
4854
  if (repository === void 0) {
5232
4855
  throw new Error(`${SECURITY_MD} needs a GitHub origin to name the repository it reports to`);
5233
4856
  }
5234
4857
  return apply_repository(content, repository);
5235
4858
  }
5236
4859
  function does_report_to_kit(root) {
5237
- const existing = file_reader.read_if_readable(path11.join(root, SECURITY_MD)) ?? "";
4860
+ const existing = file_reader.read_if_readable(path9.join(root, SECURITY_MD)) ?? "";
5238
4861
  return new Set(existing.split(LINK_DELIMITER)).has(KIT_ADVISORY_URL);
5239
4862
  }
5240
4863
  function unresolved_warning(root) {
@@ -5354,9 +4977,9 @@ function requested_profile(args, usage = "josh init [--profile basic|full]") {
5354
4977
  throw new Error("Profile must be basic or full");
5355
4978
  }
5356
4979
  function read_manifest(root) {
5357
- const filename = path12.join(root, "package.json");
4980
+ const filename = path10.join(root, "package.json");
5358
4981
  if (!existsSync6(filename)) return void 0;
5359
- const parsed = JSON.parse(readFileSync5(filename, "utf8"));
4982
+ const parsed = JSON.parse(readFileSync4(filename, "utf8"));
5360
4983
  if (!json_value.is_record(parsed)) throw new Error("package.json must contain an object");
5361
4984
  return parsed;
5362
4985
  }
@@ -5397,12 +5020,12 @@ function should_visit(entry) {
5397
5020
  function scan_directory(directory, pending) {
5398
5021
  const entries = readdirSync(directory, { withFileTypes: true });
5399
5022
  pending.push(
5400
- ...entries.filter((entry) => should_visit(entry)).map((entry) => path12.join(directory, entry.name))
5023
+ ...entries.filter((entry) => should_visit(entry)).map((entry) => path10.join(directory, entry.name))
5401
5024
  );
5402
- return entries.filter((entry) => entry.isFile()).map((entry) => path12.join(directory, entry.name));
5025
+ return entries.filter((entry) => entry.isFile()).map((entry) => path10.join(directory, entry.name));
5403
5026
  }
5404
5027
  function is_project_file(root, file) {
5405
- const relative_path = path12.relative(root, file).split(path12.sep).join(path12.posix.sep);
5028
+ const relative_path = path10.relative(root, file).split(path10.sep).join(path10.posix.sep);
5406
5029
  return !kit_written_paths.is_kit_written(relative_path);
5407
5030
  }
5408
5031
  function list_files(root) {
@@ -5414,7 +5037,7 @@ function list_files(root) {
5414
5037
  return files;
5415
5038
  }
5416
5039
  function file_extensions(root) {
5417
- return list_files(root).filter((file) => is_project_file(root, file)).map((file) => path12.extname(file));
5040
+ return list_files(root).filter((file) => is_project_file(root, file)).map((file) => path10.extname(file));
5418
5041
  }
5419
5042
  function scan_files(root) {
5420
5043
  const extensions = file_extensions(root);
@@ -5428,7 +5051,7 @@ function has_github_remote(root) {
5428
5051
  return url !== void 0 && repo_origin.parse_origin_url(url) !== void 0;
5429
5052
  }
5430
5053
  function has_git(root) {
5431
- return existsSync6(path12.join(root, ".git"));
5054
+ return existsSync6(path10.join(root, ".git"));
5432
5055
  }
5433
5056
  function has_workflow(root) {
5434
5057
  return workflow_mark.is_requested() || workflow_mark.is_recorded(read_manifest(root));
@@ -5461,7 +5084,7 @@ var SKIP_MARKER = "\u2014 skipping";
5461
5084
  var NOT_USED_MARKER = "which this project does not use";
5462
5085
 
5463
5086
  // scripts/gate/project-checks.ts
5464
- import { z as z13 } from "zod";
5087
+ import { z as z12 } from "zod";
5465
5088
  var PACKAGE_JSON2 = "package.json";
5466
5089
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
5467
5090
  "node_modules",
@@ -5496,11 +5119,11 @@ var CSPELL_CONFIGS = [
5496
5119
  ];
5497
5120
  var TYPE_CONFIGS = ["tsconfig.json", "jsconfig.json"];
5498
5121
  var DEPENDENCY_FIELDS = ["dependencies", "devDependencies"];
5499
- var object_schema = z13.record(z13.string(), z13.unknown());
5122
+ var object_schema = z12.record(z12.string(), z12.unknown());
5500
5123
  function project_root(directory) {
5501
5124
  return ancestor_directories.nearest(
5502
5125
  directory,
5503
- (candidate) => existsSync7(path13.join(candidate, PACKAGE_JSON2))
5126
+ (candidate) => existsSync7(path11.join(candidate, PACKAGE_JSON2))
5504
5127
  ) ?? directory;
5505
5128
  }
5506
5129
  function is_kit_repository(directory) {
@@ -5510,10 +5133,10 @@ function is_ignored(entry) {
5510
5133
  return entry.split(/[/\\]/u).some((part) => IGNORED_DIRS.has(part));
5511
5134
  }
5512
5135
  function has_files(directory, pattern) {
5513
- return globSync2(pattern, { cwd: project_root(directory), exclude: is_ignored }).length > 0;
5136
+ return globSync(pattern, { cwd: project_root(directory), exclude: is_ignored }).length > 0;
5514
5137
  }
5515
5138
  function has_config(directory, names) {
5516
- return names.some((name) => existsSync7(path13.join(project_root(directory), name)));
5139
+ return names.some((name) => existsSync7(path11.join(project_root(directory), name)));
5517
5140
  }
5518
5141
  function has_bin(directory, bin_name) {
5519
5142
  return find_local_bin_upwards(directory, bin_name) !== void 0;
@@ -5528,8 +5151,8 @@ function is_basic_manifest(manifest) {
5528
5151
  }
5529
5152
  function read_manifest2(directory) {
5530
5153
  try {
5531
- const manifest_path = path13.join(project_root(directory), PACKAGE_JSON2);
5532
- return json_value.parse_or_undefined(readFileSync6(manifest_path, "utf8"));
5154
+ const manifest_path = path11.join(project_root(directory), PACKAGE_JSON2);
5155
+ return json_value.parse_or_undefined(readFileSync5(manifest_path, "utf8"));
5533
5156
  } catch {
5534
5157
  return void 0;
5535
5158
  }
@@ -5549,11 +5172,11 @@ function missing_tool_reason(directory, package_name, bin_name) {
5549
5172
  if (has_bin(directory, bin_name) || is_declared2(directory, package_name)) return void 0;
5550
5173
  return `${package_name} is not installed`;
5551
5174
  }
5552
- function skip_notice(check5, reason) {
5553
- return `josh ${check5}: ${reason} ${SKIP_MARKER} ${check5}, ${NOT_USED_MARKER}.`;
5175
+ function skip_notice(check3, reason) {
5176
+ return `josh ${check3}: ${reason} ${SKIP_MARKER} ${check3}, ${NOT_USED_MARKER}.`;
5554
5177
  }
5555
- function ran_notice(check5) {
5556
- return `josh ${check5}: ran and reported no problems.`;
5178
+ function ran_notice(check3) {
5179
+ return `josh ${check3}: ran and reported no problems.`;
5557
5180
  }
5558
5181
  function type_check_skip_reason(directory) {
5559
5182
  if (!is_basic(directory)) return void 0;
@@ -5617,40 +5240,40 @@ var BASIC_REQUIRED_SUFFIXES = [
5617
5240
  ".toml"
5618
5241
  ];
5619
5242
  function normalize(paths) {
5620
- return paths.map((path16) => path16.trim()).filter((path16) => path16 !== "");
5243
+ return paths.map((path13) => path13.trim()).filter((path13) => path13 !== "");
5621
5244
  }
5622
- function is_test_file(path16) {
5623
- return TEST_SUFFIXES.some((suffix) => path16.endsWith(suffix));
5245
+ function is_test_file(path13) {
5246
+ return TEST_SUFFIXES.some((suffix) => path13.endsWith(suffix));
5624
5247
  }
5625
- function is_basic_manual(path16, is_basic2) {
5626
- const lower = path16.toLowerCase();
5248
+ function is_basic_manual(path13, is_basic2) {
5249
+ const lower = path13.toLowerCase();
5627
5250
  return is_basic2 && BASIC_REQUIRED_SUFFIXES.every((suffix) => !lower.endsWith(suffix));
5628
5251
  }
5629
- function is_basic_visual(path16) {
5630
- const lower = path16.toLowerCase();
5252
+ function is_basic_visual(path13) {
5253
+ const lower = path13.toLowerCase();
5631
5254
  return BASIC_VISUAL_SUFFIXES.some((suffix) => lower.endsWith(suffix));
5632
5255
  }
5633
- function is_exempt(path16, is_basic2 = false) {
5634
- if (EXEMPT_PATHS.includes(path16) || is_basic_manual(path16, is_basic2)) return true;
5635
- return EXEMPT_SUFFIXES.some((suffix) => path16.endsWith(suffix)) || EXEMPT_PREFIXES.some((prefix) => path16.startsWith(prefix));
5256
+ function is_exempt(path13, is_basic2 = false) {
5257
+ if (EXEMPT_PATHS.includes(path13) || is_basic_manual(path13, is_basic2)) return true;
5258
+ return EXEMPT_SUFFIXES.some((suffix) => path13.endsWith(suffix)) || EXEMPT_PREFIXES.some((prefix) => path13.startsWith(prefix));
5636
5259
  }
5637
- function is_runtime(path16, is_basic2 = false) {
5638
- return !is_test_file(path16) && !is_exempt(path16, is_basic2);
5260
+ function is_runtime(path13, is_basic2 = false) {
5261
+ return !is_test_file(path13) && !is_exempt(path13, is_basic2);
5639
5262
  }
5640
5263
  function verdict_for2(paths, is_basic2 = false) {
5641
5264
  const changed = normalize(paths);
5642
- if (changed.some((path16) => is_test_file(path16))) return "satisfied";
5643
- if (changed.some((path16) => is_runtime(path16, is_basic2))) return "required";
5265
+ if (changed.some((path13) => is_test_file(path13))) return "satisfied";
5266
+ if (changed.some((path13) => is_runtime(path13, is_basic2))) return "required";
5644
5267
  return "exempt";
5645
5268
  }
5646
5269
  function runtime_files(paths, is_basic2 = false) {
5647
- return normalize(paths).filter((path16) => is_runtime(path16, is_basic2));
5270
+ return normalize(paths).filter((path13) => is_runtime(path13, is_basic2));
5648
5271
  }
5649
5272
  function exempt_files(paths, is_basic2 = false) {
5650
- return normalize(paths).filter((path16) => is_exempt(path16, is_basic2));
5273
+ return normalize(paths).filter((path13) => is_exempt(path13, is_basic2));
5651
5274
  }
5652
5275
  function manual_check_files(paths, is_basic2) {
5653
- return normalize(paths).filter((path16) => is_exempt(path16, is_basic2) && !is_exempt(path16));
5276
+ return normalize(paths).filter((path13) => is_exempt(path13, is_basic2) && !is_exempt(path13));
5654
5277
  }
5655
5278
  var test_declared_logic = {
5656
5279
  EXEMPT_PATHS,
@@ -5709,9 +5332,9 @@ function is_untested_commit(call) {
5709
5332
  if (!run_tail_rule.is_push_step_call(call)) return false;
5710
5333
  return test_declared_changed.current_verdict() === "required";
5711
5334
  }
5712
- 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.';
5713
- var ROW19 = { id: "test-declared", is_trigger: is_untested_commit, reason: REASON };
5714
- var test_declared_commit = { REASON, ROW: ROW19 };
5335
+ var REASON = "\u26D4 test declared: this commits a runtime code change with no test beside it \u2014 `pnpm josh test:declared` reads it `required` and names the files. Tests are required for ALL code changes: add the test and reissue, or, for a change that touches no runtime path, declare the non-runtime exception in your Step 0 work summary and reissue. It fires once per run (`CLAUDE.md` \u2192 Code Change Rules).";
5336
+ var ROW20 = { id: "test-declared", is_trigger: is_untested_commit, reason: REASON };
5337
+ var test_declared_commit = { REASON, ROW: ROW20 };
5715
5338
 
5716
5339
  // scripts/rules/third-party-write.ts
5717
5340
  function is_third_party_write_segment(segment, session_owner, resolve_placeholder_owners) {
@@ -5748,15 +5371,15 @@ function writes_third_party(command, resolve_owner = repo_party.current_owner, r
5748
5371
  )
5749
5372
  );
5750
5373
  }
5751
- 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.**';
5752
- var ROW20 = {
5374
+ var THIRD_PARTY_WRITE_REASON = "\u26D4 third-party write: this `gh api` call writes to a repository whose owner is not this session, which is Tier C and needs explicit current-turn user instruction. Check with `pnpm josh repo:party <owner/repo>`, then record the finding on our own Issue and draft the artifact for a person to post. This rule fires on every occurrence (`prompts/collaboration-workflow/upstream-interrupt.md`).";
5375
+ var ROW21 = {
5753
5376
  id: "third-party-write",
5754
5377
  is_trigger: bash_triggers.on_bash_command((command) => writes_third_party(command)),
5755
5378
  reason: THIRD_PARTY_WRITE_REASON,
5756
5379
  decide: () => true
5757
5380
  };
5758
5381
  var third_party_write = {
5759
- ROW: ROW20,
5382
+ ROW: ROW21,
5760
5383
  THIRD_PARTY_WRITE_REASON,
5761
5384
  writes_third_party
5762
5385
  };
@@ -5788,7 +5411,8 @@ var DELIVERED_RULES = [
5788
5411
  reason: piped_verification.PIPED_VERIFICATION_REASON,
5789
5412
  keeps: on_bash_command(piped_verification.keeps_verdict_intact),
5790
5413
  reaches: on_bash_command(piped_verification.runs_verification),
5791
- // A `| tail` / `| grep` masking is rewritten under `pipefail` by `pretool-guard.ts`, never refused.
5414
+ // A masking whose filter reads to the end is rewritten under `pipefail`, never refused.
5415
+ rewrite: piped_verification.pipefail_input,
5792
5416
  rewrite_note: piped_verification.PIPEFAIL_NOTE
5793
5417
  },
5794
5418
  {
@@ -5827,6 +5451,7 @@ var DELIVERED_RULES = [
5827
5451
  lane_split_park.ROW,
5828
5452
  lane_park.ROW,
5829
5453
  lane_interactive_ask.ROW,
5454
+ agent_model.ROW,
5830
5455
  lane_carry_conflict.ROW,
5831
5456
  lane_switch_main.ROW,
5832
5457
  // Disjoint from `run-tail` by the `-y` flag.
@@ -5866,22 +5491,22 @@ var MEASURED_RULES = [
5866
5491
  ];
5867
5492
  var BATCH_STAMP = hook_decision.create_refusal_stamp(time_batch_guard.STAMP_PREFIX);
5868
5493
  var BATCH_REFUSAL_WINDOW_MS = 1e4;
5869
- function will_batch_guard_refuse(tail, call, run2) {
5494
+ function will_batch_guard_refuse(tail, call, run) {
5870
5495
  if (lane_guard_policy.mode_here("batching") !== "refuse") return false;
5871
- const refused_at_ms = BATCH_STAMP.last_ms(BATCH_STAMP.path(run2.transcript));
5872
- if (run2.now_ms - refused_at_ms < BATCH_REFUSAL_WINDOW_MS) return true;
5496
+ const refused_at_ms = BATCH_STAMP.last_ms(BATCH_STAMP.path(run.transcript));
5497
+ if (run.now_ms - refused_at_ms < BATCH_REFUSAL_WINDOW_MS) return true;
5873
5498
  return time_batch_guard.should_block(tail, call, refused_at_ms);
5874
5499
  }
5875
- function is_first_delivery(tail, call, delivered_at_ms, run2) {
5876
- return delivered_at_ms === hook_decision.NEVER_MS && !will_batch_guard_refuse(tail, call, run2);
5500
+ function is_first_delivery(tail, call, delivered_at_ms, run) {
5501
+ return delivered_at_ms === hook_decision.NEVER_MS && !will_batch_guard_refuse(tail, call, run);
5877
5502
  }
5878
5503
  function delivery_decision(rule) {
5879
- const { decide: decide7, already_satisfied } = rule;
5880
- if (decide7 === void 0 && already_satisfied === void 0) return is_first_delivery;
5881
- return function should_block(tail, call, delivered_at_ms, run2) {
5504
+ const { decide: decide5, already_satisfied } = rule;
5505
+ if (decide5 === void 0 && already_satisfied === void 0) return is_first_delivery;
5506
+ return function should_block(tail, call, delivered_at_ms, run) {
5882
5507
  if (already_satisfied?.(tail, call) === true) return false;
5883
- if (decide7 === void 0) return true;
5884
- return decide7(call, run2, !will_batch_guard_refuse(tail, call, run2), delivered_at_ms);
5508
+ if (decide5 === void 0) return true;
5509
+ return decide5(call, run, !will_batch_guard_refuse(tail, call, run), delivered_at_ms);
5885
5510
  };
5886
5511
  }
5887
5512
  function guard_of(rule) {
@@ -5934,6 +5559,144 @@ var delivered_rules = {
5934
5559
  is_issue_filing
5935
5560
  };
5936
5561
 
5562
+ // scripts/rules/guard-remedy.ts
5563
+ var REWRITTEN = "the hook makes the same call right itself; it refuses only a spelling it cannot rewrite";
5564
+ var ANOTHER_TOOL = "the right form is another tool, which `updatedInput` cannot switch to";
5565
+ var ANOTHER_COMMAND = "the right act is another command, whose arguments the call does not carry";
5566
+ var NO_RUN = "the call must not run at all, or only on a person's word \u2014 refusing is the remedy";
5567
+ var STOP_NOTIFY = "the hook sends the notify itself; it blocks only when the send fails";
5568
+ var HOLD_RELEASE = "the hook releases the hold itself; it blocks only when that release fails";
5569
+ var ISSUE_CITATION = "the hook prints the citation lines itself; it blocks only on a number it cannot cite";
5570
+ var FILING_OFFER = "the filing needs a title and a body only the model can write";
5571
+ function rewrite() {
5572
+ return { remedy: "rewrite", why: REWRITTEN };
5573
+ }
5574
+ function judgment(why) {
5575
+ return { remedy: "judgment", why };
5576
+ }
5577
+ var GUARD_REMEDIES = {
5578
+ // The rewrites: each fix is the same call made right.
5579
+ "piped-verification": rewrite(),
5580
+ // wrapped in `(set -o pipefail; …)`
5581
+ "issue-comments": rewrite(),
5582
+ // the same Issue's comments read on the same line
5583
+ "lane-background": rewrite(),
5584
+ // a lane child's `ship` run in the foreground (`pretool-guard.ts`)
5585
+ "file-body": judgment(ANOTHER_TOOL),
5586
+ // Edit or Write
5587
+ "shell-body": judgment(
5588
+ "which part of the body the shell was meant to expand is the author's, not the hook's"
5589
+ ),
5590
+ "raw-field-body": judgment("the body moves to a file the call does not name"),
5591
+ "rule-body": judgment("a trigger-plus-pointer line is written, not derived"),
5592
+ "direct-filing": judgment(ANOTHER_COMMAND),
5593
+ "direct-pr-create": judgment(ANOTHER_COMMAND),
5594
+ "josh-git-bare": judgment(ANOTHER_COMMAND),
5595
+ "run-tail": judgment(ANOTHER_COMMAND),
5596
+ "pre-gate-cut": judgment(ANOTHER_COMMAND),
5597
+ "implementation-cut": judgment(ANOTHER_COMMAND),
5598
+ "early-heartbeat": judgment(ANOTHER_COMMAND),
5599
+ "poll-loop": judgment(ANOTHER_COMMAND),
5600
+ "lane-park": judgment(ANOTHER_COMMAND),
5601
+ "lane-split-park": judgment(ANOTHER_COMMAND),
5602
+ "lane-interactive-ask": judgment(ANOTHER_COMMAND),
5603
+ "agent-model": judgment("which model fits the unit is the author's call"),
5604
+ "lane-carry-conflict": judgment(ANOTHER_COMMAND),
5605
+ "lane-switch-main": judgment(ANOTHER_COMMAND),
5606
+ "oracle-consulted": judgment(ANOTHER_COMMAND),
5607
+ "test-declared": judgment(ANOTHER_COMMAND),
5608
+ batching: judgment(ANOTHER_COMMAND),
5609
+ investigation: judgment(ANOTHER_COMMAND),
5610
+ "duplicate-read": judgment(ANOTHER_COMMAND),
5611
+ "run-watcher": judgment(ANOTHER_COMMAND),
5612
+ "parent-cut": judgment(ANOTHER_COMMAND),
5613
+ "issue-watch": judgment(ANOTHER_COMMAND),
5614
+ "third-party-write": judgment(NO_RUN),
5615
+ "filing-cap": judgment(NO_RUN),
5616
+ "git-force": judgment(NO_RUN),
5617
+ "worktree-mutation": judgment(NO_RUN),
5618
+ "index-mutation": judgment(NO_RUN),
5619
+ "destructive-command": judgment(NO_RUN),
5620
+ "protected-file": judgment(NO_RUN),
5621
+ // The `Stop` rows: the hook judges a finished reply, so it has no call to rewrite.
5622
+ "stop-notify": judgment(STOP_NOTIFY),
5623
+ "issue-citation": judgment(ISSUE_CITATION),
5624
+ "filing-offer": judgment(FILING_OFFER),
5625
+ "hold-release": judgment(HOLD_RELEASE)
5626
+ };
5627
+ var FAMILY_SEPARATOR = ":";
5628
+ function entry_of(id) {
5629
+ return GUARD_REMEDIES[id] ?? GUARD_REMEDIES[id.split(FAMILY_SEPARATOR)[0] ?? ""];
5630
+ }
5631
+ function remedy_of(id) {
5632
+ return entry_of(id)?.remedy;
5633
+ }
5634
+ function why_of(id) {
5635
+ return entry_of(id)?.why;
5636
+ }
5637
+ function is_rewrite(id) {
5638
+ return remedy_of(id) === "rewrite";
5639
+ }
5640
+ var guard_remedy = {
5641
+ GUARD_REMEDIES,
5642
+ is_rewrite,
5643
+ remedy_of,
5644
+ why_of
5645
+ };
5646
+
5647
+ // scripts/rules/rule-rewrite.ts
5648
+ var REWRITING_RULES = delivered_rules.DELIVERED_RULES.filter(
5649
+ (rule) => guard_remedy.is_rewrite(rule.id) && rule.rewrite !== void 0
5650
+ );
5651
+ var NOTE_SEPARATOR = "\n\n";
5652
+ function read_payload_tail(raw_payload) {
5653
+ try {
5654
+ const payload = hook_decision.parse_hook_payload(raw_payload);
5655
+ return payload ? time_density_hook.read_tail(
5656
+ time_hook_transcript.transcript_of(payload.transcript_path, payload.agent_id)
5657
+ ) : "";
5658
+ } catch {
5659
+ return "";
5660
+ }
5661
+ }
5662
+ function tail_reader(raw_payload) {
5663
+ const cache = {};
5664
+ return function tail() {
5665
+ cache.tail ??= read_payload_tail(raw_payload);
5666
+ return cache.tail;
5667
+ };
5668
+ }
5669
+ function fold(name, typed, tail) {
5670
+ const folded = { input: typed, notes: [] };
5671
+ for (const rule of REWRITING_RULES) {
5672
+ const input = rule.rewrite?.({ name, input: folded.input }, tail);
5673
+ if (input === void 0) {
5674
+ continue;
5675
+ }
5676
+ folded.input = input;
5677
+ folded.notes = [...folded.notes, rule.rewrite_note ?? ""];
5678
+ }
5679
+ return folded;
5680
+ }
5681
+ function typed_payload(raw_payload) {
5682
+ const fields = json_value.parse_or_undefined(raw_payload);
5683
+ if (!json_value.is_record(fields)) return void 0;
5684
+ const input = fields["tool_input"];
5685
+ return json_value.is_record(input) ? { fields, name: String(fields["tool_name"]), input } : void 0;
5686
+ }
5687
+ function rewrite2(raw_payload) {
5688
+ const typed = delivered_rules.is_enabled() ? typed_payload(raw_payload) : void 0;
5689
+ if (typed === void 0) return void 0;
5690
+ const { input, notes } = fold(typed.name, typed.input, tail_reader(raw_payload));
5691
+ if (notes.length === 0) return void 0;
5692
+ const note = notes.join(NOTE_SEPARATOR);
5693
+ return { payload: JSON.stringify({ ...typed.fields, tool_input: input }), input, note };
5694
+ }
5695
+ var rule_rewrite = {
5696
+ NOTE_SEPARATOR,
5697
+ rewrite: rewrite2
5698
+ };
5699
+
5937
5700
  // scripts/run/progress/run-progress-read.ts
5938
5701
  import { loadavg } from "node:os";
5939
5702
 
@@ -6004,411 +5767,16 @@ var epic_busy = {
6004
5767
  read_repository
6005
5768
  };
6006
5769
 
6007
- // scripts/run/run-liveness.ts
6008
- import { readFileSync as readFileSync7, realpathSync, statSync } from "node:fs";
6009
- import { homedir, tmpdir } from "node:os";
6010
- import path14 from "node:path";
6011
- import { setTimeout as sleep } from "node:timers/promises";
6012
-
6013
- // scripts/agent/agent-event.ts
6014
- import { z as z15 } from "zod";
6015
-
6016
- // scripts/agent/claude-result-event.ts
6017
- import { z as z14 } from "zod";
6018
- var RESULT_EVENT_SCHEMA = z14.looseObject({
6019
- type: z14.literal("result"),
6020
- is_error: z14.unknown().optional(),
6021
- subtype: z14.unknown().optional(),
6022
- num_turns: z14.unknown().optional(),
6023
- permission_denials: z14.unknown().optional(),
6024
- result: z14.unknown().optional(),
6025
- session_id: z14.unknown().optional(),
6026
- usage: z14.unknown().optional()
6027
- });
6028
- var USAGE_SCHEMA = z14.looseObject({
6029
- input_tokens: z14.unknown().optional(),
6030
- cache_read_input_tokens: z14.unknown().optional(),
6031
- output_tokens: z14.unknown().optional()
6032
- });
6033
- function usable_reason(value) {
6034
- return typeof value === "string" && value.trim() !== "" ? value : void 0;
6035
- }
6036
- function numeric_value(value) {
6037
- return typeof value === "number" ? value : void 0;
6038
- }
6039
- function denial_count(value) {
6040
- return Array.isArray(value) ? value.length : 0;
6041
- }
6042
- function usage_of(value) {
6043
- const parsed = USAGE_SCHEMA.safeParse(value);
6044
- if (!parsed.success) return void 0;
6045
- const usage = {
6046
- input_tokens: numeric_value(parsed.data.input_tokens),
6047
- cached_input_tokens: numeric_value(parsed.data.cache_read_input_tokens),
6048
- output_tokens: numeric_value(parsed.data.output_tokens)
6049
- };
6050
- return Object.values(usage).some((count) => count !== void 0) ? usage : void 0;
6051
- }
6052
- function decode(value) {
6053
- const parsed = RESULT_EVENT_SCHEMA.safeParse(value);
6054
- if (!parsed.success) return void 0;
6055
- const is_error = parsed.data.is_error === true;
6056
- return {
6057
- is_error,
6058
- subtype: usable_reason(parsed.data.subtype),
6059
- num_turns: numeric_value(parsed.data.num_turns),
6060
- permission_denials: denial_count(parsed.data.permission_denials),
6061
- refused_ask: interactive_ask.refused_ask_of(parsed.data.permission_denials),
6062
- reason: is_error ? usable_reason(parsed.data.result) ?? usable_reason(parsed.data.subtype) : void 0,
6063
- session_id: usable_reason(parsed.data.session_id),
6064
- usage: usage_of(parsed.data.usage)
6065
- };
6066
- }
6067
- var claude_result_event = { decode };
6068
-
6069
- // scripts/agent/agent-event.ts
6070
- var TEXT_BLOCK_SCHEMA = z15.looseObject({ type: z15.literal("text"), text: z15.string() });
6071
- var CLAUDE_MESSAGE_SCHEMA = z15.looseObject({ content: z15.array(z15.unknown()).optional() });
6072
- var CLAUDE_INIT_SCHEMA = z15.looseObject({ type: z15.literal("system"), subtype: z15.literal("init") });
6073
- var CLAUDE_ASSISTANT_SCHEMA = z15.looseObject({
6074
- type: z15.literal("assistant"),
6075
- message: CLAUDE_MESSAGE_SCHEMA
6076
- });
6077
- var USAGE_SCHEMA2 = z15.looseObject({
6078
- input_tokens: z15.number().optional(),
6079
- cached_input_tokens: z15.number().optional(),
6080
- output_tokens: z15.number().optional()
6081
- });
6082
- var CODEX_START_SCHEMA = z15.looseObject({ type: z15.enum(["thread.started", "turn.started"]) });
6083
- var CODEX_PROGRESS_SCHEMA = z15.looseObject({
6084
- type: z15.enum(["item.started", "item.updated", "item.completed"]),
6085
- item: z15.looseObject({ type: z15.string(), text: z15.string().optional() }).optional()
6086
- });
6087
- var CODEX_COMPLETE_SCHEMA = z15.looseObject({
6088
- type: z15.literal("turn.completed"),
6089
- usage: USAGE_SCHEMA2.optional()
6090
- });
6091
- var CODEX_FAILURE_SCHEMA = z15.looseObject({
6092
- type: z15.enum(["turn.failed", "error"]),
6093
- error: z15.looseObject({ message: z15.string().optional() }).optional(),
6094
- message: z15.string().optional()
6095
- });
6096
- var EMPTY_STATE = {
6097
- provider: void 0,
6098
- launch: "pending",
6099
- progress: void 0,
6100
- result: "running",
6101
- reason: void 0,
6102
- usage: void 0
6103
- };
6104
- var LAUNCH_HEADER_PREFIX = "=== ";
6105
- var LAUNCH_HEADER_MARKER = " \xB7 started by process ";
6106
- function usable_text(value) {
6107
- return typeof value === "string" && value.trim() !== "" ? value : void 0;
6108
- }
6109
- function claude_progress(content) {
6110
- return content.map((block) => TEXT_BLOCK_SCHEMA.safeParse(block)).filter((parsed) => parsed.success).map((parsed) => parsed.data.text).findLast((text) => text.trim() !== "");
6111
- }
6112
- function claude_result(value, state) {
6113
- const result = claude_result_event.decode(value);
6114
- if (result === void 0) return void 0;
6115
- return {
6116
- ...state,
6117
- provider: "anthropic",
6118
- result: result.is_error ? "failed" : "completed",
6119
- reason: result.is_error ? result.reason ?? state.reason : state.reason,
6120
- usage: result.usage
6121
- };
6122
- }
6123
- function claude_assistant(value, state) {
6124
- const assistant = CLAUDE_ASSISTANT_SCHEMA.safeParse(value);
6125
- if (!assistant.success) return void 0;
6126
- const progress = claude_progress(assistant.data.message.content ?? []) ?? state.progress;
6127
- return { ...state, provider: "anthropic", progress };
6128
- }
6129
- function claude_terminal(value, state) {
6130
- return claude_result(value, state);
6131
- }
6132
- function claude_event(value, state) {
6133
- if (CLAUDE_INIT_SCHEMA.safeParse(value).success) {
6134
- return { ...state, provider: "anthropic", launch: "started" };
6135
- }
6136
- return claude_assistant(value, state) ?? claude_terminal(value, state);
6137
- }
6138
- function codex_progress(value, state) {
6139
- const parsed = CODEX_PROGRESS_SCHEMA.safeParse(value);
6140
- if (!parsed.success) return void 0;
6141
- const progress = usable_text(parsed.data.item?.text) ?? state.progress;
6142
- return { ...state, provider: "openai", progress };
6143
- }
6144
- function codex_completed(value, state) {
6145
- const completed = CODEX_COMPLETE_SCHEMA.safeParse(value);
6146
- if (!completed.success) return void 0;
6147
- return { ...state, provider: "openai", result: "completed", usage: completed.data.usage };
6148
- }
6149
- function codex_failed(value, state) {
6150
- const failed = CODEX_FAILURE_SCHEMA.safeParse(value);
6151
- if (!failed.success) return void 0;
6152
- const reason = usable_text(failed.data.error?.message) ?? usable_text(failed.data.message);
6153
- return { ...state, provider: "openai", result: "failed", reason: reason ?? state.reason };
6154
- }
6155
- function codex_terminal(value, state) {
6156
- return codex_completed(value, state) ?? codex_failed(value, state);
6157
- }
6158
- function codex_event(value, state) {
6159
- if (CODEX_START_SCHEMA.safeParse(value).success) {
6160
- return { ...state, provider: "openai", launch: "started" };
6161
- }
6162
- return codex_progress(value, state) ?? codex_terminal(value, state);
6163
- }
6164
- function normalized(value, state) {
6165
- return claude_event(value, state) ?? codex_event(value, state) ?? state;
6166
- }
6167
- function next_state2(line, state) {
6168
- const is_header = line.startsWith(LAUNCH_HEADER_PREFIX) && line.includes(LAUNCH_HEADER_MARKER);
6169
- if (is_header) return EMPTY_STATE;
6170
- return normalized(json_value.parse_or_undefined(line), state);
6171
- }
6172
- function read(transcript) {
6173
- let state = EMPTY_STATE;
6174
- for (const line of transcript.split("\n")) state = next_state2(line, state);
6175
- return state;
6176
- }
6177
- function describe(state) {
6178
- const progress = state.progress === void 0 ? "" : ` progress=${state.progress}`;
6179
- const reason = state.reason === void 0 ? "" : ` reason=${state.reason}`;
6180
- return `launch=${state.launch} result=${state.result}${progress}${reason}`;
6181
- }
6182
- var agent_event = { describe, read };
6183
-
6184
- // scripts/issue/issue-state.ts
6185
- import { z as z16 } from "zod";
6186
- var NO_LABELS = "(none)";
6187
- var LABEL_SEPARATOR = ", ";
6188
- var STATE_LABEL = "state: ";
6189
- var LABELS_LABEL = "labels: ";
6190
- var HUMAN_REVIEW_LABEL_LINE = "human_review: ";
6191
- var YES = "yes";
6192
- var NO = "no";
6193
- var ISSUE_LABEL = "issue: ";
6194
- var HUMAN_REVIEW_LABELS = /* @__PURE__ */ new Set([NEEDS_HUMAN_REVIEW_LABEL]);
6195
- var label_schema = z16.looseObject({ name: z16.string() });
6196
- var issue_state_schema = z16.looseObject({
6197
- state: z16.string(),
6198
- labels: label_schema.array().optional()
6199
- });
6200
- function parse_issue_state(json) {
6201
- try {
6202
- const parsed = parse_json_object_or_undefined(json, issue_state_schema);
6203
- if (parsed === void 0) return void 0;
6204
- const labels = parsed.labels ?? [];
6205
- return {
6206
- state: parsed.state,
6207
- labels: labels.map((label) => label.name),
6208
- is_human_review: has_any_label(labels, HUMAN_REVIEW_LABELS)
6209
- };
6210
- } catch {
6211
- return void 0;
6212
- }
6213
- }
6214
- function format_labels(labels) {
6215
- return labels.length === 0 ? NO_LABELS : labels.join(LABEL_SEPARATOR);
6216
- }
6217
- function format_issue_state(state) {
6218
- const human_review = `${HUMAN_REVIEW_LABEL_LINE}${state.is_human_review ? YES : NO}`;
6219
- return `${STATE_LABEL}${state.state}
6220
- ${LABELS_LABEL}${format_labels(state.labels)}
6221
- ${human_review}`;
6222
- }
6223
- function format_attributed_issue_state(issue_number, state) {
6224
- return `${ISSUE_LABEL}${issue_number}
6225
- ${format_issue_state(state)}`;
6226
- }
6227
- var issue_state = {
6228
- NO_LABELS,
6229
- parse_issue_state,
6230
- format_issue_state,
6231
- format_attributed_issue_state
6232
- };
6233
-
6234
- // scripts/run/run-liveness.ts
6235
- var ALIVE_VERDICT = "alive";
6236
- var SETTLED_VERDICT = "settled";
6237
- var STOPPED_VERDICT = "stopped";
6238
- var UNDETERMINED_VERDICT = "undetermined";
6239
- var PROCESS_ALIVE = "alive";
6240
- var PROCESS_UNKNOWN = "unknown";
6241
- var DEFAULT_SILENT_MINUTES = 30;
6242
- var DEFAULT_GAP_SECONDS = 5;
6243
- var MS_PER_MINUTE3 = 6e4;
6244
- var MS_PER_SECOND2 = 1e3;
6245
- var OPEN_STATE = "OPEN";
6246
- var STATE_FIELDS = "state,labels";
6247
- var REASONS = {
6248
- [ALIVE_VERDICT]: "The unit is still working: its output moved, or a process of the child is alive.",
6249
- [SETTLED_VERDICT]: "The child is closed, parked with `needs-decision`, or marked `already-done`, so there is nothing here to recover.",
6250
- [STOPPED_VERDICT]: "The output has been frozen past the silent window and no process of the child is alive in that checkout.",
6251
- [UNDETERMINED_VERDICT]: "A trace could not be read, so this says nothing about the unit. A trace that could not be read answers `undetermined`, never `stopped`."
6252
- };
6253
- var ALIVE_ADVICE = "Keep polling; do not touch the child.";
6254
- var SETTLED_ADVICE = "Read the child with `pnpm josh issue:state <N>` and take the branch its state says.";
6255
- var STOPPED_CLEAN_ADVICE = 'The unit ended \u2014 classify how before booking it. Run `pnpm josh run:merge <N> --output <path>` (add `--epic <E> --repo <owner/repo> --owner "$PPID"` for a named epic): it reads the child\'s exit record without waiting for the unit to return, re-dispatches a child that could not reach the API (counting the outage; a run of them trips the environment guard and stops the run), and parks an abandoned child with `needs-decision` against the consecutive-failure guard. The checkout is clean, so there is nothing to stash.';
6256
- var STOPPED_DIRTY_ADVICE = 'Stash the half-finished work with `git stash push -u -m "backlogrun: stopped unit for #<N>"` and record it on the Issue, then classify how the unit ended with `pnpm josh run:merge <N> --output <path>` (add `--epic <E> --repo <owner/repo> --owner "$PPID"` for a named epic): it re-dispatches an API-outage child (counting the outage; a run of them trips the environment guard) and parks an abandoned child with `needs-decision` against the consecutive-failure guard, without waiting for the unit to return.';
6257
- var UNDETERMINED_ADVICE = "Read the trace that failed and ask again. Where the process trace was given as `unknown`, leave `--process` out so the command reads it. Two of these in a row is a fault in the check itself rather than a slow unit: stop polling and report it.";
6258
- function is_output_moving(traces) {
6259
- return traces.is_output_frozen === false;
6260
- }
6261
- function is_agent_state_finished(state) {
6262
- return state?.result === "completed" || state?.result === "failed";
6263
- }
6264
- function is_agent_finished(traces) {
6265
- return is_agent_state_finished(traces.agent_state);
6266
- }
6267
- function definitive_verdict(traces) {
6268
- if (traces.is_child_settled === true) return SETTLED_VERDICT;
6269
- if (is_agent_finished(traces)) return STOPPED_VERDICT;
6270
- if (is_output_moving(traces)) return ALIVE_VERDICT;
6271
- return void 0;
6272
- }
6273
- function is_check_running(traces) {
6274
- return traces.process_trace === PROCESS_ALIVE;
6275
- }
6276
- function has_unreadable_trace(traces) {
6277
- return traces.is_child_settled === void 0 || traces.is_output_frozen === void 0 || traces.process_trace === PROCESS_UNKNOWN;
6278
- }
6279
- function to_verdict(traces) {
6280
- const definitive = definitive_verdict(traces);
6281
- if (definitive !== void 0) return definitive;
6282
- if (has_unreadable_trace(traces)) return UNDETERMINED_VERDICT;
6283
- return is_check_running(traces) ? ALIVE_VERDICT : STOPPED_VERDICT;
6284
- }
6285
- function to_advice(verdict, has_work_to_stash) {
6286
- if (verdict === ALIVE_VERDICT) return ALIVE_ADVICE;
6287
- if (verdict === SETTLED_VERDICT) return SETTLED_ADVICE;
6288
- if (verdict === UNDETERMINED_VERDICT) return UNDETERMINED_ADVICE;
6289
- return has_work_to_stash ? STOPPED_DIRTY_ADVICE : STOPPED_CLEAN_ADVICE;
6290
- }
6291
- function decide5(traces) {
6292
- const verdict = to_verdict(traces);
6293
- const has_work_to_stash = verdict === STOPPED_VERDICT && traces.is_tree_dirty;
6294
- return {
6295
- advice: to_advice(verdict, has_work_to_stash),
6296
- has_work_to_stash,
6297
- reason: traces.agent_state?.reason ?? REASONS[verdict],
6298
- verdict
6299
- };
6300
- }
6301
- function to_resolved_root(root) {
6302
- try {
6303
- return realpathSync(root);
6304
- } catch {
6305
- return root;
6306
- }
6307
- }
6308
- var ALLOWED_ROOTS = [
6309
- ...new Set(
6310
- [homedir(), tmpdir(), PLATFORM_TEMP_ROOT].flatMap((root) => [root, to_resolved_root(root)])
6311
- )
6312
- ];
6313
- function is_within(candidate, root) {
6314
- const relative = path14.relative(root, candidate);
6315
- return relative !== "" && !relative.startsWith("..") && !path14.isAbsolute(relative);
6316
- }
6317
- function to_safe_path(output_path) {
6318
- const normalized2 = path14.normalize(output_path);
6319
- if (!path14.isAbsolute(normalized2)) return void 0;
6320
- return ALLOWED_ROOTS.some((root) => is_within(normalized2, root)) ? normalized2 : void 0;
6321
- }
6322
- function sample_output(output_path) {
6323
- const safe_path = to_safe_path(output_path);
6324
- if (safe_path === void 0) return void 0;
6325
- const stats = statSync(safe_path, { throwIfNoEntry: false });
6326
- if (!stats?.isFile()) return void 0;
6327
- return { mtime_ms: stats.mtimeMs, size: stats.size };
6328
- }
6329
- function read_agent_state(output_path) {
6330
- const safe_path = to_safe_path(output_path);
6331
- if (safe_path === void 0) return void 0;
6332
- try {
6333
- const state = agent_event.read(readFileSync7(safe_path, "utf8"));
6334
- return state.provider === void 0 ? void 0 : state;
6335
- } catch {
6336
- return void 0;
6337
- }
6338
- }
6339
- function describe_agent_state(output_path) {
6340
- const state = read_agent_state(output_path);
6341
- return state === void 0 ? void 0 : `agent: ${state.provider ?? "unknown"} ${agent_event.describe(state)}`;
6342
- }
6343
- async function read_output_frozen(request2) {
6344
- const first = sample_output(request2.output_path);
6345
- if (first === void 0) return void 0;
6346
- if (request2.now_ms - first.mtime_ms < request2.silent_window_ms) return false;
6347
- await sleep(request2.gap_ms);
6348
- const second = sample_output(request2.output_path);
6349
- if (second === void 0) return void 0;
6350
- return second.mtime_ms === first.mtime_ms && second.size === first.size;
6351
- }
6352
- function carries_label(labels, wanted) {
6353
- return labels.some((label) => label.toLowerCase() === wanted);
6354
- }
6355
- var SETTLED_LABELS = [NEEDS_DECISION_LABEL, ALREADY_DONE_LABEL];
6356
- function carries_settled_label(labels) {
6357
- return SETTLED_LABELS.some((label) => carries_label(labels, label));
6358
- }
6359
- async function read_child_settled(issue, repo) {
6360
- const json = await git_gh_issue_read.issue_view_json(issue, STATE_FIELDS, repo);
6361
- if (json === void 0) return void 0;
6362
- const state = issue_state.parse_issue_state(json);
6363
- if (state === void 0) return void 0;
6364
- return state.state !== OPEN_STATE || carries_settled_label(state.labels);
6365
- }
6366
- async function read_traces(request2) {
6367
- const [is_initial_child_settled, is_output_frozen, is_tree_dirty] = await Promise.all([
6368
- read_child_settled(request2.issue, request2.repo),
6369
- read_output_frozen({
6370
- gap_ms: request2.gap_ms ?? DEFAULT_GAP_SECONDS * MS_PER_SECOND2,
6371
- now_ms: Date.now(),
6372
- output_path: request2.output_path,
6373
- silent_window_ms: request2.silent_window_ms ?? DEFAULT_SILENT_MINUTES * MS_PER_MINUTE3
6374
- }),
6375
- run_hold.is_tree_dirty()
6376
- ]);
6377
- const agent_state = read_agent_state(request2.output_path);
6378
- const is_latest_child_settled = is_agent_state_finished(agent_state) ? await read_child_settled(request2.issue, request2.repo) : void 0;
6379
- const is_child_settled = is_latest_child_settled ?? is_initial_child_settled;
6380
- return {
6381
- agent_state,
6382
- is_child_settled,
6383
- is_output_frozen,
6384
- is_tree_dirty,
6385
- process_trace: request2.process_trace
6386
- };
6387
- }
6388
- async function check2(request2) {
6389
- issue_number_shape.require_issue_number(request2.issue);
6390
- return decide5(await read_traces(request2));
6391
- }
6392
- var run_liveness = {
6393
- check: check2,
6394
- decide: decide5,
6395
- describe_agent_state,
6396
- read_child_settled,
6397
- read_output_frozen,
6398
- sample_output,
6399
- to_safe_path
6400
- };
6401
-
6402
5770
  // scripts/lane/lane-report.ts
6403
5771
  var NO_LANES2 = "No lanes are open.";
6404
- var OPEN_STATE2 = "open";
5772
+ var OPEN_STATE = "open";
6405
5773
  var STRANDED_STATE = "stranded";
6406
5774
  var UNREADABLE_STATE = "unreadable";
6407
5775
  var UNKNOWN_VALUE = "-";
6408
5776
  var COLUMN_SEPARATOR = " ";
6409
5777
  function lane_state(lane) {
6410
5778
  if (lane.is_stranded) return STRANDED_STATE;
6411
- return lane.seat === void 0 ? UNREADABLE_STATE : OPEN_STATE2;
5779
+ return lane.seat === void 0 ? UNREADABLE_STATE : OPEN_STATE;
6412
5780
  }
6413
5781
  function value_or_unknown(value) {
6414
5782
  return value === void 0 ? UNKNOWN_VALUE : String(value);
@@ -6452,7 +5820,7 @@ function describe_opened(lane) {
6452
5820
  }
6453
5821
  var lane_report = {
6454
5822
  NO_LANES: NO_LANES2,
6455
- OPEN_STATE: OPEN_STATE2,
5823
+ OPEN_STATE,
6456
5824
  STRANDED_STATE,
6457
5825
  UNREADABLE_STATE,
6458
5826
  describe_lane,
@@ -6461,198 +5829,6 @@ var lane_report = {
6461
5829
  lane_state
6462
5830
  };
6463
5831
 
6464
- // scripts/run/run-preflight.ts
6465
- import { z as z17 } from "zod";
6466
- var CLEAN_VERDICT = "clean";
6467
- var RECLAIM_VERDICT = "reclaim";
6468
- var RESUME_VERDICT = "resume";
6469
- var PARK_VERDICT = "park";
6470
- var NO_PR = "none";
6471
- var OPEN_PR = "open";
6472
- var MERGED_PR = "merged";
6473
- var CLOSED_PR = "closed";
6474
- var NO_CHILD_WORK = { branch_name: void 0, pr_state: NO_PR };
6475
- var pr_info_schema = z17.object({ state: z17.string().optional() });
6476
- var GH_PR_STATES = /* @__PURE__ */ new Map([
6477
- ["CLOSED", CLOSED_PR],
6478
- ["MERGED", MERGED_PR],
6479
- ["OPEN", OPEN_PR]
6480
- ]);
6481
- var CLEAN_REASON = "Nothing was left behind: the tree is clean, HEAD is on the default branch, and this issue has no pull request and no branch holding work.";
6482
- var CLEAN_ADVICE = "Start the child.";
6483
- var RESUME_ADVICE = "Reuse the branch and run the whole verification gate from the start \u2014 refactor, `pnpm josh gate`, `/code-review` \u2014 never only the part the interrupted run had not reached. Nobody verified what it already committed.";
6484
- var PARK_ADVICE = "Park the child: add `needs-decision` and comment what was found. Do not delete the branch, do not reopen the pull request, and do not commit on top of it.";
6485
- var STASH_LABEL_PREFIX = "run:hold reclaimed before #";
6486
- var { require_issue_number } = issue_number_shape;
6487
- var UNREADABLE_PR_MESSAGE = "The pull request could not be read for branch ";
6488
- function needs_reclaim(tree) {
6489
- return tree.is_dirty || tree.branch !== tree.default_branch;
6490
- }
6491
- function is_pr_decided(state) {
6492
- return state === MERGED_PR || state === CLOSED_PR;
6493
- }
6494
- function has_leftover_work(child) {
6495
- return child.branch_name !== void 0 || child.pr_state === OPEN_PR;
6496
- }
6497
- function to_verdict2(tree, child) {
6498
- if (needs_reclaim(tree)) return RECLAIM_VERDICT;
6499
- if (is_pr_decided(child.pr_state)) return PARK_VERDICT;
6500
- if (has_leftover_work(child)) return RESUME_VERDICT;
6501
- return CLEAN_VERDICT;
6502
- }
6503
- function reclaim_reason(tree) {
6504
- const findings = [
6505
- tree.is_dirty ? "uncommitted changes" : void 0,
6506
- tree.branch === tree.default_branch ? void 0 : `HEAD on ${tree.branch}`
6507
- ].filter((entry) => entry !== void 0);
6508
- return `An earlier run left ${findings.join(" and ")} in this working tree.`;
6509
- }
6510
- function park_reason(state) {
6511
- return `The pull request for this issue is ${state}, so whether any work is still owed is a person's call.`;
6512
- }
6513
- function resume_reason(child) {
6514
- const branch_part = child.branch_name === void 0 ? "" : ` on branch ${child.branch_name}`;
6515
- const pr_part = child.pr_state === OPEN_PR ? ", and its pull request is still open" : "";
6516
- return `An earlier run left a partial implementation${branch_part}${pr_part}.`;
6517
- }
6518
- function to_reason(verdict, tree, child) {
6519
- if (verdict === RECLAIM_VERDICT) return reclaim_reason(tree);
6520
- if (verdict === PARK_VERDICT) return park_reason(child.pr_state);
6521
- if (verdict === RESUME_VERDICT) return resume_reason(child);
6522
- return CLEAN_REASON;
6523
- }
6524
- function reclaim_steps(tree, issue) {
6525
- if (!tree.is_dirty) return [];
6526
- return [
6527
- `git stash push -u -m "${STASH_LABEL_PREFIX}${issue}"`,
6528
- `Record the stash on ${session_cite.issue(issue)} \u2014 the comment is what gets it popped; nothing pops it for you.`
6529
- ];
6530
- }
6531
- function reclaim_advice(tree, issue) {
6532
- return [
6533
- ...reclaim_steps(tree, issue),
6534
- "pnpm josh ms",
6535
- "Then ask this command again: it is re-askable, and the clean tree gets its own answer."
6536
- ].join("\n");
6537
- }
6538
- function to_advice2(verdict, issue, tree) {
6539
- if (verdict === RECLAIM_VERDICT) return reclaim_advice(tree, issue);
6540
- if (verdict === RESUME_VERDICT) return RESUME_ADVICE;
6541
- if (verdict === PARK_VERDICT) return PARK_ADVICE;
6542
- return CLEAN_ADVICE;
6543
- }
6544
- function decide6(tree, child, issue) {
6545
- const verdict = to_verdict2(tree, child);
6546
- return {
6547
- advice: to_advice2(verdict, issue, tree),
6548
- reason: to_reason(verdict, tree, child),
6549
- verdict
6550
- };
6551
- }
6552
- function read_pr_field(raw) {
6553
- return json_value.parse_with(raw, pr_info_schema)?.state;
6554
- }
6555
- function to_pr_state(raw) {
6556
- const state = read_pr_field(raw);
6557
- return (state === void 0 ? void 0 : GH_PR_STATES.get(state)) ?? NO_PR;
6558
- }
6559
- async function read_tree_state() {
6560
- const [is_dirty, current, default_branch] = await Promise.all([
6561
- run_hold.is_tree_dirty(),
6562
- git_command.branch(),
6563
- git_command.get_default_branch()
6564
- ]);
6565
- return { branch: current, default_branch, is_dirty };
6566
- }
6567
- async function read_pr_state(branch_name) {
6568
- if (!await git_gh_pr_read.pr_exists(branch_name)) return NO_PR;
6569
- const raw = await git_gh_pr_read.pr_view(branch_name);
6570
- if (raw === "") throw new Error(`${UNREADABLE_PR_MESSAGE}${branch_name}`);
6571
- return to_pr_state(raw);
6572
- }
6573
- function is_pr_open(state) {
6574
- return state === OPEN_PR;
6575
- }
6576
- async function read_pr_of_branch(branch_name) {
6577
- return { branch_name, pr_state: await read_pr_state(branch_name) };
6578
- }
6579
- function pick_child_state(reads) {
6580
- const decided = reads.find((read2) => is_pr_decided(read2.pr_state));
6581
- const open = reads.find((read2) => is_pr_open(read2.pr_state));
6582
- return decided ?? open ?? { branch_name: reads[0]?.branch_name, pr_state: NO_PR };
6583
- }
6584
- var ISSUE_BRANCH_SUFFIX = "-*";
6585
- var ANY_REMOTE_PREFIX = "*/";
6586
- var REMOTE_SEPARATOR = "/";
6587
- var NOT_FOUND_INDEX = -1;
6588
- function to_head_branch(name) {
6589
- const separator_index = name.indexOf(REMOTE_SEPARATOR);
6590
- return separator_index === NOT_FOUND_INDEX ? name : name.slice(separator_index + 1);
6591
- }
6592
- function group_by_head(pairs) {
6593
- const groups = /* @__PURE__ */ new Map();
6594
- for (const [name, reference] of pairs) groups.set(name, [...groups.get(name) ?? [], reference]);
6595
- return groups;
6596
- }
6597
- async function read_branch_candidates(issue) {
6598
- const pattern = `${issue}${ISSUE_BRANCH_SUFFIX}`;
6599
- const [local, remote] = await Promise.all([
6600
- git_command.branch_names(pattern),
6601
- git_command.branch_names_remote(`${ANY_REMOTE_PREFIX}${pattern}`)
6602
- ]);
6603
- return group_by_head([
6604
- ...local.map((name) => [name, name]),
6605
- ...remote.map((reference) => [to_head_branch(reference), reference])
6606
- ]);
6607
- }
6608
- async function has_commits_beyond(base, references) {
6609
- const counts = await Promise.all(
6610
- references.map(async (reference) => await git_command.commit_count_beyond(base, reference))
6611
- );
6612
- return counts.some((count) => count > 0);
6613
- }
6614
- async function is_held_dirty(name, lane) {
6615
- if (lane?.branch !== name || lane.is_stranded) return false;
6616
- return await run_hold.is_tree_dirty(lane.directory);
6617
- }
6618
- async function read_candidate(name, references, context) {
6619
- const state = await read_pr_of_branch(name);
6620
- if (state.pr_state !== NO_PR) return state;
6621
- if (await has_commits_beyond(context.base, references)) return state;
6622
- return await is_held_dirty(name, context.lane) ? state : void 0;
6623
- }
6624
- async function read_child_state(issue) {
6625
- const candidates = await read_branch_candidates(issue);
6626
- if (candidates.size === 0) return NO_CHILD_WORK;
6627
- const [base, lane] = await Promise.all([
6628
- git_command.default_branch_reference(),
6629
- lane_registry.find_open_lane(issue)
6630
- ]);
6631
- const reads = await Promise.all(
6632
- [...candidates].map(
6633
- async ([name, references]) => await read_candidate(name, references, { base, lane })
6634
- )
6635
- );
6636
- return pick_child_state(reads.filter((read2) => read2 !== void 0));
6637
- }
6638
- async function check3(issue) {
6639
- require_issue_number(issue);
6640
- const tree = await read_tree_state();
6641
- if (needs_reclaim(tree)) return decide6(tree, NO_CHILD_WORK, issue);
6642
- return decide6(tree, await read_child_state(issue), issue);
6643
- }
6644
- var run_preflight = {
6645
- CLEAN_VERDICT,
6646
- RESUME_ADVICE,
6647
- MERGED_PR,
6648
- STASH_LABEL_PREFIX,
6649
- check: check3,
6650
- decide: decide6,
6651
- read_child_state,
6652
- read_pr_state,
6653
- to_pr_state
6654
- };
6655
-
6656
5832
  // scripts/run/progress/run-progress-read.ts
6657
5833
  var { mark: mark2, parse_stamp: parse_stamp2, read_last_report: read_last_report2 } = run_progress_clock;
6658
5834
  var FIRST_LOAD_AVERAGE = 0;
@@ -6745,35 +5921,36 @@ var run_progress_read = {
6745
5921
  };
6746
5922
 
6747
5923
  // scripts/run/run-watcher-hook.ts
6748
- import { z as z18 } from "zod";
5924
+ import { z as z13 } from "zod";
6749
5925
 
6750
5926
  // scripts/run/run-watcher-guard.ts
6751
5927
  var WATCHER_TICK_MS = 3e4;
6752
5928
  var TICK_MULTIPLIER = 3;
6753
- var MS_PER_SECOND3 = 1e3;
5929
+ var MS_PER_SECOND2 = 1e3;
6754
5930
  var STALE_THRESHOLD_MS = WATCHER_TICK_MS * TICK_MULTIPLIER;
6755
- var STALE_THRESHOLD_S = STALE_THRESHOLD_MS / MS_PER_SECOND3;
5931
+ var STALE_THRESHOLD_S = STALE_THRESHOLD_MS / MS_PER_SECOND2;
6756
5932
  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`;
6757
5933
  var OK_RESULT = { kind: "ok" };
6758
5934
  async function owes_watcher() {
6759
5935
  if (run_headless.is_headless() || !await run_headless.is_backlog_parent()) return false;
6760
5936
  return await lane_registry.has_lanes_in_flight();
6761
5937
  }
6762
- async function check4(life_target) {
6763
- if (!await owes_watcher()) return OK_RESULT;
6764
- if (run_progress_clock.is_life_fresh(life_target, STALE_THRESHOLD_MS)) return OK_RESULT;
5938
+ async function check2(life_target) {
5939
+ if (!await owes_watcher() || run_progress_clock.is_life_fresh(life_target, STALE_THRESHOLD_MS)) {
5940
+ return OK_RESULT;
5941
+ }
6765
5942
  return { kind: "stale", note: STALE_NOTE };
6766
5943
  }
6767
5944
  var run_watcher_guard = {
6768
5945
  STALE_NOTE,
6769
5946
  STALE_THRESHOLD_MS,
6770
- check: check4
5947
+ check: check2
6771
5948
  };
6772
5949
 
6773
5950
  // scripts/run/run-watcher-hook.ts
6774
5951
  var SWITCH_ENV_KEY2 = "JOSH_WATCHER_GUARD";
6775
5952
  var STAMP_PREFIX2 = "josh-watcher-guard-";
6776
- var payload_schema = z18.object({ transcript_path: z18.string().min(1) });
5953
+ var payload_schema = z13.object({ transcript_path: z13.string().min(1) });
6777
5954
  function transcript_of(raw_payload) {
6778
5955
  return json_value.parse_with(raw_payload, payload_schema)?.transcript_path;
6779
5956
  }
@@ -6803,14 +5980,14 @@ var run_watcher_hook = { transcript_of, watcher_hook_reason };
6803
5980
  var SWITCH_ENV_KEY3 = "JOSH_PARENT_CUT_GUARD";
6804
5981
  var STAMP_PREFIX3 = "josh-parent-cut-guard-";
6805
5982
  var QUIET_WINDOW_MS = 6e5;
6806
- function current_state3() {
5983
+ function current_state2() {
6807
5984
  return {
6808
5985
  carry: async () => await run_headless.driving_carry(),
6809
5986
  verdict: () => implementation_cut_verdict.reused_verdict(() => cost_cli.session_verdict())
6810
5987
  };
6811
5988
  }
6812
5989
  var THRESHOLD_TEXT2 = `${cost_format.format_tokens(CONTEXT_CUT_THRESHOLD)}-token`;
6813
- var PARENT_CUT_REASON = `\u26D4 backlogrun parent hand-off: this session drives a \`backlogrun\` (it owns the live carry record) and its recent-context cost has crossed the shared ${THRESHOLD_TEXT2} threshold between merges, so every later request re-reads the accumulated context (joshuafolkken/kit#2947). Take the hand-off now rather than at the next merge: follow \`.claude/skills/workflow-commands/backlogrun-progress.md\` \u2192 "The hand-off" from its \`over\` branch \u2014 open no new lane and take no new child, read \`pnpm josh lane:list\`, make sure every lane in flight records an output path, then run \`pnpm josh run:carry --cut --owner "$PPID"\` and, unless it answers \`capped\`, \`pnpm josh run:wake\`, post the progress comment and end the turn. A lane nobody can poll means the cut does not happen; carry on and this asks again later. Reissue this call when the hand-off is done or cannot be taken \u2014 the guard stays quiet for a while after it refuses, so the hand-off calls themselves are not refused.`;
5990
+ var PARENT_CUT_REASON = `\u26D4 backlogrun parent hand-off: this session drives a \`backlogrun\` and its recent-context cost crossed the ${THRESHOLD_TEXT2} threshold between merges. Take the hand-off now: open no new lane, follow \`backlogrun-progress.md\` \u2192 "The hand-off" (\`pnpm josh run:carry --cut --owner "$PPID"\`, then \`pnpm josh run:wake\` unless \`capped\`) and end the turn. Reissue this call once done or if it cannot be taken.`;
6814
5991
  async function is_due3(state) {
6815
5992
  const carry = await state.carry();
6816
5993
  if (carry === void 0 || run_headless.is_cut_capped(carry)) return false;
@@ -6837,7 +6014,7 @@ function is_unit_call(raw_payload) {
6837
6014
  function is_exempt2(raw_payload) {
6838
6015
  return !hook_decision.is_switch_enabled(SWITCH_ENV_KEY3) || is_unit_call(raw_payload);
6839
6016
  }
6840
- async function parent_cut_reason(raw_payload, now_ms = Date.now(), state = current_state3()) {
6017
+ async function parent_cut_reason(raw_payload, now_ms = Date.now(), state = current_state2()) {
6841
6018
  if (is_exempt2(raw_payload)) return void 0;
6842
6019
  const transcript = run_watcher_hook.transcript_of(raw_payload);
6843
6020
  if (transcript === void 0) return void 0;
@@ -6850,8 +6027,95 @@ var run_parent_cut_hook = {
6850
6027
  parent_cut_reason
6851
6028
  };
6852
6029
 
6030
+ // scripts/run/run-poll-hook.ts
6031
+ var SWITCH_ENV_KEY4 = "JOSH_POLL_GUARD";
6032
+ var BASH_TOOL = "Bash";
6033
+ var STATE_READS = /* @__PURE__ */ new Set([
6034
+ "josh lane:list",
6035
+ "josh lane:output",
6036
+ "josh run:board",
6037
+ "josh run:carry",
6038
+ "josh run:liveness",
6039
+ "josh run:progress",
6040
+ "josh run:wake"
6041
+ ]);
6042
+ var POLL_REASON = '\u26D4 poll: this `backlogrun` session repeats a state read with nothing decided in between, on every occurrence. Wait on `pnpm josh backlog:drive`, or `pnpm josh lane:await <N...> --owner "$PPID"` in the background, and end the turn; its exit is the wake.';
6043
+ var WRITING_FLAGS = /* @__PURE__ */ new Set([
6044
+ "--begin",
6045
+ "--cut",
6046
+ "--done",
6047
+ "--end",
6048
+ "--every",
6049
+ "--filed",
6050
+ "--loop",
6051
+ "--mark",
6052
+ "--merged",
6053
+ "--resume",
6054
+ "--retrospective",
6055
+ "--start",
6056
+ "--stop",
6057
+ "--wait"
6058
+ ]);
6059
+ var FLAG_PREFIX4 = "-";
6060
+ var NAME_WORDS = 2;
6061
+ var SIGNATURE_SEPARATOR = " ";
6062
+ function read_key(signature) {
6063
+ const words = signature.split(SIGNATURE_SEPARATOR);
6064
+ const name = words.slice(0, NAME_WORDS).join(SIGNATURE_SEPARATOR);
6065
+ if (!STATE_READS.has(name) || words.some((word) => WRITING_FLAGS.has(word))) return void 0;
6066
+ return words.filter((word) => !word.startsWith(FLAG_PREFIX4)).join(SIGNATURE_SEPARATOR);
6067
+ }
6068
+ function read_keys(signatures) {
6069
+ return signatures.map((signature) => read_key(signature)).filter((key) => key !== void 0);
6070
+ }
6071
+ function state_reads_of(command) {
6072
+ return read_keys(time_shell.josh_signatures_of(command));
6073
+ }
6074
+ function command_of(input) {
6075
+ if (typeof input !== "object" || input === null || !("command" in input)) return "";
6076
+ return typeof input.command === "string" ? input.command : "";
6077
+ }
6078
+ function candidate_reads(raw_payload) {
6079
+ const payload = hook_decision.parse_hook_payload(raw_payload);
6080
+ if (payload?.tool_name !== BASH_TOOL || (payload.agent_id ?? "") !== "") return [];
6081
+ return state_reads_of(command_of(payload.tool_input));
6082
+ }
6083
+ function last_step(tail) {
6084
+ return time_spans.parse_timeline(tail).spans.findLast((span) => span.category !== time_spans.MODEL_CATEGORY);
6085
+ }
6086
+ function repeats_last_call(tail, reads) {
6087
+ const last = last_step(tail);
6088
+ if (last?.category !== time_spans.TOOL_CATEGORY) return false;
6089
+ const last_reads = read_keys(last.josh_signatures);
6090
+ return reads.some((key) => last_reads.includes(key));
6091
+ }
6092
+ function is_repeat(raw_payload) {
6093
+ const reads = hook_decision.is_switch_enabled(SWITCH_ENV_KEY4) ? candidate_reads(raw_payload) : [];
6094
+ const transcript = reads.length === 0 ? void 0 : run_watcher_hook.transcript_of(raw_payload);
6095
+ if (transcript === void 0) return false;
6096
+ return repeats_last_call(time_density_hook.read_tail(transcript), reads);
6097
+ }
6098
+ async function poll_reason(raw_payload, is_parent = run_headless.is_backlog_parent) {
6099
+ if (!is_repeat(raw_payload)) return void 0;
6100
+ return await is_parent() ? POLL_REASON : void 0;
6101
+ }
6102
+ async function safe_poll_reason(raw_payload) {
6103
+ try {
6104
+ return await poll_reason(raw_payload);
6105
+ } catch (error) {
6106
+ error_text.trace_swallowed("run_poll_hook.poll_reason", error);
6107
+ return void 0;
6108
+ }
6109
+ }
6110
+ var run_poll_hook = {
6111
+ POLL_REASON,
6112
+ SWITCH_ENV_KEY: SWITCH_ENV_KEY4,
6113
+ poll_reason,
6114
+ safe_poll_reason
6115
+ };
6116
+
6853
6117
  // scripts/hooks/step-zero-notice.ts
6854
- import path15 from "node:path";
6118
+ import path12 from "node:path";
6855
6119
  var STAMP_PREFIX4 = "josh-step-zero-notice-";
6856
6120
  var EDIT_TOOLS3 = /* @__PURE__ */ new Set(["Edit", "Write"]);
6857
6121
  var PARENT_PREFIX = "..";
@@ -6866,9 +6130,9 @@ function edited_paths(tool_input) {
6866
6130
  );
6867
6131
  }
6868
6132
  function is_runtime_file(file_path, root) {
6869
- const relative = path15.relative(root, path15.resolve(root, file_path));
6870
- if (relative.startsWith(PARENT_PREFIX) || path15.isAbsolute(relative)) return false;
6871
- return !test_declared_logic.is_exempt(relative.split(path15.sep).join("/"));
6133
+ const relative = path12.relative(root, path12.resolve(root, file_path));
6134
+ if (relative.startsWith(PARENT_PREFIX) || path12.isAbsolute(relative)) return false;
6135
+ return !test_declared_logic.is_exempt(relative.split(path12.sep).join("/"));
6872
6136
  }
6873
6137
  function is_candidate2(raw_payload, root) {
6874
6138
  const payload = hook_decision.parse_hook_payload(raw_payload);
@@ -6882,26 +6146,22 @@ function notice(raw_payload, root = process.cwd()) {
6882
6146
  const transcript = is_candidate2(raw_payload, root);
6883
6147
  if (transcript === void 0) return void 0;
6884
6148
  const target = STAMP.path(transcript);
6885
- if (STAMP.last_ms(target) !== hook_decision.NEVER_MS) return void 0;
6886
- if (!STAMP.record(target, Date.now())) return void 0;
6149
+ if (STAMP.last_ms(target) !== hook_decision.NEVER_MS || !STAMP.record(target, Date.now())) {
6150
+ return void 0;
6151
+ }
6887
6152
  return NOTICE;
6888
6153
  }
6889
6154
  var step_zero_notice = { NOTICE, notice, stamp_path: STAMP.path };
6890
6155
 
6891
6156
  // scripts/hooks/pretool-guard.ts
6892
- function combine_outcomes(batch, extra_reason) {
6893
- if (batch.reason !== void 0) return batch;
6894
- if (extra_reason !== void 0) {
6895
- return { reason: extra_reason, notice: void 0, fault: void 0 };
6896
- }
6897
- return batch;
6898
- }
6157
+ var CLEAR = { reason: void 0, notice: void 0, fault: void 0 };
6899
6158
  function is_clear(outcome) {
6900
6159
  return outcome.reason === void 0 && outcome.notice === void 0 && outcome.fault === void 0;
6901
6160
  }
6902
- function with_duplicate_notice(combined, notice2) {
6903
- if (notice2 === void 0 || !is_clear(combined)) return combined;
6904
- return { reason: void 0, notice: notice2, fault: void 0 };
6161
+ function combine_outcomes(outcomes, rule_reason) {
6162
+ const reason = outcomes.find((outcome) => outcome.reason !== void 0)?.reason ?? rule_reason;
6163
+ if (reason !== void 0) return { reason, notice: void 0, fault: void 0 };
6164
+ return outcomes.find((outcome) => !is_clear(outcome)) ?? CLEAR;
6905
6165
  }
6906
6166
  function with_step_zero_notice(outcome, raw_payload) {
6907
6167
  if (!is_clear(outcome)) return outcome;
@@ -6909,13 +6169,12 @@ function with_step_zero_notice(outcome, raw_payload) {
6909
6169
  return notice2 === void 0 ? outcome : { reason: void 0, notice: notice2, fault: void 0 };
6910
6170
  }
6911
6171
  function guard_outcome(raw_payload) {
6912
- const batch = batch_outcome(raw_payload);
6913
- const investigation = investigation_refusal(raw_payload);
6914
- const duplicate = duplicate_read_outcome(raw_payload);
6915
- const rule = delivered_rules.delivery(raw_payload);
6916
- const duplicate_refusal = duplicate.reason;
6917
- const combined = combine_outcomes(batch, investigation ?? duplicate_refusal ?? rule);
6918
- return with_duplicate_notice(combined, duplicate.notice);
6172
+ const outcomes = [
6173
+ batch_outcome(raw_payload),
6174
+ investigation_outcome(raw_payload),
6175
+ duplicate_read_outcome(raw_payload)
6176
+ ];
6177
+ return combine_outcomes(outcomes, delivered_rules.delivery(raw_payload));
6919
6178
  }
6920
6179
  function pretool_outcome(raw_payload) {
6921
6180
  return with_step_zero_notice(guard_outcome(raw_payload), raw_payload);
@@ -6926,7 +6185,8 @@ async function mark_phase(outcome) {
6926
6185
  async function async_reason(raw_payload) {
6927
6186
  const watcher = await run_watcher_hook.watcher_hook_reason(raw_payload);
6928
6187
  const cut = watcher ?? await run_parent_cut_hook.parent_cut_reason(raw_payload);
6929
- return cut ?? await issue_watch_hook.watch_reason();
6188
+ const poll2 = cut ?? await run_poll_hook.safe_poll_reason(raw_payload);
6189
+ return poll2 ?? await issue_watch_hook.watch_reason();
6930
6190
  }
6931
6191
  async function pretool_outcome_async(raw_payload) {
6932
6192
  const base = guard_outcome(raw_payload);
@@ -6958,38 +6218,26 @@ function foreground_rewrite(outcome, raw_payload, is_lane_child) {
6958
6218
  rewrite_context(lane_background.FOREGROUND_NOTE, outcome)
6959
6219
  );
6960
6220
  }
6961
- function pipefail_rewrite(raw_payload) {
6962
- if (!delivered_rules.is_enabled()) return void 0;
6963
- const call = guarded_call(raw_payload);
6964
- const input = call === void 0 ? void 0 : piped_verification.pipefail_input(call);
6965
- const payload = json_value.parse_or_undefined(raw_payload);
6966
- if (input === void 0 || !json_value.is_record(payload)) return void 0;
6967
- return { payload: JSON.stringify({ ...payload, tool_input: input }), input };
6968
- }
6969
- function pipefail_envelope(outcome, input) {
6970
- if (input === void 0 || outcome.reason !== void 0) return void 0;
6971
- return hook_decision.rewrite_envelope(
6972
- input,
6973
- rewrite_context(piped_verification.PIPEFAIL_NOTE, outcome)
6974
- );
6221
+ function rule_envelope(outcome, rewrite3) {
6222
+ if (rewrite3 === void 0 || outcome.reason !== void 0) return void 0;
6223
+ return hook_decision.flow_rewrite_envelope(rewrite3.input, rewrite_context(rewrite3.note, outcome));
6975
6224
  }
6976
- function emit(raw_payload, outcome, pipefail_input2) {
6977
- const rewrite = pipefail_envelope(outcome, pipefail_input2) ?? foreground_rewrite(outcome, raw_payload);
6978
- if (rewrite === void 0) hook_decision.emit_outcome(outcome);
6979
- else process.stdout.write(`${rewrite}
6225
+ function emit(raw_payload, outcome, rewrite3) {
6226
+ const envelope = rule_envelope(outcome, rewrite3) ?? foreground_rewrite(outcome, raw_payload);
6227
+ if (envelope === void 0) hook_decision.emit_outcome(outcome);
6228
+ else process.stdout.write(`${envelope}
6980
6229
  `);
6981
6230
  }
6982
6231
  async function respond(raw_payload) {
6983
- const pipefail = pipefail_rewrite(raw_payload);
6984
- const judged = pipefail?.payload ?? raw_payload;
6985
- emit(judged, await pretool_outcome_async(judged), pipefail?.input);
6232
+ const rewrite3 = rule_rewrite.rewrite(raw_payload);
6233
+ const judged = rewrite3?.payload ?? raw_payload;
6234
+ emit(judged, await pretool_outcome_async(judged), rewrite3);
6986
6235
  }
6987
6236
  var pretool_guard = {
6988
6237
  combine_outcomes,
6989
6238
  emit,
6990
6239
  foreground_rewrite,
6991
6240
  mark_phase,
6992
- pipefail_rewrite,
6993
6241
  pretool_outcome,
6994
6242
  respond
6995
6243
  };