@joshuafolkken/kit 1.1784.0 → 1.2114.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 (690) hide show
  1. package/.claude/agents/reviewer-verification.md +19 -0
  2. package/.claude/agents/reviewer.md +19 -0
  3. package/.claude/settings.json +5 -6
  4. package/.claude/skills/dependency-update/SKILL.md +9 -10
  5. package/.claude/skills/workflow-commands/SKILL.md +6 -6
  6. package/.claude/skills/workflow-commands/background-commands.md +8 -5
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +3 -1
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +1 -1
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +5 -2
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +2 -3
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +1 -1
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +3 -2
  13. package/.claude/skills/workflow-commands/backlogrun.md +1 -1
  14. package/.claude/skills/workflow-commands/chain-rule.md +41 -32
  15. package/.claude/skills/workflow-commands/delegation.md +6 -6
  16. package/.claude/skills/workflow-commands/entry-sequence.md +2 -2
  17. package/.claude/skills/workflow-commands/followup.md +7 -5
  18. package/.claude/skills/workflow-commands/fullrun-steps.md +6 -6
  19. package/.claude/skills/workflow-commands/fullrun.md +1 -1
  20. package/.claude/skills/workflow-commands/issue-comments.md +9 -4
  21. package/.claude/skills/workflow-commands/issue-fold-existing.md +2 -1
  22. package/.claude/skills/workflow-commands/kickoff.md +2 -2
  23. package/.claude/skills/workflow-commands/observation-filing.md +1 -1
  24. package/.claude/skills/workflow-commands/observation-ledger.md +6 -4
  25. package/.claude/skills/workflow-commands/pre-gate-cut.md +9 -7
  26. package/.claude/skills/workflow-commands/prerequisite.md +3 -2
  27. package/.claude/skills/workflow-commands/retrospective.md +2 -2
  28. package/.claude/skills/workflow-commands/split-assessment.md +7 -5
  29. package/.codex/hooks.json +5 -5
  30. package/.github/actions/setup-pnpm/action.yml +9 -16
  31. package/.github/dependabot.yml +16 -0
  32. package/.github/workflows/auto-tag.yml +45 -0
  33. package/.github/workflows/ci.yml +155 -15
  34. package/.github/workflows/dependabot-auto-merge.yml +63 -4
  35. package/.github/workflows/production.yml +8 -0
  36. package/.github/workflows/publish.yml +59 -45
  37. package/.vscode/settings.json +19 -0
  38. package/CLAUDE.md +5 -12
  39. package/SECURITY.md +2 -2
  40. package/dist/CLAUDE.md +5 -12
  41. package/dist/commands/document-read-cli.js +2 -1
  42. package/dist/commands/document-section-main.js +2 -1
  43. package/dist/commands/read-set-cli.js +194 -134
  44. package/dist/commands/run-prep-main.js +9694 -10173
  45. package/dist/commands/run-step-cli.js +22138 -18285
  46. package/dist/hooks/audit-provision.js +23 -5
  47. package/dist/hooks/chunk-3GKE653K.js +6204 -0
  48. package/dist/hooks/chunk-45CJJOO7.js +1243 -0
  49. package/dist/hooks/{chunk-CWMBGKYW.js → chunk-5PM2JRGV.js} +2153 -856
  50. package/dist/hooks/{chunk-WR2KQAYZ.js → chunk-7SIUNQXK.js} +1207 -1470
  51. package/dist/hooks/{chunk-O2YBIDSI.js → chunk-CQD4LJE6.js} +1682 -1130
  52. package/dist/hooks/{chunk-VWWCFGWK.js → chunk-OPSRGWFU.js} +1 -1
  53. package/dist/hooks/{chunk-XGBNSWEI.js → chunk-UJ3FPMMM.js} +132 -49
  54. package/dist/hooks/chunk-VVNYJWEI.js +30 -0
  55. package/dist/hooks/chunk-Z6DQJKH3.js +2806 -0
  56. package/dist/hooks/codex-hook-adapter.js +8 -8
  57. package/dist/hooks/format-edited.js +4 -5
  58. package/dist/hooks/inputs.json +415 -389
  59. package/dist/hooks/pretool-guard.js +7 -6
  60. package/dist/hooks/session-lang.js +101 -258
  61. package/dist/hooks/stop-guard.js +1744 -2576
  62. package/dist/josh.js +7133 -6368
  63. package/dist/version/index.js +161 -1
  64. package/eslint/rules/comment-why-only.js +108 -0
  65. package/package.json +15 -8
  66. package/prompts/coding-standards.md +1 -1
  67. package/prompts/collaboration-workflow/file-edits.md +14 -11
  68. package/prompts/collaboration-workflow/issue-citation.md +0 -4
  69. package/prompts/collaboration-workflow/issue-template.md +40 -21
  70. package/prompts/collaboration-workflow/lane-instructions.md +3 -1
  71. package/prompts/collaboration-workflow/measurement.md +8 -1
  72. package/prompts/collaboration-workflow/operating-rules.md +11 -2
  73. package/prompts/collaboration-workflow/output-bounds.md +6 -2
  74. package/prompts/collaboration-workflow/plan-comment.md +1 -1
  75. package/prompts/collaboration-workflow/principles.md +9 -0
  76. package/prompts/collaboration-workflow/residency.md +3 -2
  77. package/prompts/collaboration-workflow/rule-delivery.md +13 -1
  78. package/prompts/collaboration-workflow/upstream-interrupt.md +2 -2
  79. package/prompts/collaboration-workflow/wip-cap.md +3 -1
  80. package/prompts/review-rubric.md +8 -0
  81. package/prompts/review.md +31 -22
  82. package/prompts/testing-guide.md +1 -0
  83. package/scripts/agent/agent-argv.ts +14 -16
  84. package/scripts/agent/agent-provider.ts +138 -0
  85. package/scripts/agent/agent-role-profile.ts +108 -156
  86. package/scripts/agent/agent-safe-text.ts +20 -5
  87. package/scripts/agent/api-usage-limit.ts +65 -0
  88. package/scripts/agent/claude-agent-argv.ts +19 -9
  89. package/scripts/agent/codex-agent-argv.ts +118 -22
  90. package/scripts/agent/usage-limit-reset.ts +179 -0
  91. package/scripts/auth/claude-token-output.ts +2 -2
  92. package/scripts/auto-ok/auto-ok-author.ts +53 -0
  93. package/scripts/auto-ok/auto-ok-cli.ts +20 -9
  94. package/scripts/backlog/backlog-defect-priority.ts +1 -1
  95. package/scripts/backlog/backlog-drive-cli.ts +6 -1
  96. package/scripts/backlog/backlog-drive-hold.ts +3 -3
  97. package/scripts/backlog/backlog-drive-idle.ts +10 -0
  98. package/scripts/backlog/backlog-drive-launch-pass.ts +90 -0
  99. package/scripts/backlog/backlog-drive-launch.ts +8 -2
  100. package/scripts/backlog/backlog-drive-named-offer.ts +73 -30
  101. package/scripts/backlog/backlog-drive-named.ts +28 -7
  102. package/scripts/backlog/backlog-drive-paused.ts +5 -4
  103. package/scripts/backlog/backlog-drive-restore.ts +11 -3
  104. package/scripts/backlog/backlog-drive.ts +21 -60
  105. package/scripts/backlog/backlog-gate-scope.ts +61 -0
  106. package/scripts/backlog/backlog-measure-close.ts +112 -0
  107. package/scripts/backlog/backlog-next.ts +39 -49
  108. package/scripts/backlog/backlog-occupied.ts +2 -2
  109. package/scripts/backlog/backlog-overlap.ts +42 -8
  110. package/scripts/backlog/backlog-plan-cli.ts +4 -1
  111. package/scripts/backlog/backlog-plan-comment.ts +81 -0
  112. package/scripts/backlog/backlog-plan-read.ts +19 -6
  113. package/scripts/backlog/backlog-pool.ts +50 -12
  114. package/scripts/backlog/backlog-rank.ts +60 -13
  115. package/scripts/backlog/backlog-stalled-detect.ts +2 -1
  116. package/scripts/backlog/backlog-waves.ts +17 -2
  117. package/scripts/backlog/backlogrun-launch.ts +7 -4
  118. package/scripts/cost-runtime/context-cut-payback.ts +27 -25
  119. package/scripts/cost-runtime/cost-cli.ts +37 -16
  120. package/scripts/cost-runtime/cost-pricing.ts +9 -0
  121. package/scripts/cost-runtime/cost-verdict.ts +4 -1
  122. package/scripts/delegation/delegation-policy.ts +2 -2
  123. package/scripts/delegation/duplicate-read-guard.ts +1 -0
  124. package/scripts/delegation/investigation-guard.ts +14 -2
  125. package/scripts/delegation/investigation-reads.ts +45 -23
  126. package/scripts/dependabot/dependabot-cooldown.ts +3 -2
  127. package/scripts/discovery/repo-party.ts +80 -20
  128. package/scripts/discovery/session-owner-pin.ts +51 -0
  129. package/scripts/document/capped-print.ts +1 -1
  130. package/scripts/document/command-reference-line-budget.ts +42 -0
  131. package/scripts/document/document-byte-budget.ts +17 -7
  132. package/scripts/document/document-checks.ts +20 -0
  133. package/scripts/document/document-ratchet-check.ts +101 -0
  134. package/scripts/document/document-section.ts +1 -0
  135. package/scripts/document/entry-read-budget.ts +7 -3
  136. package/scripts/document/issue-citation-budget.ts +94 -0
  137. package/scripts/epic/epic-cli-argv.ts +2 -2
  138. package/scripts/epic/epic-fetch.ts +4 -2
  139. package/scripts/epic/epic-graph.ts +7 -2
  140. package/scripts/epic/epic-issue.ts +3 -1
  141. package/scripts/epic/epic-rank.ts +5 -0
  142. package/scripts/exports/unused-members-check.ts +62 -0
  143. package/scripts/exports/unused-members-cli.ts +6 -51
  144. package/scripts/followup/followup-ci-path.ts +67 -0
  145. package/scripts/followup/followup-coderabbit-retrigger.ts +261 -0
  146. package/scripts/followup/followup-coderabbit-wait.ts +13 -10
  147. package/scripts/followup/followup-failed-tests.ts +160 -0
  148. package/scripts/followup/followup-job-needs.ts +100 -0
  149. package/scripts/followup/followup-job-rerun.ts +303 -0
  150. package/scripts/followup/followup-main-move.ts +113 -0
  151. package/scripts/followup/git-followup-finish.ts +43 -11
  152. package/scripts/followup/git-followup-pending.ts +3 -3
  153. package/scripts/followup/git-followup-stages.ts +41 -11
  154. package/scripts/followup/git-pr-followup-checks.ts +55 -16
  155. package/scripts/followup/git-pr-followup-wrapup.ts +24 -5
  156. package/scripts/followup/git-pr-followup.ts +62 -27
  157. package/scripts/fresh-machine/fresh-machine-script.ts +5 -9
  158. package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +2 -2
  159. package/scripts/fresh-machine/specs/kit-test-backlogrun.json +2 -2
  160. package/scripts/gate/busy-average.ts +34 -2
  161. package/scripts/gate/check-related-pinned.ts +43 -0
  162. package/scripts/gate/check-related-reuse.ts +64 -0
  163. package/scripts/gate/check-related-section.ts +57 -0
  164. package/scripts/gate/check-related-unplanned.ts +34 -0
  165. package/scripts/gate/check-related.ts +265 -0
  166. package/scripts/gate/command-memory.ts +1 -1
  167. package/scripts/gate/core-admission.ts +24 -6
  168. package/scripts/gate/core-budget-timing.ts +20 -6
  169. package/scripts/gate/core-budget.ts +40 -11
  170. package/scripts/gate/core-wait-record.ts +3 -2
  171. package/scripts/gate/core-wait.ts +25 -2
  172. package/scripts/gate/core-weights.ts +15 -7
  173. package/scripts/gate/gate-labels.ts +46 -0
  174. package/scripts/gate/gate-ledger.ts +14 -6
  175. package/scripts/gate/gate-plan.ts +103 -58
  176. package/scripts/gate/gate-report.ts +9 -7
  177. package/scripts/gate/gate-run-steps.ts +26 -3
  178. package/scripts/gate/gate-skip.ts +45 -6
  179. package/scripts/gate/gate-static-group.ts +60 -0
  180. package/scripts/gate/gate-steps.ts +2 -1
  181. package/scripts/gate/gate-tree.ts +4 -2
  182. package/scripts/gate/hook-gate-reuse.ts +19 -2
  183. package/scripts/gate/metrics-totals.ts +59 -0
  184. package/scripts/gate/scoped-green.ts +68 -15
  185. package/scripts/gate/since-green.ts +14 -6
  186. package/scripts/gate/structural-checks.ts +96 -0
  187. package/scripts/gate/verification-gate.ts +51 -16
  188. package/scripts/gate/verification-inputs.ts +21 -6
  189. package/scripts/gate/verification-tail.ts +52 -0
  190. package/scripts/gh/gh-author-association.ts +23 -2
  191. package/scripts/gh/git-ai-review-scan.ts +1 -1
  192. package/scripts/gh/git-gh-api-path.ts +12 -3
  193. package/scripts/gh/git-gh-comments-unreadable.ts +36 -0
  194. package/scripts/gh/git-gh-exec.ts +32 -2
  195. package/scripts/gh/git-gh-issue-read.ts +4 -2
  196. package/scripts/gh/git-gh-issue-rest.ts +1 -0
  197. package/scripts/gh/git-gh-issue-write.ts +8 -2
  198. package/scripts/gh/git-gh-issue.ts +12 -5
  199. package/scripts/gh/git-gh-pr-auto-merge.ts +13 -6
  200. package/scripts/gh/git-gh-pr-graphql-state.ts +56 -0
  201. package/scripts/gh/git-gh-pr-read.ts +74 -13
  202. package/scripts/gh/git-gh-pr-rest.ts +3 -1
  203. package/scripts/gh/git-gh-pr-snapshot.ts +22 -7
  204. package/scripts/gh/git-gh-pr-too-large.ts +20 -0
  205. package/scripts/gh/git-gh-pr.ts +21 -7
  206. package/scripts/gh/git-gh-retry.ts +141 -0
  207. package/scripts/gh/git-pr-ai-review.ts +15 -6
  208. package/scripts/gh/git-pr-checks-eval.ts +3 -0
  209. package/scripts/gh/git-pr-checks.ts +5 -5
  210. package/scripts/git/base-tree.ts +30 -0
  211. package/scripts/git/change-base.ts +4 -10
  212. package/scripts/git/changed-paths-sync.ts +63 -0
  213. package/scripts/git/changed-paths.ts +16 -1
  214. package/scripts/git/git-common-directory.ts +11 -1
  215. package/scripts/git/git-diff-reads.ts +66 -20
  216. package/scripts/git/git-error.ts +9 -2
  217. package/scripts/git/git-schemas.ts +13 -0
  218. package/scripts/git/git-worktree.ts +13 -0
  219. package/scripts/git/gone-branch.ts +4 -1
  220. package/scripts/git/main-merge-budget-conflict.ts +89 -0
  221. package/scripts/git/main-merge-guard.ts +55 -18
  222. package/scripts/git/main-merge-regenerate.ts +59 -25
  223. package/scripts/git/main-merge.ts +53 -5
  224. package/scripts/git/no-base-checkout.ts +127 -0
  225. package/scripts/git/stash/git-stash.ts +11 -2
  226. package/scripts/git/stash/stash-push-cli.ts +10 -0
  227. package/scripts/hooks/batch-guard.ts +40 -21
  228. package/scripts/hooks/edited-cspell.ts +37 -11
  229. package/scripts/hooks/format-edited-file.ts +22 -2
  230. package/scripts/hooks/lane-plan-guard.ts +50 -0
  231. package/scripts/hooks/pre-commit-lint-switch.ts +9 -0
  232. package/scripts/hooks/pre-commit-lint.ts +14 -1
  233. package/scripts/hooks/pre-push-related-switch.ts +10 -0
  234. package/scripts/hooks/pre-push-unit.ts +76 -10
  235. package/scripts/hooks/pretool-guard.ts +2 -0
  236. package/scripts/hooks/reserved-run.ts +14 -1
  237. package/scripts/hooks/run-hook.sh +23 -8
  238. package/scripts/hooks/step-zero-notice.ts +6 -1
  239. package/scripts/hooks/stop-actions.ts +3 -15
  240. package/scripts/hooks/stop-guard.ts +7 -6
  241. package/scripts/init/distributed-paths.ts +1 -0
  242. package/scripts/init/hook-command-rewrite.ts +5 -1
  243. package/scripts/init/hook-launch.ts +4 -2
  244. package/scripts/issue/aggregation-split.ts +52 -0
  245. package/scripts/issue/defect-origin-line.ts +19 -1
  246. package/scripts/issue/defect-rate-cli.ts +5 -102
  247. package/scripts/issue/defect-report.ts +4 -4
  248. package/scripts/issue/effect-audit-cli.ts +178 -0
  249. package/scripts/issue/effect-audit.ts +185 -0
  250. package/scripts/issue/expected-effect-lint.ts +64 -9
  251. package/scripts/issue/git-next-issues.ts +5 -0
  252. package/scripts/issue/issue-body-or-comment.ts +38 -4
  253. package/scripts/issue/issue-cite-cli.ts +1 -3
  254. package/scripts/issue/issue-classification.ts +1 -0
  255. package/scripts/issue/issue-closing-pr.ts +8 -1
  256. package/scripts/issue/issue-evidence-exit.ts +4 -2
  257. package/scripts/issue/issue-file-cap.ts +58 -0
  258. package/scripts/issue/issue-file-cli.ts +111 -38
  259. package/scripts/issue/issue-file-deferral.ts +52 -0
  260. package/scripts/issue/issue-file-fold.ts +29 -10
  261. package/scripts/issue/issue-file-interrupt.ts +85 -0
  262. package/scripts/issue/issue-file-lane-hint.ts +30 -0
  263. package/scripts/issue/issue-file.ts +43 -6
  264. package/scripts/issue/issue-labels.ts +19 -2
  265. package/scripts/issue/issue-lint-cli.ts +27 -2
  266. package/scripts/issue/issue-measured-cli.ts +13 -35
  267. package/scripts/issue/issue-measured-close.ts +58 -0
  268. package/scripts/issue/issue-not-before-label.ts +2 -2
  269. package/scripts/issue/issue-not-before.ts +4 -1
  270. package/scripts/issue/issue-priority-cli.ts +60 -0
  271. package/scripts/issue/issue-priority-raise.ts +55 -0
  272. package/scripts/issue/issue-priority.ts +174 -43
  273. package/scripts/issue/issue-rank.ts +21 -4
  274. package/scripts/issue/issue-read-cli.ts +32 -6
  275. package/scripts/issue/issue-read.ts +37 -12
  276. package/scripts/issue/issue-sample-gate.ts +95 -74
  277. package/scripts/issue/issue-sample-prime.ts +127 -0
  278. package/scripts/issue/issue-sample-required.ts +57 -0
  279. package/scripts/issue/issue-sample-source.ts +12 -11
  280. package/scripts/issue/issue-search.ts +124 -0
  281. package/scripts/issue/issue-state.ts +13 -1
  282. package/scripts/issue/issue-template-section.ts +43 -0
  283. package/scripts/issue/issue-watch-diff.ts +31 -1
  284. package/scripts/issue/issue-watch-hook.ts +11 -2
  285. package/scripts/issue/issue-watch.ts +55 -12
  286. package/scripts/issue/markdown-section.ts +1 -0
  287. package/scripts/issue/measure-declaration.ts +121 -0
  288. package/scripts/issue/measure-rerun-cli.ts +7 -70
  289. package/scripts/issue/plan-consumers-section.ts +22 -1
  290. package/scripts/issue/reproduction-measure.ts +3 -1
  291. package/scripts/issue/size-estimate.ts +90 -0
  292. package/scripts/issue/trusted-measure.ts +84 -0
  293. package/scripts/josh/command-usage.ts +3 -2
  294. package/scripts/josh/durable-state-migrate.ts +105 -0
  295. package/scripts/josh/durable-state.ts +112 -0
  296. package/scripts/josh/josh-aliases.ts +95 -0
  297. package/scripts/josh/josh-command-map.ts +1 -82
  298. package/scripts/josh/josh-commands-agent-hooks.ts +6 -6
  299. package/scripts/josh/josh-commands-ai.ts +5 -5
  300. package/scripts/josh/josh-commands-development.ts +59 -58
  301. package/scripts/josh/josh-commands-issue-write.ts +23 -4
  302. package/scripts/josh/josh-commands-issue.ts +32 -0
  303. package/scripts/josh/josh-commands-lane.ts +87 -4
  304. package/scripts/josh/josh-commands-lint.ts +16 -3
  305. package/scripts/josh/josh-commands-maintenance.ts +15 -0
  306. package/scripts/josh/josh-commands-review.ts +41 -6
  307. package/scripts/josh/josh-commands-ship.ts +56 -8
  308. package/scripts/josh/josh-commands-test-check.ts +80 -0
  309. package/scripts/josh/josh-commands-workflow.ts +1 -1
  310. package/scripts/josh/josh-environment-file.ts +43 -3
  311. package/scripts/josh/josh-in-process.ts +2 -2
  312. package/scripts/josh/josh-logic.ts +5 -3
  313. package/scripts/josh/josh-run.ts +19 -1
  314. package/scripts/josh/run-record-path.ts +15 -0
  315. package/scripts/josh/stamp-file.ts +11 -2
  316. package/scripts/lane/lane-adopt-cli.ts +98 -0
  317. package/scripts/lane/lane-adopt.ts +208 -0
  318. package/scripts/lane/lane-await.ts +5 -8
  319. package/scripts/lane/lane-baseline.ts +123 -0
  320. package/scripts/lane/lane-breakdown-cli.ts +64 -0
  321. package/scripts/lane/lane-breakdown-implement.ts +170 -0
  322. package/scripts/lane/lane-breakdown-laps.ts +107 -0
  323. package/scripts/lane/lane-breakdown-pinned.ts +76 -0
  324. package/scripts/lane/lane-breakdown-text.ts +145 -0
  325. package/scripts/lane/lane-breakdown.ts +239 -0
  326. package/scripts/lane/lane-check-stats.ts +29 -8
  327. package/scripts/lane/lane-child-invocation.ts +9 -2
  328. package/scripts/lane/lane-child-mark-keys.ts +12 -0
  329. package/scripts/lane/lane-child-marker.ts +20 -1
  330. package/scripts/lane/lane-ci-path-stats.ts +73 -0
  331. package/scripts/lane/lane-claim-stats.ts +84 -0
  332. package/scripts/lane/lane-close.ts +8 -3
  333. package/scripts/lane/lane-dispatch-log.ts +3 -3
  334. package/scripts/lane/lane-dispatch.ts +2 -2
  335. package/scripts/lane/lane-gap-stats.ts +7 -6
  336. package/scripts/lane/lane-guard-policy.ts +7 -1
  337. package/scripts/lane/lane-implement-stats.ts +207 -0
  338. package/scripts/lane/lane-implement-subagents.ts +90 -0
  339. package/scripts/lane/lane-investigation-switch.ts +16 -0
  340. package/scripts/lane/lane-ledger-kind.ts +31 -0
  341. package/scripts/lane/lane-ledger-schema-ship.ts +49 -0
  342. package/scripts/lane/lane-ledger-schema.ts +119 -36
  343. package/scripts/lane/lane-ledger-write.ts +257 -0
  344. package/scripts/lane/lane-ledger.ts +31 -177
  345. package/scripts/lane/lane-load.ts +1 -1
  346. package/scripts/lane/lane-measure-cli.ts +176 -59
  347. package/scripts/lane/lane-net-read.ts +44 -0
  348. package/scripts/lane/lane-net-time.ts +212 -0
  349. package/scripts/lane/lane-open.ts +16 -7
  350. package/scripts/lane/lane-output.ts +2 -4
  351. package/scripts/lane/lane-overrun-prompt.ts +147 -0
  352. package/scripts/lane/lane-overrun.ts +274 -0
  353. package/scripts/lane/lane-paths.ts +24 -1
  354. package/scripts/lane/lane-pause-cli.ts +4 -1
  355. package/scripts/lane/lane-paused-spans.ts +97 -0
  356. package/scripts/lane/lane-plan-check.ts +207 -0
  357. package/scripts/lane/lane-plan-cli.ts +41 -0
  358. package/scripts/lane/lane-plan.ts +144 -0
  359. package/scripts/lane/lane-registry.ts +14 -1
  360. package/scripts/lane/lane-relaunch.ts +1 -1
  361. package/scripts/lane/lane-round-one-stats.ts +51 -0
  362. package/scripts/lane/lane-sampler.ts +23 -4
  363. package/scripts/lane/lane-session-action.ts +93 -18
  364. package/scripts/lane/lane-session-broker.ts +49 -21
  365. package/scripts/lane/lane-session-reread-main.ts +14 -0
  366. package/scripts/lane/lane-session-reread.ts +51 -0
  367. package/scripts/lane/lane-ship-lap-stats.ts +74 -0
  368. package/scripts/lane/lane-sleep.ts +58 -0
  369. package/scripts/lane/lane-spent.ts +1 -1
  370. package/scripts/lane/lane-split-stats.ts +28 -0
  371. package/scripts/lane/lane-stage-stats.ts +13 -1
  372. package/scripts/lane/lane-state.ts +41 -9
  373. package/scripts/lane/lane-stats-tables.ts +58 -0
  374. package/scripts/lane/lane-stats.ts +21 -5
  375. package/scripts/lane/lane-stop-stats.ts +51 -19
  376. package/scripts/lane/lane-tail-stats.ts +32 -0
  377. package/scripts/lane/lane-transcript-calls.ts +67 -10
  378. package/scripts/lane/lane-transcript-stats.ts +44 -12
  379. package/scripts/lane/lane-value-cell.ts +72 -0
  380. package/scripts/lane/lane-value.ts +118 -0
  381. package/scripts/lane/lane-withdraw-closed.ts +124 -0
  382. package/scripts/lane/lane-withdraw.ts +108 -31
  383. package/scripts/lane/lane-yield.ts +85 -0
  384. package/scripts/lane/openai-lane-ship.ts +12 -11
  385. package/scripts/lane/openai-lane-supervisor-decision.ts +6 -8
  386. package/scripts/lane/openai-lane-supervisor-owner.ts +5 -8
  387. package/scripts/lane/openai-lane-supervisor.ts +5 -4
  388. package/scripts/lib/poll.ts +38 -9
  389. package/scripts/lib/settled-together.ts +10 -0
  390. package/scripts/lib/shell-quote.ts +21 -0
  391. package/scripts/lib/token-shape.ts +111 -0
  392. package/scripts/lint/eslint-daemon-sources.ts +79 -0
  393. package/scripts/lint/eslint-daemon.ts +45 -6
  394. package/scripts/lint/eslint-warm-cli.ts +7 -0
  395. package/scripts/lint/eslint-warm.ts +66 -0
  396. package/scripts/lint/import-graph.ts +141 -11
  397. package/scripts/lint/lint-gate-scope.ts +2 -2
  398. package/scripts/lint/lint-parallel.ts +54 -6
  399. package/scripts/lint/lint-related.ts +154 -33
  400. package/scripts/lint/rename-cli.ts +179 -0
  401. package/scripts/lint/rename-identifier.ts +192 -0
  402. package/scripts/lint/type-only-imports.ts +58 -0
  403. package/scripts/notify/telegram-notify.ts +12 -3
  404. package/scripts/observations/observation-record.ts +42 -16
  405. package/scripts/observations/observations-flush.ts +32 -9
  406. package/scripts/overrides/overrides-check.ts +9 -1
  407. package/scripts/release/release-approval-prune.ts +7 -1
  408. package/scripts/repo/required-checks-logic.ts +4 -1
  409. package/scripts/repo/ruleset-check.ts +4 -3
  410. package/scripts/report/markdown-shape.ts +28 -0
  411. package/scripts/report/report-format-reference.ts +15 -1
  412. package/scripts/report/report-labels.ts +12 -1
  413. package/scripts/report/report-lint-child-summary.ts +94 -0
  414. package/scripts/report/report-lint-cli.ts +26 -7
  415. package/scripts/report/report-lint.ts +2 -20
  416. package/scripts/review/prose-stale-cli.ts +88 -0
  417. package/scripts/review/prose-stale-scan.ts +152 -0
  418. package/scripts/review/review-attest.ts +73 -23
  419. package/scripts/review/review-brief-cli.ts +68 -19
  420. package/scripts/review/review-brief-paths.ts +22 -0
  421. package/scripts/review/review-brief.ts +14 -7
  422. package/scripts/review/review-comment-carry.ts +47 -0
  423. package/scripts/review/review-comment-round.ts +85 -22
  424. package/scripts/review/review-finding-ledger.ts +109 -7
  425. package/scripts/review/review-finding-text.ts +43 -0
  426. package/scripts/review/review-interaction.ts +11 -0
  427. package/scripts/review/review-lane-handover.ts +68 -0
  428. package/scripts/review/review-lint-cli.ts +57 -0
  429. package/scripts/review/review-lint-findings.ts +92 -0
  430. package/scripts/review/review-lint-sections.ts +80 -0
  431. package/scripts/review/review-lint.ts +221 -0
  432. package/scripts/review/review-prior-findings.ts +102 -0
  433. package/scripts/review/review-readers-scan.ts +4 -2
  434. package/scripts/review/review-record-cli.ts +177 -54
  435. package/scripts/review/review-record-code-round.ts +55 -0
  436. package/scripts/review/review-record-comment.ts +60 -0
  437. package/scripts/review/review-record-interaction.ts +35 -0
  438. package/scripts/review/review-record-pinned.ts +108 -0
  439. package/scripts/review/review-record.ts +25 -1
  440. package/scripts/review/review-reviewed-tree.ts +307 -0
  441. package/scripts/review/review-round-one-closure.ts +62 -0
  442. package/scripts/review/review-round-reuse.ts +106 -0
  443. package/scripts/review/review-round2-cli.ts +91 -29
  444. package/scripts/review/review-round2-skip-comment.ts +55 -0
  445. package/scripts/review/review-round2.ts +24 -8
  446. package/scripts/review/review-stamps.ts +7 -3
  447. package/scripts/review/review-stop-log.ts +20 -6
  448. package/scripts/review/review-stops-cli.ts +2 -1
  449. package/scripts/review/review-submit-cli.ts +101 -0
  450. package/scripts/rules/agent-model.ts +1 -1
  451. package/scripts/rules/bash-triggers.ts +3 -3
  452. package/scripts/rules/concise-reply.ts +88 -0
  453. package/scripts/rules/credential-body.ts +77 -0
  454. package/scripts/rules/decision-oracle.ts +1 -1
  455. package/scripts/rules/delivered-rules.ts +36 -17
  456. package/scripts/rules/destructive-command.ts +3 -11
  457. package/scripts/rules/direct-issue-comment.ts +34 -12
  458. package/scripts/rules/direct-issue-read.ts +191 -0
  459. package/scripts/rules/direct-pr-create.ts +4 -24
  460. package/scripts/rules/early-heartbeat.ts +2 -0
  461. package/scripts/rules/edit-tool.ts +27 -0
  462. package/scripts/rules/file-edit-rows.ts +17 -0
  463. package/scripts/rules/file-rewrite.ts +146 -0
  464. package/scripts/rules/firing-point.ts +3 -2
  465. package/scripts/rules/gh-api.ts +128 -7
  466. package/scripts/rules/gh-repo-selector.ts +84 -0
  467. package/scripts/rules/git-force.ts +2 -2
  468. package/scripts/rules/guard-remedy.ts +14 -5
  469. package/scripts/rules/implementation-cut.ts +61 -28
  470. package/scripts/rules/investigator-foreground.ts +231 -0
  471. package/scripts/rules/issue-citation.ts +15 -60
  472. package/scripts/rules/issue-comments.ts +9 -4
  473. package/scripts/rules/josh-git-bare.ts +1 -1
  474. package/scripts/rules/lane-background.ts +41 -13
  475. package/scripts/rules/lane-direct-tool.ts +107 -9
  476. package/scripts/rules/lane-interactive-ask.ts +4 -1
  477. package/scripts/rules/lane-park.ts +4 -2
  478. package/scripts/rules/lane-spilled-read.ts +90 -0
  479. package/scripts/rules/lane-split-park.ts +1 -1
  480. package/scripts/rules/lane-switch-main.ts +4 -4
  481. package/scripts/rules/last-prompt.ts +20 -47
  482. package/scripts/rules/oracle-consulted.ts +1 -1
  483. package/scripts/rules/permission-guards.ts +5 -1
  484. package/scripts/rules/piped-verification-shapes.ts +110 -0
  485. package/scripts/rules/piped-verification.ts +67 -17
  486. package/scripts/rules/poll-loop.ts +72 -9
  487. package/scripts/rules/pr-merge.ts +5 -5
  488. package/scripts/rules/pre-gate-cut.ts +3 -3
  489. package/scripts/rules/protected-files.ts +88 -24
  490. package/scripts/rules/reply-language.ts +3 -17
  491. package/scripts/rules/reply-prose.ts +34 -0
  492. package/scripts/rules/repo-admin.ts +132 -0
  493. package/scripts/rules/reviewer-foreground.ts +88 -0
  494. package/scripts/rules/round-one-check-related.ts +120 -0
  495. package/scripts/rules/rule-body-guard.ts +5 -15
  496. package/scripts/rules/rule-value-cache.ts +53 -5
  497. package/scripts/rules/rule-value-candidates.ts +84 -0
  498. package/scripts/rules/rule-value-cli.ts +59 -11
  499. package/scripts/rules/rule-value.ts +17 -4
  500. package/scripts/rules/run-tail-rule.ts +1 -1
  501. package/scripts/rules/sed-in-place.ts +196 -0
  502. package/scripts/rules/shell-segments.ts +10 -2
  503. package/scripts/rules/stop-rule-rows.ts +1 -13
  504. package/scripts/rules/stop-rules.ts +33 -58
  505. package/scripts/rules/test-declared-commit.ts +1 -1
  506. package/scripts/rules/third-party-write.ts +110 -23
  507. package/scripts/rules/timed-rerun.ts +1 -1
  508. package/scripts/rules/turn-filings.ts +90 -0
  509. package/scripts/rules/worktree-guard.ts +22 -7
  510. package/scripts/run/board/run-board-ending-reason.ts +6 -2
  511. package/scripts/run/board/run-board-fit.ts +24 -1
  512. package/scripts/run/board/run-board-found.ts +134 -0
  513. package/scripts/run/board/run-board-header.ts +8 -0
  514. package/scripts/run/board/run-board-labels.ts +11 -1
  515. package/scripts/run/board/run-board-lane-time.ts +1 -0
  516. package/scripts/run/board/run-board-link.ts +15 -2
  517. package/scripts/run/board/run-board-notes.ts +7 -1
  518. package/scripts/run/board/run-board-operation.ts +4 -2
  519. package/scripts/run/board/run-board-phase.ts +2 -5
  520. package/scripts/run/board/run-board-plan.ts +1 -1
  521. package/scripts/run/board/run-board-read.ts +46 -1
  522. package/scripts/run/board/run-board-reason.ts +20 -8
  523. package/scripts/run/board/run-board-render-checks.ts +3 -2
  524. package/scripts/run/board/run-board-render-drawn.ts +11 -0
  525. package/scripts/run/board/run-board-render-found.ts +157 -0
  526. package/scripts/run/board/run-board-render-lead.ts +9 -0
  527. package/scripts/run/board/run-board-render-legend.ts +19 -0
  528. package/scripts/run/board/run-board-render-tail.ts +1 -1
  529. package/scripts/run/board/run-board-render.ts +82 -18
  530. package/scripts/run/board/run-board-spin.ts +1 -3
  531. package/scripts/run/board/run-board-status.ts +2 -0
  532. package/scripts/run/board/run-board-tick.ts +39 -10
  533. package/scripts/run/carry/run-carry-ended.ts +3 -2
  534. package/scripts/run/carry/run-carry.ts +3 -2
  535. package/scripts/run/cut/run-cut-cli.ts +33 -133
  536. package/scripts/run/cut/run-cut-digest.ts +152 -0
  537. package/scripts/run/cut/run-cut-handoff.ts +52 -8
  538. package/scripts/run/cut/run-cut-report.ts +24 -3
  539. package/scripts/run/cut/run-cut-resume.ts +129 -0
  540. package/scripts/run/cut/run-cut-review.ts +105 -0
  541. package/scripts/run/cut/run-cut.ts +18 -7
  542. package/scripts/run/detached-launch.ts +1 -1
  543. package/scripts/run/entry/run-entry-cli.ts +88 -19
  544. package/scripts/run/entry/run-entry-spill.ts +83 -0
  545. package/scripts/run/entry/run-entry-stop.ts +5 -1
  546. package/scripts/run/entry/run-entry-trust.ts +91 -0
  547. package/scripts/run/event/run-event-baseline.ts +56 -0
  548. package/scripts/run/event/run-event-filed.ts +9 -3
  549. package/scripts/run/event/run-event-overrun.ts +197 -0
  550. package/scripts/run/event/run-event-render.ts +4 -0
  551. package/scripts/run/event/run-event-stream.ts +25 -3
  552. package/scripts/run/hold/run-stop-notify.ts +3 -8
  553. package/scripts/run/merge/run-merge-cap.ts +57 -0
  554. package/scripts/run/merge/run-merge-cli.ts +66 -23
  555. package/scripts/run/merge/run-merge-outcome.ts +2 -0
  556. package/scripts/run/merge/run-merge-ship.ts +13 -3
  557. package/scripts/run/merge/run-merge-signals.ts +40 -23
  558. package/scripts/run/merge/run-merge-steps.ts +56 -36
  559. package/scripts/run/merge/run-merge-usage-limit.ts +53 -0
  560. package/scripts/run/merge/run-merge-withdrawn.ts +53 -0
  561. package/scripts/run/merge/run-merge.ts +41 -14
  562. package/scripts/run/review/run-review-cli.ts +306 -20
  563. package/scripts/run/review/run-review-early-push.ts +121 -0
  564. package/scripts/run/review/run-review-join-read.ts +94 -0
  565. package/scripts/run/review/run-review-pinned-accepted.ts +126 -0
  566. package/scripts/run/review/run-review-pinned-join.ts +216 -0
  567. package/scripts/run/review/run-review-pinned-steps.ts +383 -0
  568. package/scripts/run/review/run-review-pinned-tree.ts +37 -0
  569. package/scripts/run/review/run-review-pinned.ts +53 -0
  570. package/scripts/run/review/run-review-steps.ts +70 -34
  571. package/scripts/run/review/run-review.ts +26 -6
  572. package/scripts/run/run-parent-cut-hook.ts +3 -4
  573. package/scripts/run/run-prep.ts +1 -0
  574. package/scripts/run/run-stage-read.ts +2 -6
  575. package/scripts/run/run-step-cli.ts +17 -5
  576. package/scripts/run/run-step-lane-plan.ts +27 -0
  577. package/scripts/run/run-step-section.ts +26 -7
  578. package/scripts/run/run-step.ts +5 -2
  579. package/scripts/run/ship/run-ship-args.ts +19 -0
  580. package/scripts/run/ship/run-ship-brokered-review.ts +106 -0
  581. package/scripts/run/ship/run-ship-cli.ts +131 -39
  582. package/scripts/run/ship/run-ship-conflict-probe.ts +59 -0
  583. package/scripts/run/ship/run-ship-detach.ts +10 -4
  584. package/scripts/run/ship/run-ship-handover.ts +106 -0
  585. package/scripts/run/ship/run-ship-infra-retry.ts +16 -1
  586. package/scripts/run/ship/run-ship-lap.ts +63 -0
  587. package/scripts/run/ship/run-ship-next.ts +27 -26
  588. package/scripts/run/ship/run-ship-overlap.ts +8 -3
  589. package/scripts/run/ship/run-ship-place.ts +9 -2
  590. package/scripts/run/ship/run-ship-planned.ts +92 -0
  591. package/scripts/run/ship/run-ship-pre-detach-pinned.ts +197 -0
  592. package/scripts/run/ship/run-ship-pre-detach-timed.ts +7 -8
  593. package/scripts/run/ship/run-ship-pre-detach.ts +77 -23
  594. package/scripts/run/ship/run-ship-preflight.ts +16 -4
  595. package/scripts/run/ship/run-ship-probe.ts +23 -9
  596. package/scripts/run/ship/run-ship-relaunch-cap.ts +43 -7
  597. package/scripts/run/ship/run-ship-relaunch.ts +51 -0
  598. package/scripts/run/ship/run-ship-return.ts +103 -29
  599. package/scripts/run/ship/run-ship-review-reason.ts +24 -0
  600. package/scripts/run/ship/run-ship-review-steps.ts +230 -59
  601. package/scripts/run/ship/run-ship-review.ts +171 -51
  602. package/scripts/run/ship/run-ship-reviewer.ts +138 -42
  603. package/scripts/run/ship/run-ship-scoped.ts +39 -10
  604. package/scripts/run/ship/run-ship-stage.ts +49 -13
  605. package/scripts/run/ship/run-ship-steps.ts +24 -4
  606. package/scripts/run/ship/run-ship-stop-cause.ts +43 -2
  607. package/scripts/run/ship/run-ship-stop-context.ts +50 -13
  608. package/scripts/run/ship/run-ship-sync.ts +171 -30
  609. package/scripts/run/ship/run-ship-synced-scope.ts +178 -0
  610. package/scripts/run/ship/run-ship-wait-cli.ts +145 -0
  611. package/scripts/run/ship/run-ship-waiter.ts +114 -0
  612. package/scripts/run/ship/run-ship.ts +16 -2
  613. package/scripts/run/wake/run-wake-caffeinate.ts +48 -0
  614. package/scripts/run/wake/run-wake-cli.ts +6 -113
  615. package/scripts/run/wake/run-wake-code.ts +53 -11
  616. package/scripts/run/wake/run-wake-driver.ts +5 -3
  617. package/scripts/run/wake/run-wake-finish.ts +137 -0
  618. package/scripts/run/wake/run-wake-loop.ts +5 -3
  619. package/scripts/run/wake/run-wake-ports.ts +11 -3
  620. package/scripts/run/wake/run-wake-session.ts +2 -0
  621. package/scripts/run/wake/run-wake.ts +3 -2
  622. package/scripts/safe-chain/ci-installer-pin.ts +49 -8
  623. package/scripts/safe-chain/project-config.ts +3 -2
  624. package/scripts/security/secretlint-scan.ts +42 -9
  625. package/scripts/security/security-audit-provision-main.ts +9 -1
  626. package/scripts/security/security-audit-provision.ts +1 -1
  627. package/scripts/security/security-audit.ts +21 -1
  628. package/scripts/security/sonar-file.ts +10 -5
  629. package/scripts/split/split-assess.ts +12 -3
  630. package/scripts/split/split-early-notice.ts +116 -0
  631. package/scripts/split/split-measure.ts +20 -2
  632. package/scripts/split/split-resolution.ts +107 -0
  633. package/scripts/test/direct-importers.ts +185 -0
  634. package/scripts/test/pure-files-entries.ts +83 -0
  635. package/scripts/test/related-heavy-tests.ts +81 -0
  636. package/scripts/test/test-no-base.ts +88 -0
  637. package/scripts/test/test-pinned-cache-logic.ts +363 -0
  638. package/scripts/test/test-pinned-cache.ts +337 -0
  639. package/scripts/test/test-pinned-emit.ts +49 -0
  640. package/scripts/test/test-pinned-functions.ts +116 -0
  641. package/scripts/test/test-pinned-ledger.ts +80 -0
  642. package/scripts/test/test-pinned-logic.ts +208 -20
  643. package/scripts/test/test-pinned-main.ts +84 -18
  644. package/scripts/test/test-pinned-pool.ts +73 -0
  645. package/scripts/test/test-pinned-probe.ts +254 -0
  646. package/scripts/test/test-pinned-prune-logic.ts +118 -0
  647. package/scripts/test/test-pinned-reach.ts +194 -0
  648. package/scripts/test/test-pinned-record.ts +196 -0
  649. package/scripts/test/test-pinned-rewire.ts +148 -0
  650. package/scripts/test/test-pinned-spawn.ts +81 -0
  651. package/scripts/test/test-pinned-stage.ts +70 -0
  652. package/scripts/test/test-pinned.ts +258 -47
  653. package/scripts/test/test-reach-cli.ts +61 -0
  654. package/scripts/test/test-read-map.ts +35 -11
  655. package/scripts/test/test-read-recording.ts +17 -5
  656. package/scripts/test/test-read-trace.ts +26 -8
  657. package/scripts/test/test-red-logic.ts +46 -7
  658. package/scripts/test/test-red.ts +34 -14
  659. package/scripts/test/test-related-claim.ts +37 -0
  660. package/scripts/test/test-related-main.ts +4 -1
  661. package/scripts/test/test-related-moved.ts +91 -0
  662. package/scripts/test/test-related-record.ts +43 -23
  663. package/scripts/test/test-related-scope.ts +99 -34
  664. package/scripts/test/test-related-synced.ts +135 -0
  665. package/scripts/test/test-related-touched.ts +84 -0
  666. package/scripts/test/test-related.ts +167 -83
  667. package/scripts/test/test-unit-guard.ts +28 -5
  668. package/scripts/test/unit-durations.ts +41 -0
  669. package/scripts/test/unit-worker-share.ts +5 -4
  670. package/scripts/test/vitest-cache-directory.ts +19 -0
  671. package/scripts/test/vitest-cli-excludes.ts +14 -0
  672. package/scripts/test/vitest-failure-summary.ts +111 -0
  673. package/scripts/test/whole-suite-files.ts +12 -1
  674. package/scripts/time-runtime/time-batch-guard.ts +42 -4
  675. package/scripts/time-runtime/time-bundle-call.ts +1 -1
  676. package/scripts/time-runtime/time-bundles.ts +36 -5
  677. package/scripts/time-runtime/time-markers.ts +2 -2
  678. package/scripts/time-runtime/time-search-call.ts +38 -0
  679. package/scripts/time-runtime/time-shell.ts +1 -1
  680. package/scripts/time-runtime/time-transcript-line.ts +17 -5
  681. package/scripts/version/development-engines-verdict.ts +124 -0
  682. package/scripts/version/latest-corepack.ts +2 -0
  683. package/templates/gitignore +2 -0
  684. package/templates/workflows/ci.yml +41 -0
  685. package/dist/hooks/chunk-AZX4FGQ3.js +0 -207
  686. package/dist/hooks/chunk-PT5R6UJD.js +0 -7131
  687. package/dist/hooks/chunk-TUIFS5E7.js +0 -72
  688. package/dist/hooks/chunk-YCDOIR3I.js +0 -3456
  689. package/scripts/cost-runtime/context-cut-threshold.ts +0 -11
  690. package/scripts/rules/filing-cap.ts +0 -139
@@ -1,16 +1,20 @@
1
1
  import {
2
- CONTEXT_CUT_THRESHOLD,
3
2
  bash_triggers,
3
+ concise_reply,
4
4
  cost_cli,
5
5
  cost_verdict,
6
+ delegation_policy,
6
7
  doctor_consumer,
8
+ duplicate_read_outcome,
7
9
  epic_busy,
8
- filing_cap,
9
10
  git_argv,
10
11
  hook_decision,
11
12
  interactive_ask,
13
+ investigation_reads,
12
14
  lane_background,
15
+ lane_guard_policy,
13
16
  lane_park,
17
+ lane_plan,
14
18
  pre_gate_cut,
15
19
  project_checks,
16
20
  repo_party,
@@ -20,18 +24,19 @@ import {
20
24
  run_stage,
21
25
  run_tail_rule,
22
26
  shell_segments,
27
+ stop_rules,
23
28
  tail_commands
24
- } from "./chunk-CWMBGKYW.js";
29
+ } from "./chunk-5PM2JRGV.js";
25
30
  import {
26
- core_weights,
27
31
  cost_blocks,
28
32
  time_batch_guard,
29
- time_bundle_call,
30
33
  time_density_hook,
31
34
  time_hook_transcript,
35
+ time_markers,
32
36
  time_shell,
33
- time_spans
34
- } from "./chunk-PT5R6UJD.js";
37
+ time_spans,
38
+ time_transcript_line
39
+ } from "./chunk-Z6DQJKH3.js";
35
40
  import {
36
41
  agent_role_profile,
37
42
  agent_session_role,
@@ -41,20 +46,14 @@ import {
41
46
  lane_registry,
42
47
  run_headless,
43
48
  run_hold,
44
- run_preflight
45
- } from "./chunk-YCDOIR3I.js";
49
+ run_preflight,
50
+ test_declared_logic,
51
+ token_shape
52
+ } from "./chunk-3GKE653K.js";
46
53
  import {
47
54
  PACKAGE_JSON,
48
- PORCELAIN_FLAG,
49
- UNTRACKED_FILES_FLAG,
50
55
  ancestor_directories,
51
- cost_transcript,
52
- figure_format,
53
56
  file_reader,
54
- git_command,
55
- git_common_directory,
56
- git_diff_reads,
57
- git_spawn_sync,
58
57
  issue_cite,
59
58
  issue_number_shape,
60
59
  json_value,
@@ -65,533 +64,56 @@ import {
65
64
  run_event_stream,
66
65
  run_event_stream_emit,
67
66
  session_cite
68
- } from "./chunk-WR2KQAYZ.js";
67
+ } from "./chunk-7SIUNQXK.js";
69
68
  import {
70
69
  MS_PER_MINUTE,
71
70
  MS_PER_SECOND,
71
+ PORCELAIN_FLAG,
72
+ UNTRACKED_FILES_FLAG,
72
73
  error_text,
74
+ git_command,
75
+ git_common_directory,
76
+ git_diff_reads,
77
+ git_spawn_sync,
73
78
  platform_temporary,
74
79
  stamp_file
75
- } from "./chunk-AZX4FGQ3.js";
76
-
77
- // scripts/delegation/duplicate-read-guard.ts
78
- import { statSync } from "node:fs";
79
-
80
- // scripts/lane/lane-guard-policy.ts
81
- var DEFAULT_MODE = "refuse";
82
- var OFF_MODE = "off";
83
- var LANE_GUARD_POLICY = [
84
- {
85
- id: "investigation",
86
- mode_in_lane_child: "refuse",
87
- because: "the notice was measured not to move the reading \u2014 52% of lane cost stayed in main-line exploration \u2014 and the refusal fires once per accumulation, a round trip the child survives as it survives the rule row, so the child is refused like the main line"
88
- },
89
- {
90
- id: "batching",
91
- mode_in_lane_child: "off",
92
- because: "a refusal ends a child turn and every advisory notice was measured not to move the call density, so the guard is off and the density stays readable through `josh time:density`"
93
- },
94
- {
95
- id: "duplicate-read",
96
- mode_in_lane_child: "notice",
97
- because: "a refusal ends a child turn, yet unchanged re-reads are measured in lane children above all, so the child is nudged to scroll up to the earlier read by a notice rather than killed"
98
- },
99
- {
100
- id: "rule",
101
- mode_in_lane_child: "refuse",
102
- because: "it carries the lane-only rules a child depends on (`pre-gate-cut`, `implementation-cut`, `lane-park`) and the safety rules it must still obey, and a notice was rejected despite the collateral a refusal costs a batched sibling, because a cut carried as prose was measured never to fire"
103
- }
104
- ];
105
- var KNOWN_GUARD_IDS = LANE_GUARD_POLICY.map((entry) => entry.id);
106
- function entry_for(id) {
107
- return LANE_GUARD_POLICY.find((entry) => entry.id === id.trim());
108
- }
109
- function mode_in_lane_child(id) {
110
- return entry_for(id)?.mode_in_lane_child ?? DEFAULT_MODE;
111
- }
112
- function mode_here(id, directory = process.cwd(), source = process.env) {
113
- const lane_mode = mode_in_lane_child(id);
114
- if (lane_mode === DEFAULT_MODE) return DEFAULT_MODE;
115
- return lane_child_marker.is_child_of(directory, source) ? lane_mode : DEFAULT_MODE;
116
- }
117
- function is_suppressed_here(id, directory = process.cwd(), source = process.env) {
118
- return mode_here(id, directory, source) === OFF_MODE;
119
- }
120
- var lane_guard_policy = {
121
- LANE_GUARD_POLICY,
122
- KNOWN_GUARD_IDS,
123
- entry_for,
124
- mode_in_lane_child,
125
- mode_here,
126
- is_suppressed_here
127
- };
128
-
129
- // scripts/delegation/investigation-reads.ts
130
- import path2 from "node:path";
131
-
132
- // scripts/document/capped-print-part.ts
133
- import path from "node:path";
134
- var DIRECTORY_PREFIX = "josh-capped-print-";
135
- var PART_PREFIX = "part-";
136
- var PART_SUFFIX = ".md";
137
- var PART_PATTERN = /^part-[1-9]\d*\.md$/u;
138
- function part_name(index) {
139
- return `${PART_PREFIX}${String(index + 1)}${PART_SUFFIX}`;
140
- }
141
- function is_part_file(absolute) {
142
- const parent = path.basename(path.dirname(absolute));
143
- return parent.startsWith(DIRECTORY_PREFIX) && PART_PATTERN.test(path.basename(absolute));
144
- }
145
- var capped_print_part = { DIRECTORY_PREFIX, part_name, is_part_file };
146
-
147
- // scripts/delegation/delegation-policy.ts
148
- var DELEGATE_VERDICT = "delegate";
149
- var KEEP_VERDICT = "keep";
150
- var INVESTIGATION_FILE_THRESHOLD = 3;
151
- var INVESTIGATION_SEARCH_TURN_THRESHOLD = 3;
152
- var INVESTIGATOR_AGENT = "investigator";
153
- var INVESTIGATION_LIMIT = `${String(INVESTIGATION_FILE_THRESHOLD)} files read and not edited, or ${String(INVESTIGATION_SEARCH_TURN_THRESHOLD)} search turns, since the last delegated unit`;
154
- var INVESTIGATION_STEP = `Dispatch the \`${INVESTIGATOR_AGENT}\` agent (\`kit:${INVESTIGATOR_AGENT}\` in a consumer); it returns \`file:line\` citations, never the file text.`;
155
- var DELEGATABLE_STEPS = [
156
- {
157
- name: "gate-fix",
158
- does: "apply the fixes the verification gate already named \u2014 a lint rule, a type error, a spelling the dictionary rejects",
159
- verifier: "`pnpm josh gate` is re-run; a wrong fix fails it again, and the failure names the file"
160
- },
161
- {
162
- name: "epic-child",
163
- // One row, two entry points: an epic's child and one named issue of a `backlogrun` are the same
164
- // unit — same brief, same summary, same verifier. A second row for the batch would be the clone
165
- // `CLAUDE.md` prohibits.
166
- does: "run one child of a batch end to end in an isolated unit \u2014 an epic's child or one named issue of a `backlogrun` alike \u2014 plan, verification gate, PR, merge \u2014 and return only its summary to the parent loop",
167
- verifier: "the parent reads the child's state from GitHub with `pnpm josh issue:state`, not from the summary; a child reported done but not merged is still open, which is the failure showing rather than a run continuing, and its own gate, `/code-review` and CI ran inside the unit before `followup` would touch the PR"
168
- },
169
- {
170
- name: "followup-filing",
171
- // The late-run follow-up filing chain — `issue:file` (which scouts, lints, labels and runs
172
- // epic:bundle itself) → epic --add — is a dependency chain the batching guard
173
- // correctly keeps one call per step, and what makes it expensive is the ~340k context it runs in
174
- // at the tail of a run, not the step count. A fresh unit runs the same
175
- // chain at a small context, and the review round cap already composed the finding text, so what
176
- // the unit is handed is mechanical: run the commands and act on their deterministic answers. This
177
- // is not the batching guard's business — delegation only moves the chain to a cheaper context.
178
- does: "run the late-run follow-up filing chain in an isolated unit \u2014 issue:file (scout, lint, labels, epic:bundle), then epic --add \u2014 given the finding text the parent already composed, and return the new Issue number and the epic:bundle answer",
179
- verifier: "the parent reads the filed Issue from GitHub with `pnpm josh issue:state <new>`, not the unit's summary; a follow-up reported filed but not created is still absent, the way `epic-child`'s reported-done-but-not-merged child is still open. The finding text came from the parent and `issue:file`'s steps are deterministic, so the unit's work is the mechanical execution `issue:state` confirms"
180
- },
181
- {
182
- name: "implementation-unit",
183
- // The Step 0 change list already decided *what* to change — that design stays in the main line,
184
- // which is why `design` is rejected below. What is delegated is the *writing* of one unit of that
185
- // list, and only a unit whose files are disjoint from every other dispatched unit: two subagents
186
- // editing one file in parallel race on it. `pnpm josh fanout` confirms that disjointness
187
- // mechanically, so the split is read off the file sets rather than judged.
188
- does: "implement one file-disjoint unit of the Step 0 change list in an isolated subagent \u2014 the files it edits shared with no other dispatched unit, confirmed by `pnpm josh fanout` \u2014 and return the edited files, never a summary standing in for the diff; the main line keeps integration and the verification gate. Units that share a file, or an Issue that will not split, stay serial",
189
- verifier: "the parent runs the whole integrated change through `pnpm josh gate` and a `/code-review` subagent it would run anyway \u2014 a unit's mistake fails the same gate and review a serial edit passes through, so delegating writes nothing the backstop does not already catch, and the `pnpm josh fanout` disjointness removes the one failure parallelism adds, two units colliding on a file"
190
- },
191
- {
192
- name: "survey",
193
- does: "read across many files and report where something appears \u2014 every reference to a symbol, which documents carry a marker",
194
- verifier: "the reported locations are checked directly; a fabricated or missed location does not survive one `grep` of what it claimed"
195
- },
196
- {
197
- name: "investigation",
198
- // Not `survey`: that row reports *where* something appears and is checked by one `grep`, while
199
- // this one reports *how the subject works* and is checked by opening the lines it cited. The two
200
- // verifiers differ, so widening `survey` would put both in one field and lose the boundary below.
201
- // **No ordinal.** `${N}rd` would print `1rd` for any other threshold, past a test that only looks
202
- // for the numeral, so the count is phrased as a plain "N or more".
203
- does: `read the Issue's subject to find out how it currently works \u2014 once ${String(INVESTIGATION_FILE_THRESHOLD)} or more files the run will not edit have to be read, or ${String(INVESTIGATION_SEARCH_TURN_THRESHOLD)} or more search turns run without an edit \u2014 and return the conclusion plus the \`file:line\` citations that support it, never the file text, which would put the cost back where it was. A throwaway probe script is written, run and deleted inside the unit, which returns its output alone`,
204
- verifier: "the parent opens the cited lines; a conclusion those lines do not support fails there, and reading a handful of cited regions costs far less than redoing the reading. The unit reports what the code does and never what to change \u2014 deciding that is `diagnosis`, which stays kept, so a unit returning a root cause would be delegating the rejected row under this name"
205
- },
206
- {
207
- name: "lane-failure-investigation",
208
- // The `backlogrun` parent's counterpart of `investigation`. The parent
209
- // orchestrates and never implements, yet it read a failed lane's logs, records and sources in its
210
- // own context — about 150 grep / cat / sed calls on 2026-10-02 — and every later request of the
211
- // batch re-read them. The verifier is `investigation`'s, and so is the boundary: the unit reports
212
- // what happened, and what the parent does about it (park, re-dispatch, file) stays its own call.
213
- does: "find out why a lane's child stopped, failed or conflicted \u2014 its output log, run records, PR checks and the files they name \u2014 and return the conclusion plus the `file:line` or log-line citations that support it, never the text read, so the batch parent's context stays the size its orchestration needs",
214
- verifier: "the parent opens the cited lines and re-reads the child with `pnpm josh issue:state`; a conclusion those do not support fails there. The unit reports what happened and never what to do \u2014 parking, re-dispatching or filing stays with the parent, so a wrong account is caught before it is acted on"
215
- }
216
- ];
217
- var REJECTED_STEPS = [
218
- {
219
- name: "notify-body",
220
- because: "no verifier. A notification with a wrong body is sent and read as though it were right; nothing in the run disagrees with it afterwards"
221
- },
222
- {
223
- name: "issue-comment",
224
- because: "no verifier. A decision log or completion comment that says the wrong thing is the record, so there is nothing left to check it against"
225
- },
226
- {
227
- name: "status-read",
228
- because: "a misread routes the run to the wrong child, and no later step disagrees. Running the status command is already cheap; what is expensive is acting on it, which is not this step"
229
- },
230
- {
231
- name: "diagnosis",
232
- because: "a wrong root cause produces a fix that passes the gate and leaves the defect"
233
- },
234
- {
235
- name: "design",
236
- because: "the cost of a wrong design is paid by every step after it"
237
- },
238
- {
239
- name: "split-assessment",
240
- because: "a missed split widens one Issue into a batch nobody authorized"
241
- },
242
- {
243
- name: "review",
244
- because: "the review is the last thing between a defect and a merge; a cheaper one finds less"
245
- }
246
- ];
247
- function find_step(name) {
248
- return DELEGATABLE_STEPS.find((step) => step.name === name.trim());
249
- }
250
- function verdict_for(name) {
251
- return find_step(name) === void 0 ? KEEP_VERDICT : DELEGATE_VERDICT;
252
- }
253
- function rejection_for(name) {
254
- return REJECTED_STEPS.find((step) => step.name === name.trim());
255
- }
256
- function reason_for(name) {
257
- const step = find_step(name);
258
- if (step !== void 0) return `delegatable: ${step.verifier}`;
259
- const rejected = rejection_for(name);
260
- if (rejected !== void 0) return `kept deliberately: ${rejected.because}`;
261
- return "kept by default: not on the delegatable list, and the list is the whole of it";
262
- }
263
- var delegation_policy = {
264
- DELEGATE_VERDICT,
265
- INVESTIGATION_FILE_THRESHOLD,
266
- INVESTIGATION_LIMIT,
267
- INVESTIGATION_SEARCH_TURN_THRESHOLD,
268
- INVESTIGATION_STEP,
269
- INVESTIGATOR_AGENT,
270
- KEEP_VERDICT,
271
- DELEGATABLE_STEPS,
272
- REJECTED_STEPS,
273
- find_step,
274
- verdict_for,
275
- reason_for
276
- };
277
-
278
- // scripts/delegation/investigation-reads.ts
279
- var READ_TOOLS = /* @__PURE__ */ new Set(["Read"]);
280
- var DELEGATION_TOOLS = /* @__PURE__ */ new Set(["Agent", "Task"]);
281
- var CONTENT_READ_COMMANDS = [
282
- "bat",
283
- "cat",
284
- "head",
285
- "less",
286
- "more",
287
- "nl",
288
- "sed",
289
- "tail"
290
- ];
291
- var CONTENT_READ_LABELS = new Set(
292
- CONTENT_READ_COMMANDS.map((command) => time_shell.bash_label(command))
293
- );
294
- var SEARCH_COMMANDS = ["fd", "find", "grep", "rg"];
295
- var SEARCH_LABELS = new Set(
296
- SEARCH_COMMANDS.map((command) => time_shell.bash_label(command))
297
- );
298
- var { INVESTIGATOR_AGENT: INVESTIGATOR_AGENT2 } = delegation_policy;
299
- var REASON = `\u26D4 investigation: ${delegation_policy.INVESTIGATION_LIMIT}. ${delegation_policy.INVESTIGATION_STEP} Keep main-line reads to files you will edit; every read-only search turn counts. \`.claude/skills/workflow-commands/delegation.md\``;
300
- var INSTRUCTION_PATHS = ["prompts", ".claude/skills"];
301
- var INSTRUCTION_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "AGENTS.md", "GEMINI.md"]);
302
- var REPOSITORY_ROOT = process.cwd();
303
- function resolved(target) {
304
- return path2.resolve(REPOSITORY_ROOT, target);
305
- }
306
- function repository_relative(absolute) {
307
- return path2.relative(REPOSITORY_ROOT, absolute);
308
- }
309
- function is_instruction_document(target) {
310
- const relative = repository_relative(resolved(target));
311
- const segments = relative.split(path2.sep);
312
- return INSTRUCTION_FILES.has(segments.at(-1) ?? "") || INSTRUCTION_PATHS.some((prefix) => relative.startsWith(`${prefix}${path2.sep}`));
313
- }
314
- var SESSION_TEMP_SEGMENT = /^claude-\d+$/u;
315
- var SESSION_STATE_SEGMENTS = cost_transcript.TRANSCRIPT_ROOT.split(path2.sep);
316
- var HARNESS_OUTPUT_DIRECTORY = "tasks";
317
- var HARNESS_OUTPUT_EXTENSION = ".output";
318
- var FILE_SEGMENT_INDEX = -1;
319
- var PARENT_SEGMENT_INDEX = -2;
320
- function has_state_root(segments, index) {
321
- return SESSION_STATE_SEGMENTS.every((segment, offset) => segments[index + offset] === segment);
322
- }
323
- function is_harness_output(segments) {
324
- return segments.some((segment) => SESSION_TEMP_SEGMENT.test(segment)) && segments.at(PARENT_SEGMENT_INDEX) === HARNESS_OUTPUT_DIRECTORY && (segments.at(FILE_SEGMENT_INDEX) ?? "").endsWith(HARNESS_OUTPUT_EXTENSION);
325
- }
326
- function is_outside_repository(absolute) {
327
- return repository_relative(absolute).startsWith("..");
328
- }
329
- function is_session_artifact(target) {
330
- const absolute = resolved(target);
331
- const segments = absolute.split(path2.sep);
332
- if (!is_outside_repository(absolute)) return false;
333
- return segments.some((_segment, index) => has_state_root(segments, index)) || is_harness_output(segments) || capped_print_part.is_part_file(absolute);
334
- }
335
- function is_subject_file(target) {
336
- return !is_instruction_document(target) && !is_session_artifact(target);
337
- }
338
- function is_nameable_file(target) {
339
- return !target.includes("*") && !target.includes("?");
340
- }
341
- function is_content_read(label) {
342
- return READ_TOOLS.has(label) || CONTENT_READ_LABELS.has(label);
343
- }
344
- function subject_targets(targets) {
345
- return targets.filter((target) => is_nameable_file(target) && is_subject_file(target)).map((target) => resolved(target));
346
- }
347
- function call_targets(call) {
348
- return subject_targets(time_bundle_call.call_facts(call.name, call.input).targets);
349
- }
350
- function delegation_instant(span) {
351
- return span === void 0 ? hook_decision.NEVER_MS : span.ended_ms;
352
- }
353
- function is_failed_outcome(span) {
354
- return span.outcome === time_spans.FAILED_OUTCOME;
355
- }
356
- function edited_files(spans) {
357
- const written = spans.filter((span) => !is_failed_outcome(span)).flatMap((span) => subject_targets(span.writes));
358
- return new Set(written);
359
- }
360
- function add_all(pending, targets, at_ms) {
361
- for (const target of targets) if (!pending.has(target)) pending.set(target, at_ms);
362
- }
363
- function is_read_only_search(span) {
364
- return SEARCH_LABELS.has(span.label) && span.is_bundleable && !span.may_write;
365
- }
366
- function search_turn_key(searches, span) {
367
- if (span.message_id !== time_spans.NO_MESSAGE_ID) return span.message_id;
368
- return `${time_spans.NO_MESSAGE_ID}#${String(searches.size)}`;
369
- }
370
- function apply_search(searches, span) {
371
- if (span.is_writing) {
372
- searches.clear();
373
- return;
374
- }
375
- const turn = search_turn_key(searches, span);
376
- if (is_read_only_search(span) && !searches.has(turn)) searches.set(turn, span.ended_ms);
377
- }
378
- function apply_span(accumulation, span, edited) {
379
- if (DELEGATION_TOOLS.has(span.label)) {
380
- accumulation.pending.clear();
381
- accumulation.searches.clear();
382
- return;
383
- }
384
- if (is_failed_outcome(span)) return;
385
- apply_search(accumulation.searches, span);
386
- if (!is_content_read(span.label)) return;
387
- const fresh = subject_targets(span.targets).filter((target) => !edited.has(target));
388
- add_all(accumulation.pending, fresh, span.ended_ms);
389
- }
390
- function last_delegation_ms(spans) {
391
- return delegation_instant(spans.findLast((span) => DELEGATION_TOOLS.has(span.label)));
392
- }
393
- function pending_after(pending, since_ms) {
394
- return [...pending.values()].filter((at_ms) => at_ms > since_ms).length;
395
- }
396
- function tally_of(text, since_ms = hook_decision.NEVER_MS) {
397
- const { spans, started_ms } = time_spans.parse_timeline(text);
398
- const edited = edited_files(spans);
399
- const accumulation = { pending: /* @__PURE__ */ new Map(), searches: /* @__PURE__ */ new Map() };
400
- for (const span of spans) apply_span(accumulation, span, edited);
401
- return {
402
- pending: [...accumulation.pending.keys()],
403
- pending_since: pending_after(accumulation.pending, since_ms),
404
- reset_ms: last_delegation_ms(spans),
405
- window_start_ms: started_ms,
406
- searches: accumulation.searches.size,
407
- searches_since: pending_after(accumulation.searches, since_ms),
408
- edited
409
- };
410
- }
411
- function projected_count(pending, call, edited = /* @__PURE__ */ new Set()) {
412
- return (/* @__PURE__ */ new Set([...pending, ...call_targets(call).filter((target) => !edited.has(target))])).size;
413
- }
414
- function is_at_threshold(pending_count) {
415
- return pending_count + 1 >= delegation_policy.INVESTIGATION_FILE_THRESHOLD;
416
- }
417
- function is_at_search_threshold(search_count) {
418
- return search_count + 1 >= delegation_policy.INVESTIGATION_SEARCH_TURN_THRESHOLD;
419
- }
420
- function has_cleared_the_disarm(tally, refused_at_ms) {
421
- return tally.reset_ms > refused_at_ms || refused_at_ms < tally.window_start_ms;
422
- }
423
- function is_rearmed(tally, refused_at_ms) {
424
- if (refused_at_ms === hook_decision.NEVER_MS || has_cleared_the_disarm(tally, refused_at_ms)) {
425
- return true;
426
- }
427
- return is_at_threshold(tally.pending_since) || is_at_search_threshold(tally.searches_since);
428
- }
429
- function is_unit_transcript(transcript) {
430
- return transcript.split(path2.sep).includes(cost_transcript.UNIT_DIRECTORY);
431
- }
432
- function is_unit_run(run) {
433
- return run !== void 0 && is_unit_transcript(run.transcript);
434
- }
435
- function is_read_only_command(call, labels) {
436
- if (call.name !== cost_blocks.BASH_TOOL) return false;
437
- const label = time_shell.bash_label(time_shell.bash_command(call.input));
438
- return labels.has(label) && time_batch_guard.is_read_only_call(call);
439
- }
440
- function is_search_call(call) {
441
- return is_read_only_command(call, SEARCH_LABELS);
442
- }
443
- function is_refusable_call(call) {
444
- if (is_search_call(call)) return true;
445
- if (call_targets(call).length === 0) return false;
446
- if (READ_TOOLS.has(call.name)) return true;
447
- return is_read_only_command(call, CONTENT_READ_LABELS);
448
- }
449
- function is_growing_past_threshold(tally, call) {
450
- if (is_search_call(call)) return is_at_search_threshold(tally.searches);
451
- if (!is_at_threshold(tally.pending.length)) return false;
452
- return projected_count(tally.pending, call, tally.edited) > tally.pending.length;
453
- }
454
- function should_block(text, call, refused_at_ms, run) {
455
- if (!is_refusable_call(call) || is_unit_run(run)) return false;
456
- const tally = tally_of(text, refused_at_ms);
457
- return is_growing_past_threshold(tally, call) && is_rearmed(tally, refused_at_ms);
458
- }
459
- var investigation_reads = {
460
- DELEGATION_TOOLS,
461
- INVESTIGATOR_AGENT: INVESTIGATOR_AGENT2,
462
- READ_TOOLS,
463
- REASON,
464
- is_at_threshold,
465
- is_instruction_document,
466
- is_rearmed,
467
- is_refusable_call,
468
- is_session_artifact,
469
- is_subject_file,
470
- projected_count,
471
- resolved,
472
- should_block,
473
- tally_of
474
- };
475
-
476
- // scripts/delegation/duplicate-read-guard.ts
477
- var SWITCH_ENV_KEY = "JOSH_DUPLICATE_READ_GUARD";
478
- var LANE_GUARD_ID = "duplicate-read";
479
- var STAMP_PREFIX = "josh-duplicate-read-guard-";
480
- var NOTICE_STAMP_PREFIX = "josh-duplicate-read-notice-";
481
- var READ_TOOL = "Read";
482
- var GLOB_CHARACTERS = /[*?]/u;
483
- var REASON2 = `\u26D4 duplicate read: this file was already read in this session and has not changed since (its mtime predates that read), so reading it again returns nothing new. Scroll up to the earlier result instead of re-reading it. A read with a new offset/limit, or of a file that has since changed, is allowed; set \`JOSH_DUPLICATE_READ_GUARD=off\` to disable this guard.`;
484
- var NOTICE = `\u26A0 duplicate read: this file was already read in this session and has not changed since, so re-reading it returns nothing new \u2014 scroll up to the earlier result instead of re-reading it. Set \`JOSH_DUPLICATE_READ_GUARD=off\` to silence this.`;
485
- function is_paginated(input) {
486
- return input["offset"] !== void 0 || input["limit"] !== void 0;
487
- }
488
- function names_one_file(file_path) {
489
- return typeof file_path === "string" && file_path !== "" && !GLOB_CHARACTERS.test(file_path);
490
- }
491
- function read_target(call) {
492
- if (call.name !== READ_TOOL || !json_value.is_record(call.input)) return void 0;
493
- const { input } = call;
494
- if (is_paginated(input) || !names_one_file(input["file_path"])) return void 0;
495
- return investigation_reads.resolved(input["file_path"]);
496
- }
497
- function is_refusable_call2(call) {
498
- return read_target(call) !== void 0;
499
- }
500
- function is_successful_read(span) {
501
- const is_read2 = investigation_reads.READ_TOOLS.has(span.label);
502
- return is_read2 && span.outcome !== time_spans.FAILED_OUTCOME;
503
- }
504
- function reads_target(span, target) {
505
- return span.targets.some((one) => investigation_reads.resolved(one) === target);
506
- }
507
- function last_read_ms(spans, target) {
508
- const times = spans.filter((span) => is_successful_read(span) && reads_target(span, target)).map((span) => span.ended_ms);
509
- return times.length === 0 ? hook_decision.NEVER_MS : Math.max(...times);
510
- }
511
- function file_mtime_ms(target) {
512
- try {
513
- return statSync(target).mtimeMs;
514
- } catch {
515
- return void 0;
516
- }
517
- }
518
- function read_window(tail, target) {
519
- const { spans, started_ms } = time_spans.parse_timeline(tail);
520
- return { last_ms: last_read_ms(spans, target), window_start_ms: started_ms };
521
- }
522
- function is_unchanged_reread(last_ms, mtime) {
523
- if (mtime === void 0 || last_ms === hook_decision.NEVER_MS) return false;
524
- return mtime < last_ms;
525
- }
526
- function is_rearmed2(window, fired_at_ms) {
527
- if (fired_at_ms === hook_decision.NEVER_MS || fired_at_ms < window.window_start_ms) return true;
528
- return window.last_ms > fired_at_ms;
529
- }
530
- function is_redundant_reread(tail, call, fired_at_ms) {
531
- const target = read_target(call);
532
- if (target === void 0) return false;
533
- const window = read_window(tail, target);
534
- if (!is_unchanged_reread(window.last_ms, file_mtime_ms(target))) return false;
535
- return is_rearmed2(window, fired_at_ms);
536
- }
537
- function duplicate_mode() {
538
- return lane_guard_policy.mode_here(LANE_GUARD_ID);
539
- }
540
- function is_candidate(call) {
541
- return is_refusable_call2(call) && duplicate_mode() !== "off";
542
- }
543
- function should_block_here(tail, call, refused_at_ms) {
544
- if (duplicate_mode() !== "refuse") return false;
545
- return is_redundant_reread(tail, call, refused_at_ms);
546
- }
547
- function should_notify_here(tail, call, notified_at_ms) {
548
- if (duplicate_mode() !== "notice") return false;
549
- return is_redundant_reread(tail, call, notified_at_ms);
550
- }
551
- var GUARD = hook_decision.create_transcript_guard({
552
- prefix: STAMP_PREFIX,
553
- switch_key: SWITCH_ENV_KEY,
554
- is_candidate,
555
- should_block: should_block_here,
556
- reason: REASON2,
557
- notify: {
558
- prefix: NOTICE_STAMP_PREFIX,
559
- should_notify: should_notify_here,
560
- text: () => NOTICE
561
- }
562
- });
563
- var duplicate_read_refusal = GUARD.refusal;
564
- var duplicate_read_outcome = GUARD.outcome;
565
- var { is_enabled, refusal_path } = GUARD;
566
- var notice_refusal_path = hook_decision.create_refusal_stamp(NOTICE_STAMP_PREFIX).path;
567
- var { deny_envelope, DISABLED_VALUES } = hook_decision;
80
+ } from "./chunk-45CJJOO7.js";
568
81
 
569
82
  // scripts/delegation/investigation-guard.ts
570
- var SWITCH_ENV_KEY2 = "JOSH_INVESTIGATION_GUARD";
571
- var STAMP_PREFIX2 = "josh-investigation-guard-";
572
- function should_block2(tail, call, refused_at_ms, run) {
83
+ var SWITCH_ENV_KEY = "JOSH_INVESTIGATION_GUARD";
84
+ var STAMP_PREFIX = "josh-investigation-guard-";
85
+ function should_block(tail, call, refused_at_ms2, run) {
573
86
  if (agent_session_role.is_reviewer() || lane_guard_policy.mode_here("investigation") !== "refuse") {
574
87
  return false;
575
88
  }
576
- return investigation_reads.should_block(tail, call, refused_at_ms, run);
89
+ return investigation_reads.should_block(tail, call, refused_at_ms2, run);
577
90
  }
578
- var GUARD2 = hook_decision.create_transcript_guard({
579
- prefix: STAMP_PREFIX2,
580
- switch_key: SWITCH_ENV_KEY2,
91
+ var GUARD = hook_decision.create_transcript_guard({
92
+ prefix: STAMP_PREFIX,
93
+ switch_key: SWITCH_ENV_KEY,
581
94
  is_candidate: investigation_reads.is_refusable_call,
582
- should_block: should_block2,
95
+ should_block,
583
96
  reason: investigation_reads.REASON
584
97
  });
585
- var investigation_refusal = GUARD2.refusal;
586
- var investigation_outcome = GUARD2.outcome;
587
- var { is_enabled: is_enabled2, refusal_path: refusal_path2 } = GUARD2;
588
- var { deny_envelope: deny_envelope2, load_environment_file, DISABLED_VALUES: DISABLED_VALUES2 } = hook_decision;
98
+ var investigation_refusal = GUARD.refusal;
99
+ var investigation_outcome = GUARD.outcome;
100
+ var { is_enabled, refusal_path } = GUARD;
101
+ var { deny_envelope, load_environment_file, DISABLED_VALUES } = hook_decision;
102
+ var STAMP = hook_decision.create_refusal_stamp(STAMP_PREFIX);
103
+ function will_refuse(tail, call, run) {
104
+ if (!is_enabled() || !investigation_reads.is_refusable_call(call)) return false;
105
+ return should_block(tail, call, STAMP.last_ms(STAMP.path(run.transcript)), run);
106
+ }
589
107
 
590
108
  // scripts/issue/issue-watch-hook.ts
591
109
  var POLL_INTERVAL_MS = 18e4;
592
110
  var POLL_TIMEOUT_MS = 5e3;
593
111
  function is_new_to_run(record2) {
594
- return record2?.pending !== void 0 && record2.pending !== record2.shown;
112
+ const news = record2 === void 0 ? void 0 : issue_watch.news_of(record2);
113
+ return news !== void 0 && news !== record2?.shown;
114
+ }
115
+ function message_of(record2) {
116
+ return record2.pending === void 0 ? record2.notice ?? "" : issue_watch_gate.unread_message(record2);
595
117
  }
596
118
  async function new_update_reason(now) {
597
119
  const record2 = await issue_watch.poll({
@@ -601,7 +123,7 @@ async function new_update_reason(now) {
601
123
  });
602
124
  if (!is_new_to_run(record2)) return void 0;
603
125
  issue_watch.mark_shown(record2);
604
- return issue_watch_gate.unread_message(record2);
126
+ return message_of(record2);
605
127
  }
606
128
  async function watch_reason(now = /* @__PURE__ */ new Date()) {
607
129
  try {
@@ -640,6 +162,67 @@ async function mark_implement(directory = process.cwd(), source = process.env) {
640
162
  }
641
163
  var lane_phase = { mark_implement };
642
164
 
165
+ // scripts/hooks/batch-guard.ts
166
+ var SWITCH_ENV_KEY2 = "JOSH_BATCH_GUARD";
167
+ function is_batch_candidate(call) {
168
+ return time_batch_guard.is_guarded_call(call) || time_batch_guard.is_notice_call(call);
169
+ }
170
+ function batching_mode() {
171
+ return lane_guard_policy.mode_here("batching");
172
+ }
173
+ function is_search_refused_here() {
174
+ return lane_guard_policy.mode_here("search-batching") === "refuse";
175
+ }
176
+ function is_candidate(call) {
177
+ if (batching_mode() !== "off") return is_batch_candidate(call);
178
+ return is_search_refused_here() && time_batch_guard.is_search_chain_call(call);
179
+ }
180
+ function should_block_here(tail, call, refused_at_ms2, run) {
181
+ if (batching_mode() === "refuse") return time_batch_guard.should_block(tail, call, refused_at_ms2);
182
+ if (!is_search_refused_here() || will_refuse(tail, call, run)) return false;
183
+ return time_batch_guard.should_block_search_chain(tail, call, refused_at_ms2);
184
+ }
185
+ function should_notify_here(tail, call, notified_at_ms) {
186
+ if (time_batch_guard.should_notify(tail, call, notified_at_ms)) return true;
187
+ if (batching_mode() !== "notice") return false;
188
+ return time_batch_guard.should_block(
189
+ tail,
190
+ call,
191
+ notified_at_ms,
192
+ time_batch_guard.NOTICE_REFIRE_EVERY
193
+ );
194
+ }
195
+ function notice_text(call, tail) {
196
+ const base = time_batch_guard.is_notice_call(call) ? time_batch_guard.NOTICE : time_batch_guard.LANE_NOTICE;
197
+ return `${base}${time_batch_guard.recent_candidates(tail)}`;
198
+ }
199
+ var GUARD2 = hook_decision.create_transcript_guard({
200
+ prefix: time_batch_guard.STAMP_PREFIX,
201
+ switch_key: SWITCH_ENV_KEY2,
202
+ is_candidate,
203
+ should_block: should_block_here,
204
+ reason: time_batch_guard.REASON,
205
+ // The whole-file write is notified rather than refused, on a record of its own so it never spends
206
+ // the refusal's stamp; the refusable-call notice fires only under a `notice`-mode batching row.
207
+ notify: {
208
+ prefix: time_batch_guard.NOTICE_STAMP_PREFIX,
209
+ should_notify: should_notify_here,
210
+ text: notice_text
211
+ }
212
+ });
213
+ var batch_refusal = GUARD2.refusal;
214
+ var batch_outcome = GUARD2.outcome;
215
+ var { is_enabled: is_enabled2, refusal_path: refusal_path2 } = GUARD2;
216
+ var notice_refusal_path = hook_decision.create_refusal_stamp(
217
+ time_batch_guard.NOTICE_STAMP_PREFIX
218
+ ).path;
219
+ var { deny_envelope: deny_envelope2, load_environment_file: load_environment_file2, DISABLED_VALUES: DISABLED_VALUES2 } = hook_decision;
220
+ var STAMP2 = hook_decision.create_refusal_stamp(time_batch_guard.STAMP_PREFIX);
221
+ function will_refuse2(tail, call, run) {
222
+ if (!is_enabled2() || !is_candidate(call)) return false;
223
+ return should_block_here(tail, call, STAMP2.last_ms(STAMP2.path(run.transcript)), run);
224
+ }
225
+
643
226
  // scripts/rules/agent-model.ts
644
227
  var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["Agent", "Task"]);
645
228
  var GENERAL_PURPOSE = "general-purpose";
@@ -673,35 +256,36 @@ var ROW = {
673
256
  quiet: "a named agent type, or a `model` set"
674
257
  }
675
258
  };
676
- var agent_model = { AGENT_MODEL_REASON, ROW, is_unnamed_model };
677
-
678
- // scripts/rules/direct-filing.ts
679
- var DIRECT_FILING_REASON = '\u26D4 direct filing: an Issue is filed with `pnpm josh issue:file "<title>" --body-file <path>`, 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`").';
680
- function is_any_filing(command) {
681
- return bash_triggers.is_direct_filing(command) || bash_triggers.is_issue_filing(command);
682
- }
683
- var ROW2 = {
684
- id: "direct-filing",
685
- is_trigger: bash_triggers.on_bash_command(bash_triggers.is_direct_filing),
686
- reason: DIRECT_FILING_REASON,
687
- decide: () => true,
688
- keeps: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
689
- reaches: bash_triggers.on_bash_command(is_any_filing),
690
- about: {
691
- title: "Direct filing",
692
- topic: "`docs/josh-commands-backlog.md` \u2192 `josh issue:file`",
693
- fires: "a `Bash` that creates an Issue outside `josh issue:file` (`gh issue create`, or a POST with `title` to `\u2026/issues`); the refusal hands over the `pnpm josh issue:file` form. Every occurrence",
694
- quiet: "filing through `pnpm josh issue:file` \u2014 the duplicate scan, the body check and `epic:bundle` all run"
695
- }
696
- };
697
- var direct_filing = { DIRECT_FILING_REASON, ROW: ROW2 };
259
+ var agent_model = { AGENT_MODEL_REASON, ROW, SUBAGENT_TOOLS, is_unnamed_model };
698
260
 
699
261
  // scripts/rules/gh-api.ts
700
262
  var GH_FLAGS = String.raw`(?:-{1,2}[\w-]+(?:[= ][^\s]+)?\s+)*`;
701
263
  var GH_API_COMMAND = new RegExp(String.raw`^gh\s+${GH_FLAGS}api\s`, "u");
702
- var API_METHOD = /(?:^|\s)(?:--method|-X)[= ]([A-Za-z]+)/u;
703
- var API_FIELD = /(?:^|\s)(?:--raw-field|--field|--input|-F|-f)(?:[= ]|$)/u;
264
+ var GH_COMMAND = "gh";
265
+ var METHOD_FLAGS = /* @__PURE__ */ new Set(["-X", "--method"]);
266
+ var ATTACHED_METHOD = /^(?:-X=?|--method=)([A-Za-z]+)$/u;
267
+ var FIELD_FLAGS = /* @__PURE__ */ new Set(["-f", "-F", "--field", "--raw-field", "--input"]);
268
+ var ATTACHED_FIELD = /^(?:-[fF].|--(?:raw-field|field|input)=)/u;
269
+ var QUOTES = /['"]/gu;
270
+ var FLAG_PREFIX = "-";
271
+ var NEXT = 1;
704
272
  var READ_METHOD = "GET";
273
+ var API_VALUE_TAKING_FLAGS = /* @__PURE__ */ new Set([
274
+ "-R",
275
+ "--repo",
276
+ ...METHOD_FLAGS,
277
+ ...FIELD_FLAGS,
278
+ "-H",
279
+ "--header",
280
+ "-q",
281
+ "--jq",
282
+ "-t",
283
+ "--template",
284
+ "--hostname",
285
+ "-p",
286
+ "--preview",
287
+ "--cache"
288
+ ]);
705
289
  var REPO_PATH = /repos\/([^/\s'"?]+)\/([^/\s'"?]+)/u;
706
290
  var OWNER_GROUP = 1;
707
291
  var REPO_GROUP = 2;
@@ -709,17 +293,68 @@ var OWNER_PLACEHOLDERS = /* @__PURE__ */ new Set(["{owner}", ":owner"]);
709
293
  function is_gh_api(segment) {
710
294
  return GH_API_COMMAND.test(segment);
711
295
  }
296
+ function flags_of(segment) {
297
+ const words = git_argv.arguments_of(segment, GH_COMMAND) ?? [];
298
+ return words.map((word, index) => [word, words[index + NEXT] ?? ""]).filter(([word], index) => {
299
+ if (!word.startsWith(FLAG_PREFIX)) return false;
300
+ return !API_VALUE_TAKING_FLAGS.has(words[index - NEXT] ?? "");
301
+ });
302
+ }
303
+ function method_value(flag, next) {
304
+ if (METHOD_FLAGS.has(flag)) return next.replaceAll(QUOTES, "");
305
+ return ATTACHED_METHOD.exec(flag)?.[1];
306
+ }
712
307
  function method_of(segment) {
713
- return API_METHOD.exec(segment)?.[1]?.toUpperCase();
308
+ const methods = flags_of(segment).map(([flag, next]) => method_value(flag, next));
309
+ return methods.findLast((method) => method !== void 0)?.toUpperCase();
310
+ }
311
+ function is_field_flag(flag) {
312
+ return FIELD_FLAGS.has(flag) || ATTACHED_FIELD.test(flag);
714
313
  }
715
314
  function is_read(segment) {
716
315
  const method = method_of(segment);
717
- if (method !== void 0) return method.toUpperCase() === READ_METHOD;
718
- return !API_FIELD.test(segment);
316
+ if (method !== void 0) return method === READ_METHOD;
317
+ return flags_of(segment).every(([flag]) => !is_field_flag(flag));
719
318
  }
720
319
  function is_write(segment) {
721
320
  return !is_read(segment);
722
321
  }
322
+ var INPUT_FLAG = "--input";
323
+ var READS_FILE_FLAGS = /* @__PURE__ */ new Set(["-F", "--field"]);
324
+ var ATTACHED_PARTS = /^(-[fF]|--(?:raw-field|field|input))=?(.+)$/u;
325
+ var ATTACHED_FLAG_GROUP = 1;
326
+ var ATTACHED_VALUE_GROUP = 2;
327
+ var FILE_PREFIX = "@";
328
+ var STANDARD_INPUT = "-";
329
+ var FIELD_KEY_END = "=";
330
+ function field_parts(flag, next) {
331
+ if (FIELD_FLAGS.has(flag)) return [flag, next];
332
+ const match = ATTACHED_PARTS.exec(flag);
333
+ if (match === null) return void 0;
334
+ return [String(match[ATTACHED_FLAG_GROUP]), String(match[ATTACHED_VALUE_GROUP])];
335
+ }
336
+ function field_value(value) {
337
+ return value.slice(value.indexOf(FIELD_KEY_END) + NEXT);
338
+ }
339
+ function file_path(flag, value) {
340
+ if (flag === INPUT_FLAG) return value;
341
+ const sent = field_value(value);
342
+ if (!READS_FILE_FLAGS.has(flag) || !sent.startsWith(FILE_PREFIX)) return void 0;
343
+ return sent.slice(FILE_PREFIX.length);
344
+ }
345
+ function field_source(flag, raw) {
346
+ const value = raw.replaceAll(QUOTES, "");
347
+ const path4 = file_path(flag, value);
348
+ if (path4 === void 0) return { kind: "text", value: field_value(value) };
349
+ return path4 === STANDARD_INPUT ? void 0 : { kind: "file", value: path4 };
350
+ }
351
+ function field_sources(segment) {
352
+ return flags_of(segment).flatMap(([flag, next]) => {
353
+ const parts = field_parts(flag, next);
354
+ const source = parts === void 0 ? void 0 : field_source(...parts);
355
+ return source === void 0 ? [] : [source];
356
+ });
357
+ }
723
358
  function repo_target(segment) {
724
359
  const match = REPO_PATH.exec(segment);
725
360
  if (match === null) return void 0;
@@ -729,7 +364,9 @@ function is_owner_placeholder(owner) {
729
364
  return OWNER_PLACEHOLDERS.has(owner);
730
365
  }
731
366
  var gh_api = {
367
+ API_VALUE_TAKING_FLAGS,
732
368
  GH_FLAGS,
369
+ field_sources,
733
370
  is_gh_api,
734
371
  is_owner_placeholder,
735
372
  is_read,
@@ -739,44 +376,25 @@ var gh_api = {
739
376
  };
740
377
 
741
378
  // scripts/rules/direct-pr-create.ts
742
- var GH_COMMAND = "gh";
379
+ var GH_COMMAND2 = "gh";
743
380
  var PR_GROUP = "pr";
744
381
  var PR_CREATE_ACTIONS = /* @__PURE__ */ new Set(["create", "new"]);
745
- var FLAG_PREFIX = "-";
382
+ var FLAG_PREFIX2 = "-";
746
383
  var HELP_FLAGS = /* @__PURE__ */ new Set(["-h", "--help"]);
747
384
  var VALUE_TAKING_FLAGS = /* @__PURE__ */ new Set(["-R", "--repo"]);
748
- var API_VALUE_TAKING_FLAGS = /* @__PURE__ */ new Set([
749
- ...VALUE_TAKING_FLAGS,
750
- "-X",
751
- "--method",
752
- "-f",
753
- "--raw-field",
754
- "-F",
755
- "--field",
756
- "-H",
757
- "--header",
758
- "--input",
759
- "-q",
760
- "--jq",
761
- "-t",
762
- "--template",
763
- "--hostname",
764
- "-p",
765
- "--preview",
766
- "--cache"
767
- ]);
385
+ var { API_VALUE_TAKING_FLAGS: API_VALUE_TAKING_FLAGS2 } = gh_api;
768
386
  var API_SUBCOMMAND = "api";
769
387
  var PULLS_PATH = /^['"]?(?:https:\/\/api\.github\.com)?\/?repos\/[^/\s'"]+\/[^/\s'"]+\/pulls\/?['"]?$/u;
770
388
  function positional_words(args, value_taking_flags) {
771
389
  return args.filter((argument, index) => {
772
- if (argument.startsWith(FLAG_PREFIX)) return false;
390
+ if (argument.startsWith(FLAG_PREFIX2)) return false;
773
391
  return !value_taking_flags.has(args[index - 1] ?? "");
774
392
  });
775
393
  }
776
394
  function is_pr_create_api_call(args) {
777
- const call = [GH_COMMAND, ...args].join(" ");
395
+ const call = [GH_COMMAND2, ...args].join(" ");
778
396
  if (!gh_api.is_gh_api(call) || !gh_api.is_write(call)) return false;
779
- const [subcommand, endpoint = ""] = positional_words(args, API_VALUE_TAKING_FLAGS);
397
+ const [subcommand, endpoint = ""] = positional_words(args, API_VALUE_TAKING_FLAGS2);
780
398
  return subcommand === API_SUBCOMMAND && PULLS_PATH.test(endpoint);
781
399
  }
782
400
  function is_pr_create_cli_call(args) {
@@ -785,11 +403,11 @@ function is_pr_create_cli_call(args) {
785
403
  }
786
404
  function is_help_call(args) {
787
405
  return args.some(
788
- (argument, index) => HELP_FLAGS.has(argument) && !(args[index - 1] ?? "").startsWith(FLAG_PREFIX)
406
+ (argument, index) => HELP_FLAGS.has(argument) && !(args[index - 1] ?? "").startsWith(FLAG_PREFIX2)
789
407
  );
790
408
  }
791
409
  function is_pr_create_segment(segment) {
792
- const args = git_argv.arguments_of(segment, GH_COMMAND);
410
+ const args = git_argv.arguments_of(segment, GH_COMMAND2);
793
411
  if (args === void 0 || is_help_call(args)) return false;
794
412
  return is_pr_create_cli_call(args) || is_pr_create_api_call(args);
795
413
  }
@@ -815,24 +433,24 @@ function has_run_segment(command, is_match) {
815
433
  function is_direct_pr_create(command) {
816
434
  return has_run_segment(command, is_pr_create_segment);
817
435
  }
818
- 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.**";
819
- var ROW3 = {
436
+ 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 (`operating-rules.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.**';
437
+ var ROW2 = {
820
438
  id: "direct-pr-create",
821
439
  is_trigger: bash_triggers.on_bash_command(is_direct_pr_create),
822
440
  reason: DIRECT_PR_CREATE_REASON,
823
441
  decide: () => true,
824
442
  about: {
825
443
  title: "Direct PR creation",
826
- topic: "`CLAUDE.md` \u2192 Git Rules",
444
+ topic: '`operating-rules.md` \u2192 "git-rules"',
827
445
  fires: "`gh pr create`, or a `gh api` write to `repos/<o>/<r>/pulls`; it points at `pnpm josh pr`. Every occurrence",
828
446
  quiet: "`pnpm josh pr`, or reading a PR \u2014 the path that writes `closes #N`"
829
447
  }
830
448
  };
831
449
  var direct_pr_create = {
832
- API_VALUE_TAKING_FLAGS,
450
+ API_VALUE_TAKING_FLAGS: API_VALUE_TAKING_FLAGS2,
833
451
  DIRECT_PR_CREATE_REASON,
834
- GH_COMMAND,
835
- ROW: ROW3,
452
+ GH_COMMAND: GH_COMMAND2,
453
+ ROW: ROW2,
836
454
  VALUE_TAKING_FLAGS,
837
455
  has_run_segment,
838
456
  is_direct_pr_create,
@@ -840,6 +458,63 @@ var direct_pr_create = {
840
458
  positional_words
841
459
  };
842
460
 
461
+ // scripts/rules/credential-body.ts
462
+ var TYPED_ESCAPE = /\\./gu;
463
+ function sent_text(source) {
464
+ if (source.kind === "text") return source.value;
465
+ return token_shape.decoded(file_reader.read_if_readable(source.value) ?? "");
466
+ }
467
+ function holds_token(text) {
468
+ return [text, text.replaceAll(TYPED_ESCAPE, () => " ")].some(
469
+ (reading) => token_shape.holds(reading)
470
+ );
471
+ }
472
+ function is_credential_segment(segment) {
473
+ const args = git_argv.arguments_of(segment, direct_pr_create.GH_COMMAND);
474
+ if (args === void 0) return false;
475
+ const call = [direct_pr_create.GH_COMMAND, ...args].join(" ");
476
+ if (!gh_api.is_gh_api(call)) return false;
477
+ return gh_api.field_sources(call).some((source) => holds_token(sent_text(source)));
478
+ }
479
+ function sends_credential(command) {
480
+ return direct_pr_create.has_run_segment(command, is_credential_segment);
481
+ }
482
+ var CREDENTIAL_BODY_REASON = "\u26D4 credential in a gh api body: a field value, or the file `-F key=@<path>` / `--input <path>` names, holds a token-shaped string (`scripts/lib/token-shape.ts`), and the call would publish it. Remove the credential from the body and send it again. **This rule fires on every occurrence, not once per run.**";
483
+ var ROW3 = {
484
+ id: "credential-body",
485
+ is_trigger: bash_triggers.on_bash_command(sends_credential),
486
+ reason: CREDENTIAL_BODY_REASON,
487
+ decide: () => true,
488
+ about: {
489
+ title: "Credential in a body",
490
+ topic: "`shell-body.md`",
491
+ fires: "a `gh api` call whose field value, or the file `-F key=@<path>` / `--input <path>` names, holds a token-shaped string (`token-shape.ts`). Every occurrence",
492
+ quiet: "a body with no credential, or a body sent through kit, which runs the same scan"
493
+ }
494
+ };
495
+ var credential_body = { CREDENTIAL_BODY_REASON, ROW: ROW3, sends_credential };
496
+
497
+ // scripts/rules/direct-filing.ts
498
+ var DIRECT_FILING_REASON = '\u26D4 direct filing: an Issue is filed with `pnpm josh issue:file "<title>" --body-file <path>`, 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`").';
499
+ function is_any_filing(command) {
500
+ return bash_triggers.is_direct_filing(command) || bash_triggers.is_issue_filing(command);
501
+ }
502
+ var ROW4 = {
503
+ id: "direct-filing",
504
+ is_trigger: bash_triggers.on_bash_command(bash_triggers.is_direct_filing),
505
+ reason: DIRECT_FILING_REASON,
506
+ decide: () => true,
507
+ keeps: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
508
+ reaches: bash_triggers.on_bash_command(is_any_filing),
509
+ about: {
510
+ title: "Direct filing",
511
+ topic: "`docs/josh-commands-backlog.md` \u2192 `josh issue:file`",
512
+ fires: "a `Bash` that creates an Issue outside `josh issue:file` (`gh issue create`, or a POST with `title` to `\u2026/issues`); the refusal hands over the `pnpm josh issue:file` form. Every occurrence",
513
+ quiet: "filing through `pnpm josh issue:file` \u2014 the duplicate scan, the body check and `epic:bundle` all run"
514
+ }
515
+ };
516
+ var direct_filing = { DIRECT_FILING_REASON, ROW: ROW4 };
517
+
843
518
  // scripts/rules/direct-issue-comment.ts
844
519
  var ISSUE_GROUP = "issue";
845
520
  var COMMENT_ACTION = "comment";
@@ -860,50 +535,292 @@ function is_comment_cli_call(args) {
860
535
  );
861
536
  return group === ISSUE_GROUP && action === COMMENT_ACTION;
862
537
  }
863
- function is_own_comments_endpoint(endpoint) {
864
- const owner = gh_api.repo_target(endpoint)?.owner ?? "";
865
- return gh_api.is_owner_placeholder(owner) && COMMENTS_PATH.test(endpoint);
538
+ function is_own_comments_endpoint(endpoint, resolve_own) {
539
+ const target = gh_api.repo_target(endpoint);
540
+ if (target === void 0 || !COMMENTS_PATH.test(endpoint)) return false;
541
+ return gh_api.is_owner_placeholder(target.owner) || repo_party.is_own_repository(target, resolve_own());
866
542
  }
867
- function is_comment_api_call(args) {
543
+ function is_comment_api_call(args, resolve_own) {
868
544
  const call = [direct_pr_create.GH_COMMAND, ...args].join(" ");
869
545
  if (!gh_api.is_gh_api(call) || !gh_api.is_write(call)) return false;
870
546
  const [subcommand, endpoint = ""] = direct_pr_create.positional_words(
871
547
  args,
872
- direct_pr_create.API_VALUE_TAKING_FLAGS
548
+ direct_pr_create.API_VALUE_TAKING_FLAGS
549
+ );
550
+ return subcommand === API_SUBCOMMAND2 && is_own_comments_endpoint(endpoint, resolve_own);
551
+ }
552
+ function is_comment_segment(segment, resolve_own) {
553
+ const args = git_argv.arguments_of(segment, direct_pr_create.GH_COMMAND);
554
+ if (args === void 0 || direct_pr_create.is_help_call(args)) return false;
555
+ return is_comment_cli_call(args) || is_comment_api_call(args, resolve_own);
556
+ }
557
+ function is_direct_issue_comment(command, resolve_own = repo_party.current_repository) {
558
+ return direct_pr_create.has_run_segment(
559
+ command,
560
+ (segment) => is_comment_segment(segment, resolve_own)
561
+ );
562
+ }
563
+ var COMMENT_NAMES = /* @__PURE__ */ new Set(["issue:comment"]);
564
+ function is_josh_comment(command) {
565
+ return shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, COMMENT_NAMES));
566
+ }
567
+ function is_any_comment(command) {
568
+ return is_direct_issue_comment(command) || is_josh_comment(command);
569
+ }
570
+ var DIRECT_ISSUE_COMMENT_REASON = "\u26D4 direct issue comment: `gh issue comment` (or a `gh api` write to `\u2026/issues/<N>/comments`) posts without the run marker, so the run reads its own comment as a person updating the issue \u2014 the review goes stale and `pnpm josh followup` refuses the merge. Post with `pnpm josh issue:comment <N> --body-file <path>` instead (`shell-body.md`). **This rule fires on every occurrence, not once per run.**";
571
+ var ROW5 = {
572
+ id: "direct-issue-comment",
573
+ is_trigger: bash_triggers.on_bash_command(is_direct_issue_comment),
574
+ reason: DIRECT_ISSUE_COMMENT_REASON,
575
+ decide: () => true,
576
+ keeps: bash_triggers.on_bash_command(is_josh_comment),
577
+ reaches: bash_triggers.on_bash_command(is_any_comment),
578
+ about: {
579
+ title: "Direct issue comment",
580
+ topic: "`shell-body.md`",
581
+ fires: "a comment to the checkout's own repository outside `josh issue:comment` (`gh issue comment` with no `--repo`, or a `gh api` write to `repos/<owner>/<r>/issues/<N>/comments` whose owner is the `{owner}` placeholder or the checkout's own `owner/repo` spelled out); it points at `pnpm josh issue:comment`. Every occurrence",
582
+ quiet: "`pnpm josh issue:comment`, or reading the comments \u2014 the path that writes the run marker"
583
+ }
584
+ };
585
+ var direct_issue_comment = { DIRECT_ISSUE_COMMENT_REASON, ROW: ROW5, is_direct_issue_comment };
586
+
587
+ // scripts/rules/prior-comment-read.ts
588
+ var ISSUE_PATH_NUMBER = /issues\/(\d+)/gu;
589
+ var ISSUE_VIEW_NUMBER = /\bissue\s+view\s+(?:-{1,2}\S+\s+)*(\d+)/u;
590
+ var ISSUE_READ_ARGUMENTS = /\b(?:issue:read|run:entry|run:prep)\b([\s\d]*)/u;
591
+ var COMMENTS_ENDPOINT = /issues\/\d+\/comments\b/u;
592
+ var VIEW_WITH_COMMENTS = /\bissue\s+view\b[^\n]*--comments\b/u;
593
+ var ISSUE_READ_COMMAND = /\b(?:issue:read|run:entry|run:prep)\b/u;
594
+ function add_issue_read_numbers(command, numbers) {
595
+ const match = ISSUE_READ_ARGUMENTS.exec(command);
596
+ if (match === null) return;
597
+ const tokens = (match[1] ?? "").split(/\s+/u);
598
+ for (const token of tokens) {
599
+ if (issue_number_shape.ISSUE_NUMBER_PATTERN.test(token)) numbers.add(Number(token));
600
+ }
601
+ }
602
+ function issue_numbers_of(command) {
603
+ const numbers = /* @__PURE__ */ new Set();
604
+ for (const match of command.matchAll(ISSUE_PATH_NUMBER)) numbers.add(Number(match[1]));
605
+ const view = ISSUE_VIEW_NUMBER.exec(command);
606
+ if (view !== null) numbers.add(Number(view[1]));
607
+ add_issue_read_numbers(command, numbers);
608
+ return numbers;
609
+ }
610
+ function fetches_comments(command) {
611
+ return COMMENTS_ENDPOINT.test(command) || VIEW_WITH_COMMENTS.test(command) || ISSUE_READ_COMMAND.test(command);
612
+ }
613
+ function reads_target_comments(command, target) {
614
+ if (!fetches_comments(command)) return false;
615
+ for (const number of issue_numbers_of(command)) {
616
+ if (target.has(number)) return true;
617
+ }
618
+ return false;
619
+ }
620
+ function already_read_comments_for(tail, command) {
621
+ const target = issue_numbers_of(command);
622
+ if (target.size === 0) return false;
623
+ return tail_commands.prior_bash_commands(tail).some((prior) => reads_target_comments(prior, target));
624
+ }
625
+ function already_read_for_call(tail, call) {
626
+ if (call.name !== cost_blocks.BASH_TOOL) return false;
627
+ return already_read_comments_for(tail, time_shell.bash_command(call.input));
628
+ }
629
+ var prior_comment_read = {
630
+ already_read_comments_for,
631
+ already_read_for_call,
632
+ fetches_comments,
633
+ issue_numbers_of
634
+ };
635
+
636
+ // scripts/rules/issue-comments.ts
637
+ var ISSUE_VIEW_COMMAND = new RegExp(String.raw`^gh\s+${gh_api.GH_FLAGS}issue\s+view\s`, "u");
638
+ var ISSUE_BODY_PATH = /repos\/[^\s'"]*\/issues\/\d+(?=$|["'\s])/u;
639
+ var FETCHES_COMMENTS = /--comments\b|--json[=\s]['"]?[\w,]*\bcomments\b|\/comments\b/u;
640
+ var ISSUES_PATH = /repos\/[^\s'"]*\/issues\//u;
641
+ var FIELD_PROJECTION = /(?:--jq|--json)[= ]\s*('[^']*'|"[^"]*"|\S+)/u;
642
+ var NAMES_THE_BODY = /\bbody\b/u;
643
+ var NAMES_A_FIELD = /(?:^|[.{,\s'"])[A-Za-z_]/u;
644
+ function projects_away_body(segment) {
645
+ const value = FIELD_PROJECTION.exec(segment)?.[1];
646
+ return value !== void 0 && NAMES_A_FIELD.test(value) && !NAMES_THE_BODY.test(value);
647
+ }
648
+ function is_body_read_segment(segment) {
649
+ if (projects_away_body(segment)) return false;
650
+ if (ISSUE_VIEW_COMMAND.test(segment)) return true;
651
+ return gh_api.is_gh_api(segment) && ISSUE_BODY_PATH.test(segment) && gh_api.is_read(segment);
652
+ }
653
+ function fetches_issue_comments(segment) {
654
+ if (!FETCHES_COMMENTS.test(segment)) return false;
655
+ return ISSUE_VIEW_COMMAND.test(segment) || gh_api.is_gh_api(segment) && ISSUES_PATH.test(segment);
656
+ }
657
+ function is_body_only_issue_read(command) {
658
+ const segments = shell_segments.segments_of(command);
659
+ if (segments.some((segment) => fetches_issue_comments(segment))) return false;
660
+ return segments.some((segment) => is_body_read_segment(segment));
661
+ }
662
+ function reads_issue_comments(command) {
663
+ return shell_segments.segments_of(command).some((segment) => fetches_issue_comments(segment));
664
+ }
665
+ var RULE_DOC = "`.claude/skills/workflow-commands/issue-comments.md`";
666
+ var COMMENTS_FALLBACK = "gh api repos/<owner>/<repo>/issues/<N>/comments";
667
+ var ISSUE_COMMENTS_REASON = `\u26D4 issue comments: pnpm josh issue:read <N>
668
+ 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}).`;
669
+ var COMMENTS_JQ = String.raw`'.[] | "— \(.user.login) \(.created_at)\n\(.body)\n"'`;
670
+ 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}).`;
671
+ function rewritable_path(command) {
672
+ const read2 = shell_segments.first_of(command);
673
+ if (read2 === void 0 || ISSUE_VIEW_COMMAND.test(read2)) return void 0;
674
+ return is_body_read_segment(read2) ? ISSUE_BODY_PATH.exec(read2)?.[0] : void 0;
675
+ }
676
+ function comments_command(command) {
677
+ if (!is_body_only_issue_read(command)) return void 0;
678
+ const path4 = rewritable_path(command);
679
+ if (path4 === void 0) return void 0;
680
+ return `gh api --paginate ${path4}/comments --jq ${COMMENTS_JQ}
681
+ ${command}`;
682
+ }
683
+ function comments_input(call, tail) {
684
+ if (call.name !== cost_blocks.BASH_TOOL || !json_value.is_record(call.input)) return void 0;
685
+ const command = comments_command(time_shell.bash_command(call.input));
686
+ if (command === void 0 || prior_comment_read.already_read_for_call(tail(), call)) {
687
+ return void 0;
688
+ }
689
+ return { ...call.input, command };
690
+ }
691
+ var ROW6 = {
692
+ id: "issue-comments",
693
+ is_trigger: bash_triggers.on_bash_command(is_body_only_issue_read),
694
+ reason: ISSUE_COMMENTS_REASON,
695
+ already_satisfied: prior_comment_read.already_read_for_call,
696
+ keeps: bash_triggers.on_bash_command(reads_issue_comments),
697
+ rewrite: comments_input,
698
+ rewrite_note: COMMENTS_NOTE,
699
+ // The opening the reason carried before it led with the command.
700
+ former_reasons: ["\u26D4 an Issue's comments are part of the Issue: read them before implementing"],
701
+ about: {
702
+ title: "Reading Issue comments",
703
+ topic: "`issue-comments.md`",
704
+ fires: "a `Bash` that reads only the Issue body (`gh issue view <N>`, or a GET ending in `\u2026/issues/<N>`). Refused until it is re-read with the comments",
705
+ quiet: "no Issue read, or a read with `--comments` / `--json comments` / `\u2026/comments` from the start"
706
+ }
707
+ };
708
+ var issue_comments = {
709
+ COMMENTS_NOTE,
710
+ ISSUE_COMMENTS_REASON,
711
+ ROW: ROW6,
712
+ comments_input,
713
+ fetches_issue_comments,
714
+ is_body_only_issue_read,
715
+ projects_away_body
716
+ };
717
+
718
+ // scripts/rules/direct-issue-read.ts
719
+ var API_SUBCOMMAND3 = "api";
720
+ var ISSUE_GROUP2 = "issue";
721
+ var VIEW_ACTION = "view";
722
+ var WEB_FLAGS = /* @__PURE__ */ new Set(["-w", "--web"]);
723
+ var VIEW_VALUE_TAKING_FLAGS = /* @__PURE__ */ new Set([
724
+ ...direct_pr_create.VALUE_TAKING_FLAGS,
725
+ "--json",
726
+ "-q",
727
+ "--jq",
728
+ "-t",
729
+ "--template"
730
+ ]);
731
+ var REPO_EQUALS = "--repo=";
732
+ var ISSUE_URL = /^https?:\/\/[^/\s]+\/(?<named>[^/\s]+\/[^/\s]+)\/issues\/\d+/u;
733
+ var OWNER_REPO_PARTS = 2;
734
+ var QUOTE2 = `['"]?`;
735
+ var API_ORIGIN2 = String.raw`(?:https://api\.github\.com)?/?`;
736
+ var SEGMENT2 = String.raw`[^/\s'"?]+`;
737
+ var NUMBER = String.raw`(?:\d+|\$\{?\w+\}?)`;
738
+ var ISSUE_READ_PATH = new RegExp(
739
+ String.raw`^${QUOTE2}${API_ORIGIN2}repos/${SEGMENT2}/${SEGMENT2}/issues/${NUMBER}(?:/comments|/timeline)?/?(?:\?[^\s'"]*)?${QUOTE2}$`,
740
+ "u"
741
+ );
742
+ function is_own_repository(owner, repo) {
743
+ if (gh_api.is_owner_placeholder(owner)) return true;
744
+ return `${owner}/${repo}`.toLowerCase() === session_cite.session_repo()?.toLowerCase();
745
+ }
746
+ function is_own_issue_endpoint(endpoint) {
747
+ const target = gh_api.repo_target(endpoint);
748
+ if (target === void 0) return false;
749
+ return is_own_repository(target.owner, target.repo) && ISSUE_READ_PATH.test(endpoint);
750
+ }
751
+ function is_body_api_read(call) {
752
+ return gh_api.is_gh_api(call) && gh_api.is_read(call) && !issue_comments.projects_away_body(call);
753
+ }
754
+ function is_api_issue_read(args, call) {
755
+ if (!is_body_api_read(call)) return false;
756
+ const [subcommand, endpoint = ""] = direct_pr_create.positional_words(
757
+ args,
758
+ direct_pr_create.API_VALUE_TAKING_FLAGS
759
+ );
760
+ return subcommand === API_SUBCOMMAND3 && is_own_issue_endpoint(endpoint);
761
+ }
762
+ function repo_flag(args) {
763
+ const index = args.findIndex((argument) => direct_pr_create.VALUE_TAKING_FLAGS.has(argument));
764
+ if (index !== -1) return args[index + 1];
765
+ return args.find((argument) => argument.startsWith(REPO_EQUALS))?.slice(REPO_EQUALS.length);
766
+ }
767
+ function named_repository(args, issue) {
768
+ return ISSUE_URL.exec(issue)?.groups?.["named"] ?? repo_flag(args);
769
+ }
770
+ function is_own_view_target(args, issue) {
771
+ const named = named_repository(args, issue);
772
+ if (named === void 0) return true;
773
+ const [owner = "", repo = ""] = named.split("/").slice(-OWNER_REPO_PARTS);
774
+ return is_own_repository(owner, repo);
775
+ }
776
+ function prints_body_or_comments(call) {
777
+ if (issue_comments.fetches_issue_comments(call)) return true;
778
+ return !issue_comments.projects_away_body(call);
779
+ }
780
+ function is_printed_view(args, issue, call) {
781
+ if (args.some((argument) => WEB_FLAGS.has(argument))) return false;
782
+ return is_own_view_target(args, issue) && prints_body_or_comments(call);
783
+ }
784
+ function is_issue_view_read(args, call) {
785
+ const [group, action, issue = ""] = direct_pr_create.positional_words(
786
+ args,
787
+ VIEW_VALUE_TAKING_FLAGS
873
788
  );
874
- return subcommand === API_SUBCOMMAND2 && is_own_comments_endpoint(endpoint);
789
+ const is_view = group === ISSUE_GROUP2 && action === VIEW_ACTION;
790
+ return is_view && is_printed_view(args, issue, call);
875
791
  }
876
- function is_comment_segment(segment) {
792
+ function is_issue_read_segment(segment) {
877
793
  const args = git_argv.arguments_of(segment, direct_pr_create.GH_COMMAND);
878
794
  if (args === void 0 || direct_pr_create.is_help_call(args)) return false;
879
- return is_comment_cli_call(args) || is_comment_api_call(args);
795
+ const call = [direct_pr_create.GH_COMMAND, ...args].join(" ");
796
+ return is_api_issue_read(args, call) || is_issue_view_read(args, call);
880
797
  }
881
- function is_direct_issue_comment(command) {
882
- return direct_pr_create.has_run_segment(command, is_comment_segment);
798
+ function is_direct_issue_read(command) {
799
+ return direct_pr_create.has_run_segment(command, is_issue_read_segment);
883
800
  }
884
- var COMMENT_NAMES = /* @__PURE__ */ new Set(["issue:comment"]);
885
- function is_josh_comment(command) {
886
- return shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, COMMENT_NAMES));
801
+ var READ_NAMES = /* @__PURE__ */ new Set(["issue:read", "run:prep", "run:entry"]);
802
+ function is_josh_read(command) {
803
+ return shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, READ_NAMES));
887
804
  }
888
- function is_any_comment(command) {
889
- return is_direct_issue_comment(command) || is_josh_comment(command);
805
+ function is_any_read(command) {
806
+ return is_direct_issue_read(command) || is_josh_read(command);
890
807
  }
891
- var DIRECT_ISSUE_COMMENT_REASON = "\u26D4 direct issue comment: `gh issue comment` (or a `gh api` write to `\u2026/issues/<N>/comments`) posts without the run marker, so the run reads its own comment as a person updating the issue \u2014 the review goes stale and `pnpm josh followup` refuses the merge. Post with `pnpm josh issue:comment <N> --body-file <path>` instead (`shell-body.md`). **This rule fires on every occurrence, not once per run.**";
892
- var ROW4 = {
893
- id: "direct-issue-comment",
894
- is_trigger: bash_triggers.on_bash_command(is_direct_issue_comment),
895
- reason: DIRECT_ISSUE_COMMENT_REASON,
808
+ var DIRECT_ISSUE_READ_REASON = "\u26D4 direct issue read: pnpm josh issue:read <N>\nRun that in place of this read of `\u2026/issues/<N>`, its comments or its timeline (`gh api` or `gh issue view`) \u2014 it withholds a comment from an author without write access and marks an untrusted body as a report, which a raw read prints whole as if it were an instruction (`issue-comments.md`). **This rule fires on every occurrence, not once per run.**";
809
+ var ROW7 = {
810
+ id: "direct-issue-read",
811
+ is_trigger: bash_triggers.on_bash_command(is_direct_issue_read),
812
+ reason: DIRECT_ISSUE_READ_REASON,
896
813
  decide: () => true,
897
- keeps: bash_triggers.on_bash_command(is_josh_comment),
898
- reaches: bash_triggers.on_bash_command(is_any_comment),
814
+ keeps: bash_triggers.on_bash_command(is_josh_read),
815
+ reaches: bash_triggers.on_bash_command(is_any_read),
899
816
  about: {
900
- title: "Direct issue comment",
901
- topic: "`shell-body.md`",
902
- fires: "a comment to the checkout's own repository outside `josh issue:comment` (`gh issue comment` with no `--repo`, or a `gh api` write to `repos/{owner}/<r>/issues/<N>/comments`); it points at `pnpm josh issue:comment`. Every occurrence",
903
- quiet: "`pnpm josh issue:comment`, or reading the comments \u2014 the path that writes the run marker"
817
+ title: "Direct issue read",
818
+ topic: "`issue-comments.md`",
819
+ fires: "a `gh api` GET of the checkout's own `repos/<o>/<r>/issues/<N>`, `\u2026/comments` or `\u2026/timeline` (the `{owner}` placeholder or the origin spelled out), or a `gh issue view <N>` of it, with no projection or one naming the body (or, for the view, the comments); it points at `pnpm josh issue:read`. Every occurrence",
820
+ quiet: "`pnpm josh issue:read` / `run:prep` / `run:entry`, a `--jq` / `--json` projection without the body or the comments, `gh issue view --web`, or another repository named outright"
904
821
  }
905
822
  };
906
- var direct_issue_comment = { DIRECT_ISSUE_COMMENT_REASON, ROW: ROW4, is_direct_issue_comment };
823
+ var direct_issue_read = { DIRECT_ISSUE_READ_REASON, ROW: ROW7, is_direct_issue_read };
907
824
 
908
825
  // scripts/run/progress/run-progress.ts
909
826
  var DEFAULT_INTERVAL_MINUTES = 20;
@@ -1256,7 +1173,9 @@ var early_heartbeat = {
1256
1173
  EARLY_HEARTBEAT_REASON,
1257
1174
  decide,
1258
1175
  is_progress_watch,
1176
+ is_wait_or_inert,
1259
1177
  is_wait_timer,
1178
+ sleep_seconds,
1260
1179
  wait_duration_ms,
1261
1180
  waits_for_progress
1262
1181
  };
@@ -1287,10 +1206,10 @@ function command_lines(command) {
1287
1206
  }
1288
1207
  var FILE_REDIRECT = /(?:^|\s)>>?\s*(?!&)("[^"]+"|'[^']+'|\S+)/u;
1289
1208
  var TEE_TARGET = /\btee\s+(?:-a\s+)?(?!-)("[^"]+"|'[^']+'|\S+)/u;
1290
- var QUOTES = /^['"]|['"]$/gu;
1209
+ var QUOTES2 = /^['"]|['"]$/gu;
1291
1210
  function redirect_target(line) {
1292
1211
  const target = FILE_REDIRECT.exec(line)?.[1] ?? TEE_TARGET.exec(line)?.[1];
1293
- return target === void 0 ? void 0 : target.replaceAll(QUOTES, "");
1212
+ return target === void 0 ? void 0 : target.replaceAll(QUOTES2, "");
1294
1213
  }
1295
1214
  var BODY_INTERPRETERS = /* @__PURE__ */ new Set([
1296
1215
  "python",
@@ -1336,7 +1255,7 @@ function carries_a_file_body(command, has_file = existsSync) {
1336
1255
  );
1337
1256
  }
1338
1257
  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`).";
1339
- var ROW5 = {
1258
+ var ROW8 = {
1340
1259
  id: "file-body",
1341
1260
  is_trigger: bash_triggers.on_bash_command((command) => carries_a_file_body(command)),
1342
1261
  reason: FILE_BODY_REASON,
@@ -1350,10 +1269,225 @@ var ROW5 = {
1350
1269
  };
1351
1270
  var file_body = {
1352
1271
  FILE_BODY_REASON,
1353
- ROW: ROW5,
1272
+ ROW: ROW8,
1354
1273
  carries_a_file_body
1355
1274
  };
1356
1275
 
1276
+ // scripts/rules/edit-tool.ts
1277
+ var EDIT = "Edit";
1278
+ var WRITE = "Write";
1279
+ var EDIT_TOOLS = /* @__PURE__ */ new Set([EDIT, WRITE]);
1280
+ function is_edit_tool(name) {
1281
+ return EDIT_TOOLS.has(name);
1282
+ }
1283
+ function string_field(input, key) {
1284
+ const value = input[key];
1285
+ return typeof value === "string" ? value : "";
1286
+ }
1287
+ var edit_tool = {
1288
+ EDIT,
1289
+ WRITE,
1290
+ is_edit_tool,
1291
+ string_field
1292
+ };
1293
+
1294
+ // scripts/rules/file-rewrite.ts
1295
+ var { WRITE: WRITE2, EDIT: EDIT2, is_edit_tool: is_edit_tool2, string_field: string_field2 } = edit_tool;
1296
+ var MAX_SHARED_RATIO = 0.5;
1297
+ var PERCENT = 100;
1298
+ var LINE_BREAK3 = "\n";
1299
+ var ISSUE_PLACEHOLDER = "<N>";
1300
+ var REGEX_SPECIALS = /[.*+?^${}()|[\]\\]/gu;
1301
+ var ISSUE_DIGITS = String.raw`\d+`;
1302
+ var HANDOFF_PATTERN = run_cut_handoff.HANDOFF_PATH.replaceAll(
1303
+ REGEX_SPECIALS,
1304
+ (special) => `\\${special}`
1305
+ ).replace(ISSUE_PLACEHOLDER, () => ISSUE_DIGITS);
1306
+ var HANDOFF = new RegExp(`(?:^|/)${HANDOFF_PATTERN}$`, "u");
1307
+ function body_lines(text) {
1308
+ return text.split(LINE_BREAK3).map((line) => line.trim()).filter((line) => line !== "");
1309
+ }
1310
+ function count_lines(lines) {
1311
+ const counts = /* @__PURE__ */ new Map();
1312
+ for (const line of lines) counts.set(line, (counts.get(line) ?? 0) + 1);
1313
+ return counts;
1314
+ }
1315
+ function take_line(counts, line) {
1316
+ const left = counts.get(line) ?? 0;
1317
+ if (left === 0) return false;
1318
+ counts.set(line, left - 1);
1319
+ return true;
1320
+ }
1321
+ function shared_ratio(old_text, new_text) {
1322
+ const new_lines = body_lines(new_text);
1323
+ if (new_lines.length === 0) return 0;
1324
+ const old_counts = count_lines(body_lines(old_text));
1325
+ const shared = new_lines.filter((line) => take_line(old_counts, line)).length;
1326
+ return shared / new_lines.length;
1327
+ }
1328
+ function write_input(call) {
1329
+ if (call.name !== WRITE2 || !json_value.is_record(call.input)) return void 0;
1330
+ return HANDOFF.test(string_field2(call.input, "file_path")) ? void 0 : call.input;
1331
+ }
1332
+ function rewrite_ratio(call, read2) {
1333
+ const input = write_input(call);
1334
+ const old_text = input === void 0 ? void 0 : read2(string_field2(input, "file_path"));
1335
+ if (input === void 0 || old_text === void 0) return void 0;
1336
+ return shared_ratio(old_text, string_field2(input, "content"));
1337
+ }
1338
+ function rewrites_kept_body(call, read2 = file_reader.read_optional) {
1339
+ return (rewrite_ratio(call, read2) ?? 0) > MAX_SHARED_RATIO;
1340
+ }
1341
+ function reaches_file_change(call) {
1342
+ return is_edit_tool2(call.name);
1343
+ }
1344
+ function keeps_region_edit(call) {
1345
+ return call.name === EDIT2;
1346
+ }
1347
+ var FILE_REWRITE_REASON = `\u26D4 whole-file Write of an existing file: the new body keeps more than ${String(MAX_SHARED_RATIO * PERCENT)}% of the old lines, so it is a region edit carried as a whole file and its diff cannot be read. Change the region with the Edit tool; a Write stays for a new file or a rebuild sharing almost nothing. This rule fires on every occurrence (\`prompts/collaboration-workflow/file-edits.md\`).`;
1348
+ var ROW9 = {
1349
+ id: "file-rewrite",
1350
+ is_trigger: (call) => rewrites_kept_body(call),
1351
+ reason: FILE_REWRITE_REASON,
1352
+ decide: () => true,
1353
+ keeps: keeps_region_edit,
1354
+ reaches: reaches_file_change,
1355
+ about: {
1356
+ title: "No whole-file Write that keeps most of the old body",
1357
+ topic: "`file-edits.md`",
1358
+ fires: "a `Write` over an existing file whose new body shares more than half its non-blank lines with the old one. Every occurrence",
1359
+ quiet: "a new file, the run's handoff, a rebuild sharing half or less, or an `Edit`"
1360
+ }
1361
+ };
1362
+ var file_rewrite = {
1363
+ FILE_REWRITE_REASON,
1364
+ MAX_SHARED_RATIO,
1365
+ ROW: ROW9,
1366
+ rewrites_kept_body,
1367
+ shared_ratio
1368
+ };
1369
+
1370
+ // scripts/rules/sed-in-place.ts
1371
+ var SED = "sed";
1372
+ var IN_PLACE_SHORT = /^-[A-Za-z]*i/u;
1373
+ var IN_PLACE_LONG = /^--in-place(?:=|$)/u;
1374
+ var IN_PLACE_CLUSTER = /^-[A-Za-z]*i$/u;
1375
+ var EXTENDED_SHORT = /^-[A-Za-z]*[Er]/u;
1376
+ var EXTENDED_LONG = "--regexp-extended";
1377
+ var PLAIN_FLAGS = /* @__PURE__ */ new Set(["", "g"]);
1378
+ var SCRIPT_FLAG = "-e";
1379
+ var FLAG_PREFIX3 = "-";
1380
+ var SURROUNDING_QUOTES = /^(['"])(.*)\1$/su;
1381
+ var QUOTED_BODY = 2;
1382
+ var META_CHARACTER = /[/&\\.*[\]^$]/u;
1383
+ var SUBSTITUTE = "s";
1384
+ var SUBSTITUTE_PARTS = 3;
1385
+ var ONE_MATCH = 1;
1386
+ function unquoted(word) {
1387
+ return SURROUNDING_QUOTES.exec(word)?.[QUOTED_BODY] ?? word;
1388
+ }
1389
+ function is_in_place_flag(word) {
1390
+ return IN_PLACE_LONG.test(word) || IN_PLACE_SHORT.test(word);
1391
+ }
1392
+ function without_suffix(words) {
1393
+ const index = words.findIndex((word) => IN_PLACE_CLUSTER.test(word));
1394
+ if (index === -1 || words[index + 1] !== "") return [...words];
1395
+ return words.toSpliced(index + 1, 1);
1396
+ }
1397
+ function sed_words(segment) {
1398
+ const words = git_argv.arguments_of(segment, SED)?.map((word) => unquoted(word));
1399
+ return words?.some((word) => is_in_place_flag(word)) === true ? without_suffix(words) : void 0;
1400
+ }
1401
+ function is_script_value(words, index) {
1402
+ return words[index - 1] === SCRIPT_FLAG;
1403
+ }
1404
+ function sed_call(words) {
1405
+ const scripts = words.filter((_word, index) => is_script_value(words, index));
1406
+ const operands = words.filter(
1407
+ (word, index) => !word.startsWith(FLAG_PREFIX3) && !is_script_value(words, index)
1408
+ );
1409
+ if (scripts.length > 0) return { scripts, files: operands };
1410
+ const [script, ...files] = operands;
1411
+ return { scripts: script === void 0 ? [] : [script], files };
1412
+ }
1413
+ function substitute_parts(script) {
1414
+ if (!script.startsWith(SUBSTITUTE)) return void 0;
1415
+ const parts = script.slice(SUBSTITUTE.length + 1).split(script.charAt(SUBSTITUTE.length));
1416
+ return parts.length === SUBSTITUTE_PARTS ? parts : void 0;
1417
+ }
1418
+ function has_plain_flags(parts) {
1419
+ return PLAIN_FLAGS.has(parts.at(-1) ?? "");
1420
+ }
1421
+ function is_plain(parts) {
1422
+ const [pattern = "", replacement = ""] = parts;
1423
+ return pattern !== "" && has_plain_flags(parts) && !META_CHARACTER.test(pattern + replacement);
1424
+ }
1425
+ function is_extended(words) {
1426
+ return words.some((word) => word === EXTENDED_LONG || EXTENDED_SHORT.test(word));
1427
+ }
1428
+ function single_script(words) {
1429
+ if (is_extended(words)) return void 0;
1430
+ const { scripts } = sed_call(words);
1431
+ return scripts.length === 1 ? scripts[0] : void 0;
1432
+ }
1433
+ function plain_pattern(words) {
1434
+ const script = single_script(words);
1435
+ const parts = script === void 0 ? void 0 : substitute_parts(script);
1436
+ return parts !== void 0 && is_plain(parts) ? parts[0] : void 0;
1437
+ }
1438
+ function match_count(text, pattern) {
1439
+ return text.split(pattern).length - 1;
1440
+ }
1441
+ function is_unsafe_segment(segment, read2) {
1442
+ const words = sed_words(segment);
1443
+ if (words === void 0) return false;
1444
+ const { files } = sed_call(words);
1445
+ const pattern = plain_pattern(words);
1446
+ if (pattern === void 0 || files.length === 0) return true;
1447
+ return files.some((file) => match_count(read2(file), pattern) !== ONE_MATCH);
1448
+ }
1449
+ function is_unsafe_sed(command, read2 = file_reader.read_file_or_empty) {
1450
+ return shell_segments.segments_of(command).some((segment) => is_unsafe_segment(segment, read2));
1451
+ }
1452
+ function is_in_place_sed(command) {
1453
+ return shell_segments.segments_of(command).some((segment) => sed_words(segment) !== void 0);
1454
+ }
1455
+ function keeps_plain_sed(command) {
1456
+ return shell_segments.segments_of(command).some((segment) => {
1457
+ const words = sed_words(segment);
1458
+ return words !== void 0 && plain_pattern(words) !== void 0;
1459
+ });
1460
+ }
1461
+ var SED_IN_PLACE_REASON = "\u26D4 unsafe `sed -i`: its pattern or replacement carries a meta character (`/ & \\ . * [ ] ^ $`), it is not one plain `s` command (a flag other than `g`, or `-E`), or it does not match exactly once in each target file, so it can rewrite a line you did not mean to. Change the region with the Edit tool. This rule fires on every occurrence (`prompts/collaboration-workflow/file-edits.md`).";
1462
+ var ROW10 = {
1463
+ id: "sed-in-place",
1464
+ is_trigger: bash_triggers.on_bash_command((command) => is_unsafe_sed(command)),
1465
+ reason: SED_IN_PLACE_REASON,
1466
+ decide: () => true,
1467
+ keeps: bash_triggers.on_bash_command(keeps_plain_sed),
1468
+ reaches: bash_triggers.on_bash_command(is_in_place_sed),
1469
+ about: {
1470
+ title: "No `sed -i` its countable conditions do not clear",
1471
+ topic: "`file-edits.md`",
1472
+ fires: "a `sed -i` whose script is not one `s` command, whose pattern or replacement has a meta character, or which does not match exactly once in a target file. Every occurrence",
1473
+ quiet: "not in place, or one plain `s` command matching once in each file"
1474
+ }
1475
+ };
1476
+ var sed_in_place = {
1477
+ ROW: ROW10,
1478
+ is_in_place_sed,
1479
+ is_unsafe_sed,
1480
+ keeps_plain_sed
1481
+ };
1482
+
1483
+ // scripts/rules/file-edit-rows.ts
1484
+ var SHELL_ROWS = [file_body.ROW, sed_in_place.ROW];
1485
+ var file_edit_rows = {
1486
+ FILE_BODY_REASON: file_body.FILE_BODY_REASON,
1487
+ REWRITE_ROW: file_rewrite.ROW,
1488
+ SHELL_ROWS
1489
+ };
1490
+
1357
1491
  // scripts/rules/git-force.ts
1358
1492
  var FORCE_LONG_PREFIX = "--force";
1359
1493
  var DELETE_LONG = "--delete";
@@ -1383,7 +1517,7 @@ function is_force_or_delete(command) {
1383
1517
  return shell_segments.commands_of(command).some((segment) => is_destructive_segment(segment));
1384
1518
  }
1385
1519
  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`).";
1386
- var ROW6 = {
1520
+ var ROW11 = {
1387
1521
  id: "git-force",
1388
1522
  is_trigger: bash_triggers.on_bash_command(is_force_or_delete),
1389
1523
  reason: GIT_FORCE_REASON,
@@ -1397,10 +1531,13 @@ var ROW6 = {
1397
1531
  };
1398
1532
  var git_force = {
1399
1533
  GIT_FORCE_REASON,
1400
- ROW: ROW6,
1534
+ ROW: ROW11,
1401
1535
  is_force_or_delete
1402
1536
  };
1403
1537
 
1538
+ // scripts/rules/implementation-cut.ts
1539
+ import path from "node:path";
1540
+
1404
1541
  // scripts/run/cut/run-cut-decline.ts
1405
1542
  import { z as z3 } from "zod";
1406
1543
  var DECLINE_PREFIX = "josh-run-cut-decline-";
@@ -1499,17 +1636,18 @@ var implementation_cut_verdict = {
1499
1636
  };
1500
1637
 
1501
1638
  // scripts/rules/implementation-cut.ts
1502
- var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write"]);
1503
- function is_edit_tool(name) {
1504
- return EDIT_TOOLS.has(name);
1505
- }
1639
+ var { is_edit_tool: is_edit_tool3 } = edit_tool;
1506
1640
  var CUT_COMMANDS = /* @__PURE__ */ new Set(["run:cut"]);
1507
1641
  var IMPL_FLAG = /(?:^|\s)--impl(?:[=\s]|$)/u;
1508
- function is_impl_cut_segment(segment) {
1509
- return shell_segments.is_josh_command(segment, CUT_COMMANDS) && IMPL_FLAG.test(segment);
1642
+ var RESUME_FLAG = /(?:^|\s)--resume(?:[=\s]|$)/u;
1643
+ function calls_cut_with(command, flag) {
1644
+ return shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, CUT_COMMANDS) && flag.test(segment));
1510
1645
  }
1511
1646
  function takes_the_impl_cut(command) {
1512
- return shell_segments.segments_of(command).some((segment) => is_impl_cut_segment(segment));
1647
+ return calls_cut_with(command, IMPL_FLAG);
1648
+ }
1649
+ function resumes_a_cut(command) {
1650
+ return calls_cut_with(command, RESUME_FLAG);
1513
1651
  }
1514
1652
  function can_cut_sync(issue) {
1515
1653
  return !run_cut_decline.is_declined_sync(issue) && run_cut_ready.is_ready_sync();
@@ -1544,19 +1682,33 @@ function uncut_run_issue(state) {
1544
1682
  if (issue === void 0) return void 0;
1545
1683
  return state.carried()?.issue === issue ? void 0 : issue;
1546
1684
  }
1685
+ function file_path_of(call) {
1686
+ if (!json_value.is_record(call.input)) return "";
1687
+ const value = call.input["file_path"];
1688
+ return typeof value === "string" ? value : "";
1689
+ }
1690
+ function is_handoff_write(call, directory, issue) {
1691
+ const handoff = path.resolve(directory, run_cut_handoff.handoff_path_of(issue));
1692
+ return path.resolve(directory, file_path_of(call)) === handoff;
1693
+ }
1694
+ function refusable_issue(call, state) {
1695
+ const issue = uncut_run_issue(state);
1696
+ if (issue === void 0) return void 0;
1697
+ return is_handoff_write(call, state.directory, issue) ? void 0 : issue;
1698
+ }
1547
1699
  function warrants_the_cut(state) {
1548
1700
  const verdict = state.verdict();
1549
1701
  if (lane_child_issue(state) === void 0) return verdict === cost_verdict.OVER_VERDICT;
1550
1702
  return verdict !== cost_verdict.UNDER_VERDICT;
1551
1703
  }
1552
1704
  function is_over_threshold_edit(call, state = current_state()) {
1553
- if (!is_edit_tool(call.name)) return false;
1554
- const issue = uncut_run_issue(state);
1705
+ if (!is_edit_tool3(call.name)) return false;
1706
+ const issue = refusable_issue(call, state);
1555
1707
  if (issue === void 0) return false;
1556
1708
  return warrants_the_cut(state) && state.can_cut(issue);
1557
1709
  }
1558
- var THRESHOLD_TEXT = `${figure_format.format_tokens(CONTEXT_CUT_THRESHOLD)}-token`;
1559
- 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\`).`;
1710
+ var THRESHOLD_TEXT = "`pnpm josh cost --cut`";
1711
+ var IMPLEMENTATION_CUT_REASON = `\u26D4 implementation-phase cut: refused on every edit over ${THRESHOLD_TEXT}. Write \`${run_cut_handoff.HANDOFF_PATH}\`: ${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\`).`;
1560
1712
  var REISSUE_WINDOW_MS = 9e4;
1561
1713
  function is_reissued_refusal(now_ms, delivered_at_ms) {
1562
1714
  return now_ms - delivered_at_ms < REISSUE_WINDOW_MS;
@@ -1564,12 +1716,11 @@ function is_reissued_refusal(now_ms, delivered_at_ms) {
1564
1716
  function decide2(_call, run, _can_record, delivered_at_ms) {
1565
1717
  return !is_reissued_refusal(run.now_ms, delivered_at_ms);
1566
1718
  }
1567
- var ROW7 = {
1719
+ var ROW12 = {
1568
1720
  id: "implementation-cut",
1569
1721
  is_trigger: is_over_threshold_edit,
1570
1722
  reason: IMPLEMENTATION_CUT_REASON,
1571
1723
  decide: decide2,
1572
- keeps: bash_triggers.on_bash_command(takes_the_impl_cut),
1573
1724
  // The opening the reason carried before it was cut to the action and the next command.
1574
1725
  former_reasons: [
1575
1726
  "\u26D4 implementation-phase cut: this checkout is a lane dispatched",
@@ -1579,146 +1730,131 @@ var ROW7 = {
1579
1730
  title: "The implementation-phase cut",
1580
1731
  topic: '`.claude/skills/workflow-commands/pre-gate-cut.md` \u2192 "It is a guard, fired at the edit that crosses the threshold"',
1581
1732
  fires: "an `Edit` / `Write` by an uncut lane child whose recent context is over the `pnpm josh cost --cut` threshold; it hands over `pnpm josh run:cut --impl <N> --handoff <path>`. At each threshold crossing",
1582
- quiet: "not a lane child, a non-editing tool, already cut, or under the threshold"
1733
+ quiet: "not a lane child, a non-editing tool, the run's own handoff file, already cut, or under the threshold"
1583
1734
  }
1584
1735
  };
1585
1736
  var implementation_cut = {
1586
1737
  IMPLEMENTATION_CUT_REASON,
1587
1738
  REISSUE_WINDOW_MS,
1588
- ROW: ROW7,
1739
+ ROW: ROW12,
1589
1740
  decide: decide2,
1590
1741
  is_over_threshold_edit,
1591
1742
  is_reissued_refusal,
1743
+ resumes_a_cut,
1592
1744
  takes_the_impl_cut,
1593
1745
  uncut_run_issue
1594
1746
  };
1595
1747
 
1596
- // scripts/rules/prior-comment-read.ts
1597
- var ISSUE_PATH_NUMBER = /issues\/(\d+)/gu;
1598
- var ISSUE_VIEW_NUMBER = /\bissue\s+view\s+(?:-{1,2}\S+\s+)*(\d+)/u;
1599
- var ISSUE_READ_ARGUMENTS = /\b(?:issue:read|run:entry|run:prep)\b([\s\d]*)/u;
1600
- var COMMENTS_ENDPOINT = /issues\/\d+\/comments\b/u;
1601
- var VIEW_WITH_COMMENTS = /\bissue\s+view\b[^\n]*--comments\b/u;
1602
- var ISSUE_READ_COMMAND = /\b(?:issue:read|run:entry|run:prep)\b/u;
1603
- function add_issue_read_numbers(command, numbers) {
1604
- const match = ISSUE_READ_ARGUMENTS.exec(command);
1605
- if (match === null) return;
1606
- const tokens = (match[1] ?? "").split(/\s+/u);
1607
- for (const token of tokens) {
1608
- if (issue_number_shape.ISSUE_NUMBER_PATTERN.test(token)) numbers.add(Number(token));
1609
- }
1748
+ // scripts/rules/investigator-foreground.ts
1749
+ var INVESTIGATOR_FOREGROUND_REASON = "\u26D4 investigator foreground: a background `investigator` leaves its result queued until a later turn. Reissue the call with `run_in_background: false`, and put the reads of files this run will edit in the same message. It fires on every occurrence (`.claude/skills/workflow-commands/delegation.md`).";
1750
+ var BACKGROUND_FIELD = "run_in_background";
1751
+ var BODY_FIELDS = ["description", "prompt"];
1752
+ var GUARD_LABEL = time_transcript_line.guard_from_refusal(INVESTIGATOR_FOREGROUND_REASON);
1753
+ var NO_FIELD_STAMP = hook_decision.create_refusal_stamp("josh-investigator-no-field-");
1754
+ function is_investigator(call) {
1755
+ if (!agent_model.SUBAGENT_TOOLS.has(call.name) || !json_value.is_record(call.input)) return false;
1756
+ const type = call.input["subagent_type"];
1757
+ return typeof type === "string" && type.split(":").at(-1) === delegation_policy.INVESTIGATOR_AGENT;
1610
1758
  }
1611
- function issue_numbers_of(command) {
1612
- const numbers = /* @__PURE__ */ new Set();
1613
- for (const match of command.matchAll(ISSUE_PATH_NUMBER)) numbers.add(Number(match[1]));
1614
- const view = ISSUE_VIEW_NUMBER.exec(command);
1615
- if (view !== null) numbers.add(Number(view[1]));
1616
- add_issue_read_numbers(command, numbers);
1617
- return numbers;
1759
+ function field_of(input, key) {
1760
+ return json_value.is_record(input) ? input[key] : void 0;
1618
1761
  }
1619
- function fetches_comments(command) {
1620
- return COMMENTS_ENDPOINT.test(command) || VIEW_WITH_COMMENTS.test(command) || ISSUE_READ_COMMAND.test(command);
1762
+ function is_foreground(call) {
1763
+ return field_of(call.input, BACKGROUND_FIELD) === false;
1621
1764
  }
1622
- function reads_target_comments(command, target) {
1623
- if (!fetches_comments(command)) return false;
1624
- for (const number of issue_numbers_of(command)) {
1625
- if (target.has(number)) return true;
1626
- }
1627
- return false;
1765
+ function is_background_investigator(call) {
1766
+ return is_investigator(call) && !is_foreground(call);
1628
1767
  }
1629
- function already_read_comments_for(tail, command) {
1630
- const target = issue_numbers_of(command);
1631
- if (target.size === 0) return false;
1632
- return tail_commands.prior_bash_commands(tail).some((prior) => reads_target_comments(prior, target));
1768
+ function keeps_foreground(call) {
1769
+ return is_investigator(call) && is_foreground(call);
1633
1770
  }
1634
- function already_read_for_call(tail, call) {
1635
- if (call.name !== cost_blocks.BASH_TOOL) return false;
1636
- return already_read_comments_for(tail, time_shell.bash_command(call.input));
1771
+ function is_untyped_field(name, input) {
1772
+ if (!agent_model.SUBAGENT_TOOLS.has(name) || !json_value.is_record(input)) return false;
1773
+ return typeof input[BACKGROUND_FIELD] === "string";
1637
1774
  }
1638
- var prior_comment_read = {
1639
- already_read_comments_for,
1640
- already_read_for_call,
1641
- fetches_comments,
1642
- issue_numbers_of
1643
- };
1644
-
1645
- // scripts/rules/issue-comments.ts
1646
- var ISSUE_VIEW_COMMAND = new RegExp(String.raw`^gh\s+${gh_api.GH_FLAGS}issue\s+view\s`, "u");
1647
- var ISSUE_BODY_PATH = /repos\/[^\s'"]*\/issues\/\d+(?=$|["'\s])/u;
1648
- var FETCHES_COMMENTS = /--comments\b|--json\s[\w,]*\bcomments\b|\/comments\b/u;
1649
- var ISSUES_PATH = /repos\/[^\s'"]*\/issues\//u;
1650
- var FIELD_PROJECTION = /(?:--jq|--json)[= ]\s*('[^']*'|"[^"]*"|\S+)/u;
1651
- var NAMES_THE_BODY = /\bbody\b/u;
1652
- function projects_away_body(segment) {
1653
- const projection = FIELD_PROJECTION.exec(segment);
1654
- return projection !== null && !NAMES_THE_BODY.test(projection[1] ?? "");
1775
+ function placed_of(lines) {
1776
+ return lines.flatMap(
1777
+ (line) => line.blocks.map((block) => ({ block, message_id: line.message_id, at_ms: line.timestamp_ms }))
1778
+ );
1655
1779
  }
1656
- function is_body_read_segment(segment) {
1657
- if (projects_away_body(segment)) return false;
1658
- if (ISSUE_VIEW_COMMAND.test(segment)) return true;
1659
- return gh_api.is_gh_api(segment) && ISSUE_BODY_PATH.test(segment) && gh_api.is_read(segment);
1780
+ function results_of(placed) {
1781
+ const results = placed.filter(({ block }) => block.result_id !== "");
1782
+ const refused = results.filter(({ block }) => block.refusal_guard === GUARD_LABEL);
1783
+ return {
1784
+ answered: new Set(results.map(({ block }) => block.result_id)),
1785
+ refused: new Map(refused.map(({ block, at_ms }) => [block.result_id, at_ms]))
1786
+ };
1660
1787
  }
1661
- function fetches_issue_comments(segment) {
1662
- if (!FETCHES_COMMENTS.test(segment)) return false;
1663
- return ISSUE_VIEW_COMMAND.test(segment) || gh_api.is_gh_api(segment) && ISSUES_PATH.test(segment);
1788
+ function writes_foreground(block) {
1789
+ return String(field_of(block.input, BACKGROUND_FIELD)) === "false";
1664
1790
  }
1665
- function is_body_only_issue_read(command) {
1666
- const segments = shell_segments.segments_of(command);
1667
- if (segments.some((segment) => fetches_issue_comments(segment))) return false;
1668
- return segments.some((segment) => is_body_read_segment(segment));
1791
+ function has_same_body(block, call) {
1792
+ return BODY_FIELDS.every((key) => field_of(block.input, key) === field_of(call.input, key));
1669
1793
  }
1670
- function reads_issue_comments(command) {
1671
- return shell_segments.segments_of(command).some((segment) => fetches_issue_comments(segment));
1794
+ function is_pending_foreground(block, results) {
1795
+ return !results.answered.has(block.id) && writes_foreground(block);
1672
1796
  }
1673
- var RULE_DOC = "`.claude/skills/workflow-commands/issue-comments.md`";
1674
- var COMMENTS_FALLBACK = "gh api repos/{owner}/{repo}/issues/<N>/comments";
1675
- var ISSUE_COMMENTS_REASON = `\u26D4 issue comments: pnpm josh issue:read <N>
1676
- 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}).`;
1677
- var COMMENTS_JQ = String.raw`'.[] | "— \(.user.login) \(.created_at)\n\(.body)\n"'`;
1678
- 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}).`;
1679
- function rewritable_path(command) {
1680
- const read2 = shell_segments.first_of(command);
1681
- if (read2 === void 0 || ISSUE_VIEW_COMMAND.test(read2)) return void 0;
1682
- return is_body_read_segment(read2) ? ISSUE_BODY_PATH.exec(read2)?.[0] : void 0;
1797
+ function is_same_dispatch(block, call, results) {
1798
+ if (!is_investigator(block) || !has_same_body(block, call)) return false;
1799
+ return results.refused.has(block.id) || is_pending_foreground(block, results);
1683
1800
  }
1684
- function comments_command(command) {
1685
- if (!is_body_only_issue_read(command)) return void 0;
1686
- const path5 = rewritable_path(command);
1687
- if (path5 === void 0) return void 0;
1688
- return `gh api --paginate ${path5}/comments --jq ${COMMENTS_JQ}
1689
- ${command}`;
1801
+ function is_dropped_foreground(block, results) {
1802
+ return results.refused.has(block.id) && is_investigator(block) && writes_foreground(block);
1690
1803
  }
1691
- function comments_input(call, tail) {
1692
- if (call.name !== cost_blocks.BASH_TOOL || !json_value.is_record(call.input)) return void 0;
1693
- const command = comments_command(time_shell.bash_command(call.input));
1694
- if (command === void 0 || prior_comment_read.already_read_for_call(tail(), call)) {
1695
- return void 0;
1696
- }
1697
- return { ...call.input, command };
1804
+ function refused_at_ms(dispatch, results) {
1805
+ return results.refused.get(dispatch?.block.id ?? "") ?? hook_decision.NEVER_MS;
1698
1806
  }
1699
- var ROW8 = {
1700
- id: "issue-comments",
1701
- is_trigger: bash_triggers.on_bash_command(is_body_only_issue_read),
1702
- reason: ISSUE_COMMENTS_REASON,
1703
- already_satisfied: prior_comment_read.already_read_for_call,
1704
- keeps: bash_triggers.on_bash_command(reads_issue_comments),
1705
- rewrite: comments_input,
1706
- rewrite_note: COMMENTS_NOTE,
1707
- // The opening the reason carried before it led with the command.
1708
- former_reasons: ["\u26D4 an Issue's comments are part of the Issue: read them before implementing"],
1807
+ function is_alone(dispatch, dispatches) {
1808
+ return dispatches.filter(({ message_id }) => message_id === dispatch.message_id).length === 1;
1809
+ }
1810
+ function is_reissue(placed, results, now_ms) {
1811
+ const dispatches = placed.filter(({ block }) => is_investigator(block));
1812
+ const last = dispatches.findLast(({ block }) => results.answered.has(block.id));
1813
+ const at_ms = refused_at_ms(last, results);
1814
+ if (last === void 0 || at_ms === hook_decision.NEVER_MS) return false;
1815
+ return is_alone(last, dispatches) && now_ms - at_ms < implementation_cut.REISSUE_WINDOW_MS;
1816
+ }
1817
+ function tail_shows_no_field(tail, call, now_ms) {
1818
+ const placed = placed_of(time_transcript_line.parse_text(tail));
1819
+ const results = results_of(placed);
1820
+ const has_block_tell = placed.some(
1821
+ ({ block }) => is_untyped_field(block.name, block.input) || is_same_dispatch(block, call, results) || is_dropped_foreground(block, results)
1822
+ );
1823
+ return has_block_tell || is_reissue(placed, results, now_ms);
1824
+ }
1825
+ function remember_no_field(run) {
1826
+ NO_FIELD_STAMP.record(NO_FIELD_STAMP.path(run.transcript), run.now_ms);
1827
+ return true;
1828
+ }
1829
+ function is_remembered(run) {
1830
+ return NO_FIELD_STAMP.last_ms(NO_FIELD_STAMP.path(run.transcript)) !== hook_decision.NEVER_MS;
1831
+ }
1832
+ function lacks_background_field(tail, call, run) {
1833
+ if (field_of(call.input, BACKGROUND_FIELD) === true) return false;
1834
+ if (is_remembered(run)) return true;
1835
+ const has_tell = is_untyped_field(call.name, call.input) || tail_shows_no_field(tail, call, run.now_ms);
1836
+ return has_tell && remember_no_field(run);
1837
+ }
1838
+ var ROW13 = {
1839
+ id: "investigator-foreground",
1840
+ is_trigger: is_background_investigator,
1841
+ reason: INVESTIGATOR_FOREGROUND_REASON,
1842
+ already_satisfied: lacks_background_field,
1843
+ decide: () => true,
1844
+ keeps: keeps_foreground,
1845
+ reaches: is_investigator,
1709
1846
  about: {
1710
- title: "Reading Issue comments",
1711
- topic: "`issue-comments.md`",
1712
- fires: "a `Bash` that reads only the Issue body (`gh issue view <N>`, or a GET ending in `\u2026/issues/<N>`). Refused until it is re-read with the comments",
1713
- quiet: "no Issue read, or a read with `--comments` / `--json comments` / `\u2026/comments` from the start"
1847
+ title: "Investigator dispatched in the foreground",
1848
+ topic: '`.claude/skills/workflow-commands/delegation.md` \u2192 "The pre-implementation reading \u2014 what goes to a unit, and from which file"',
1849
+ fires: "an `Agent` / `Task` call of the `investigator` type without `run_in_background: false`. Every occurrence",
1850
+ quiet: "another agent type, `run_in_background: false` set, or a session whose subagent tool declares no `run_in_background` (the field written as a string, the tail holding the same description and prompt, a refused dispatch that wrote it `false`, or a reissue still without the field just after this row refused a lone dispatch) \u2014 remembered for the session"
1714
1851
  }
1715
1852
  };
1716
- var issue_comments = {
1717
- COMMENTS_NOTE,
1718
- ISSUE_COMMENTS_REASON,
1719
- ROW: ROW8,
1720
- comments_input,
1721
- is_body_only_issue_read
1853
+ var investigator_foreground = {
1854
+ INVESTIGATOR_FOREGROUND_REASON,
1855
+ ROW: ROW13,
1856
+ is_background_investigator,
1857
+ lacks_background_field
1722
1858
  };
1723
1859
 
1724
1860
  // scripts/rules/josh-git-bare.ts
@@ -1735,21 +1871,21 @@ function is_bare_josh_git(command) {
1735
1871
  return shell_segments.segments_of(command).some((segment) => is_bare_segment(segment));
1736
1872
  }
1737
1873
  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.';
1738
- var ROW9 = {
1874
+ var ROW14 = {
1739
1875
  id: "josh-git-bare",
1740
1876
  is_trigger: bash_triggers.on_bash_command(is_bare_josh_git),
1741
1877
  reason: JOSH_GIT_BARE_REASON,
1742
1878
  decide: () => true,
1743
1879
  about: {
1744
1880
  title: "Bare `pnpm josh git`",
1745
- topic: "`CLAUDE.md` \u2192 Git Rules",
1881
+ topic: '`operating-rules.md` \u2192 "git-rules"',
1746
1882
  fires: "`pnpm josh git` without `-y`, whose staging prompt cancels with no TTY. Every occurrence",
1747
1883
  quiet: "`pnpm josh git -y`"
1748
1884
  }
1749
1885
  };
1750
1886
  var josh_git_bare = {
1751
1887
  JOSH_GIT_BARE_REASON,
1752
- ROW: ROW9,
1888
+ ROW: ROW14,
1753
1889
  is_bare_josh_git
1754
1890
  };
1755
1891
 
@@ -1774,7 +1910,7 @@ function is_carry_conflict(command, is_lane_child = lane_child_marker.is_child_o
1774
1910
  return is_lane_child;
1775
1911
  }
1776
1912
  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.";
1777
- var ROW10 = {
1913
+ var ROW15 = {
1778
1914
  id: "lane-carry-conflict",
1779
1915
  is_trigger: bash_triggers.on_bash_command(is_carry_conflict),
1780
1916
  reason: LANE_CARRY_CONFLICT_REASON,
@@ -1788,20 +1924,69 @@ var ROW10 = {
1788
1924
  };
1789
1925
  var lane_carry_conflict = {
1790
1926
  LANE_CARRY_CONFLICT_REASON,
1791
- ROW: ROW10,
1927
+ ROW: ROW15,
1792
1928
  invokes_budget_command,
1793
1929
  is_carry_conflict
1794
1930
  };
1795
1931
 
1932
+ // scripts/rules/lane-direct-tool.ts
1933
+ import os2 from "node:os";
1934
+
1796
1935
  // scripts/gate/command-memory.ts
1797
1936
  import os from "node:os";
1937
+
1938
+ // scripts/gate/core-weights.ts
1939
+ var MEASURED = {
1940
+ lint: { cpu_s: 16.9, wall_s: 14 },
1941
+ type_check: { cpu_s: 31.2, wall_s: 27.3 },
1942
+ spell_check: { cpu_s: 5.7, wall_s: 6.2 },
1943
+ eslint_scan: { cpu_s: 113, wall_s: 98 },
1944
+ format: { cpu_s: 2.1, wall_s: 1.7 },
1945
+ retrospective: { cpu_s: 16.5, wall_s: 19.8 },
1946
+ install: { cpu_s: 1.9, wall_s: 6.1 }
1947
+ };
1948
+ function rounded_up(measurement) {
1949
+ return Math.ceil(measurement.cpu_s / measurement.wall_s);
1950
+ }
1951
+ var CORE_WEIGHTS = {
1952
+ lint: rounded_up(MEASURED.lint),
1953
+ type_check: rounded_up(MEASURED.type_check),
1954
+ spell_check: rounded_up(MEASURED.spell_check),
1955
+ eslint_scan: rounded_up(MEASURED.eslint_scan),
1956
+ format: rounded_up(MEASURED.format),
1957
+ retrospective: rounded_up(MEASURED.retrospective),
1958
+ install: rounded_up(MEASURED.install),
1959
+ e2e: 2
1960
+ };
1961
+ var MEMORY_MB = {
1962
+ lint: 2400,
1963
+ metrics: 1900,
1964
+ type_check: 1300,
1965
+ spell_check: 350,
1966
+ eslint_scan: 2400,
1967
+ unit_worker: 400,
1968
+ format: 300,
1969
+ retrospective: 900,
1970
+ install: 100
1971
+ };
1972
+ var TOOL_MEMORY = {
1973
+ eslint: "lint",
1974
+ tsc: "type_check",
1975
+ cspell: "spell_check",
1976
+ vitest: "unit_worker",
1977
+ prettier: "format",
1978
+ install: "install"
1979
+ };
1980
+ var core_weights = { CORE_WEIGHTS, MEASURED, MEMORY_MB, TOOL_MEMORY };
1981
+
1982
+ // scripts/gate/command-memory.ts
1798
1983
  var JOSH_PROGRAM = "josh";
1799
1984
  var RESERVED_RUN = "reserved-run";
1800
1985
  var COMMAND_SEPARATOR = "--";
1801
1986
  var ESLINT = "eslint";
1802
1987
  var VITEST = "vitest";
1803
1988
  var AUTO_WORKERS = "auto";
1804
- var LINE_BREAK3 = "\n";
1989
+ var LINE_BREAK4 = "\n";
1805
1990
  var SHELL_HEREDOC = /\b(?:ba|z|da|k)?sh(?:\s+-\w+)*\s*<<-?\s*(['"]?)(\w+)\1/u;
1806
1991
  var HEREDOC_DELIMITER_GROUP = 2;
1807
1992
  var AUTO_CORE_SHARE = 2;
@@ -1825,10 +2010,10 @@ function reading_of(segment, is_reserved) {
1825
2010
  }
1826
2011
  function body_until(lines, delimiter) {
1827
2012
  const end = lines.findIndex((body_line) => body_line.trim() === delimiter);
1828
- return (end === -1 ? lines : lines.slice(0, end)).join(LINE_BREAK3);
2013
+ return (end === -1 ? lines : lines.slice(0, end)).join(LINE_BREAK4);
1829
2014
  }
1830
2015
  function shell_heredoc_bodies(line) {
1831
- const lines = line.split(LINE_BREAK3);
2016
+ const lines = line.split(LINE_BREAK4);
1832
2017
  return lines.flatMap((opening, index) => {
1833
2018
  const delimiter = SHELL_HEREDOC.exec(opening)?.[HEREDOC_DELIMITER_GROUP];
1834
2019
  return delimiter === void 0 ? [] : body_until(lines.slice(index + 1), delimiter);
@@ -1885,34 +2070,116 @@ function runs_multi_worker_eslint(line, cores = os.availableParallelism()) {
1885
2070
  (call) => call.name === ESLINT && workers_of(call, cores) > SINGLE_WORKER
1886
2071
  );
1887
2072
  }
1888
- var command_memory = { estimated_memory_mb, runs_multi_worker_eslint, tool_calls_of };
2073
+ var command_memory = { estimated_memory_mb, runs_multi_worker_eslint, tool_calls_of, workers_of };
2074
+
2075
+ // scripts/rules/timed-rerun.ts
2076
+ var TIMED_SEGMENT = /^\s*(?:[A-Za-z_]\w*=\S*\s+)*(?:\S*\/)?(?:time|hyperfine)\s/u;
2077
+ function is_timed_run(command) {
2078
+ return shell_segments.quote_aware_commands_of(command).some((segment) => TIMED_SEGMENT.test(segment));
2079
+ }
2080
+ var RECORD_READ = /\bgh\s+api\b.*\/actions\/runs\b|\bjosh\s+(?:lane:stats|lst)\b/u;
2081
+ function reads_the_record(command) {
2082
+ return RECORD_READ.test(command);
2083
+ }
2084
+ function measures_duration(command) {
2085
+ return reads_the_record(command) || is_timed_run(command);
2086
+ }
2087
+ var TIMED_RERUN_REASON = "\u26D4 timed re-run: a CI or gate duration is already on record \u2014 read the Actions jobs with `gh api \u2026/actions/runs/<run_id>/jobs` or the gate timings with `pnpm josh lane:stats`. Re-run only when the record cannot answer: reissue the same command once, and post the result on the Issue (`prompts/collaboration-workflow/measurement.md`).";
2088
+ var ROW16 = {
2089
+ id: "timed-rerun",
2090
+ is_trigger: bash_triggers.on_bash_command(is_timed_run),
2091
+ reason: TIMED_RERUN_REASON,
2092
+ keeps: bash_triggers.on_bash_command(reads_the_record),
2093
+ reaches: bash_triggers.on_bash_command(measures_duration),
2094
+ about: {
2095
+ title: "Timed re-run",
2096
+ topic: "`measurement.md`",
2097
+ fires: "a command timed with `time` / `/usr/bin/time` / `hyperfine`",
2098
+ quiet: "the duration is read from the Actions jobs or `pnpm josh lane:stats`"
2099
+ }
2100
+ };
2101
+ var timed_rerun = {
2102
+ ROW: ROW16,
2103
+ TIMED_RERUN_REASON,
2104
+ is_timed_run,
2105
+ reads_the_record
2106
+ };
1889
2107
 
1890
2108
  // scripts/rules/lane-direct-tool.ts
1891
- var HEAVY_TOOLS = /* @__PURE__ */ new Set(["eslint", "vitest", "cspell", "tsc"]);
2109
+ var ESLINT2 = "eslint";
2110
+ var VITEST2 = "vitest";
2111
+ var HEAVY_TOOLS = /* @__PURE__ */ new Set([ESLINT2, VITEST2, "cspell", "tsc"]);
2112
+ var TOOL_WEIGHTS = {
2113
+ [ESLINT2]: core_weights.CORE_WEIGHTS.lint,
2114
+ tsc: core_weights.CORE_WEIGHTS.type_check,
2115
+ cspell: core_weights.CORE_WEIGHTS.spell_check
2116
+ };
2117
+ var SINGLE_CORE = 1;
2118
+ var RESERVED_RUN2 = "pnpm josh reserved-run";
2119
+ var MULTI_COMMAND_SYNTAX = /[;|&<>()`#\n]|\$\(/u;
2120
+ var KEPT_REDIRECT = /2>&1/gu;
2121
+ var ENV_ASSIGNMENT = /^[A-Za-z_]\w*=/u;
2122
+ var RESERVED_NOTE = "\u21BB lane direct tool: a lane child ran a heavy tool directly, so the line was wrapped in `pnpm josh reserved-run <weight> --` to claim its cores beside the other lanes. Prefer `pnpm josh lint:related` / `test:related` / `cspell:dot` \u2014 `docs/josh-commands-automation.md`.";
2123
+ function direct_calls(command) {
2124
+ return command_memory.tool_calls_of(command).filter((call) => !call.is_reserved && HEAVY_TOOLS.has(call.name));
2125
+ }
1892
2126
  function runs_heavy_tool(command) {
1893
- return command_memory.tool_calls_of(command).some((call) => !call.is_reserved && HEAVY_TOOLS.has(call.name));
2127
+ return direct_calls(command).length > 0;
2128
+ }
2129
+ function weight_of(call, cores) {
2130
+ if (call.name === VITEST2) return command_memory.workers_of(call, cores);
2131
+ return TOOL_WEIGHTS[call.name] ?? SINGLE_CORE;
2132
+ }
2133
+ function is_one_command(command) {
2134
+ const judged = time_shell.blanked(command);
2135
+ if (judged === void 0) return false;
2136
+ return !MULTI_COMMAND_SYNTAX.test(judged.replaceAll(KEPT_REDIRECT, " ")) && !ENV_ASSIGNMENT.test(judged);
2137
+ }
2138
+ function can_wrap(line, cores) {
2139
+ if (!is_one_command(line) || timed_rerun.is_timed_run(line)) return false;
2140
+ return !command_memory.runs_multi_worker_eslint(line, cores);
2141
+ }
2142
+ function reserved_command(command, cores = os2.availableParallelism()) {
2143
+ const line = command.trim();
2144
+ const calls = direct_calls(line);
2145
+ if (!can_wrap(line, cores) || calls.length === 0) return void 0;
2146
+ const weight = Math.max(...calls.map((call) => weight_of(call, cores)));
2147
+ return `${RESERVED_RUN2} ${String(weight)} -- ${line}`;
2148
+ }
2149
+ function is_lane_bash(call) {
2150
+ return call.name === cost_blocks.BASH_TOOL && lane_child_marker.is_child_of(process.cwd());
2151
+ }
2152
+ function reserved_input(call) {
2153
+ if (!is_lane_bash(call) || !json_value.is_record(call.input)) return void 0;
2154
+ const command = reserved_command(time_shell.bash_command(call.input));
2155
+ return command === void 0 ? void 0 : { ...call.input, command };
1894
2156
  }
1895
2157
  function is_lane_direct_tool(command, is_lane_child = lane_child_marker.is_child_of(process.cwd())) {
1896
2158
  if (!is_lane_child) return false;
1897
2159
  return runs_heavy_tool(command) || command_memory.runs_multi_worker_eslint(command);
1898
2160
  }
1899
- var LANE_DIRECT_TOOL_REASON = "\u26D4 lane child running `eslint` / `vitest` / `cspell` / `tsc` directly, or multi-worker eslint: it overlaps the other lanes. Run `pnpm josh lint:related`, `pnpm josh test:related`, `pnpm josh cspell:dot` or `pnpm josh reserved-run <weight> -- <command>`, eslint at `--concurrency off`; measure concurrency outside a lane. Refused on every occurrence.";
1900
- var ROW11 = {
2161
+ var LANE_DIRECT_TOOL_REASON = "\u26D4 lane child running `eslint` / `vitest` / `cspell` / `tsc` directly, or multi-worker eslint: it overlaps the other lanes. Run `pnpm josh lint:related`, `pnpm josh test:related`, `pnpm josh cspell:dot` or `pnpm josh reserved-run <weight> -- <command>`, eslint at `--concurrency off`. Only a one-command line is wrapped for you. Refused on every occurrence.";
2162
+ var ROW17 = {
1901
2163
  id: "lane-direct-tool",
1902
2164
  is_trigger: bash_triggers.on_bash_command(is_lane_direct_tool),
1903
2165
  reason: LANE_DIRECT_TOOL_REASON,
1904
2166
  decide: () => true,
2167
+ rewrite: reserved_input,
2168
+ rewrite_note: RESERVED_NOTE,
1905
2169
  about: {
1906
2170
  title: "Lane child running a heavy tool directly",
1907
2171
  topic: "`docs/josh-commands-automation.md` \u2192 `josh reserved-run`",
1908
- fires: "a dispatched lane child launching `eslint` / `vitest` / `cspell` / `tsc` (bare, `pnpm exec`, `npx`, inside `sh -c`), or eslint with `--concurrency` other than `off` / `1` by any path, `reserved-run` included. Every occurrence",
2172
+ fires: "a dispatched lane child launching `eslint` / `vitest` / `cspell` / `tsc` (bare, `pnpm exec`, `npx`, inside `sh -c`), or eslint with `--concurrency` other than `off` / `1` by any path, `reserved-run` included. A one-command line is rewritten behind `pnpm josh reserved-run <weight> --` at the gate's weight; a chained, piped, redirected or env-prefixed line and a multi-worker eslint are refused, every occurrence",
1909
2173
  quiet: "not a lane child, or the run goes through `pnpm josh` with single-worker eslint"
1910
2174
  }
1911
2175
  };
1912
2176
  var lane_direct_tool = {
1913
2177
  LANE_DIRECT_TOOL_REASON,
1914
- ROW: ROW11,
2178
+ RESERVED_NOTE,
2179
+ ROW: ROW17,
1915
2180
  is_lane_direct_tool,
2181
+ reserved_command,
2182
+ reserved_input,
1916
2183
  runs_heavy_tool
1917
2184
  };
1918
2185
 
@@ -1922,19 +2189,69 @@ function is_interactive_ask(call, is_lane_child = lane_child_marker.is_child_of(
1922
2189
  return is_lane_child;
1923
2190
  }
1924
2191
  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: `pnpm josh run:park <N> --body-file <path>` (it applies `needs-decision`), the `confirmation` Telegram, stop. It fires on every occurrence (`.claude/skills/workflow-commands/pre-gate-cut.md`, `backlogrun-park.md`).";
1925
- var ROW12 = {
2192
+ var ROW18 = {
1926
2193
  id: "lane-interactive-ask",
1927
2194
  is_trigger: is_interactive_ask,
1928
2195
  reason: LANE_INTERACTIVE_ASK_REASON,
1929
2196
  decide: () => true,
2197
+ // A reply asking a person for the git index is the same ask, delivered by the Stop hook.
2198
+ stop_reasons: [stop_rules.LANE_INDEX_REASON],
1930
2199
  about: {
1931
2200
  title: "Lane child interactive ask",
1932
2201
  topic: '`.claude/skills/workflow-commands/pre-gate-cut.md` \u2192 "The interactive ask is refused one call earlier"',
1933
- fires: "a dispatched lane child's `AskUserQuestion`; it hands over the park procedure. Every occurrence",
2202
+ fires: "a dispatched lane child's `AskUserQuestion`; it hands over the park procedure; the Stop hook also blocks a reply asking a person for the git index. Every occurrence",
1934
2203
  quiet: "not a lane child, or not an interactive tool"
1935
2204
  }
1936
2205
  };
1937
- var lane_interactive_ask = { LANE_INTERACTIVE_ASK_REASON, ROW: ROW12, is_interactive_ask };
2206
+ var lane_interactive_ask = { LANE_INTERACTIVE_ASK_REASON, ROW: ROW18, is_interactive_ask };
2207
+
2208
+ // scripts/rules/lane-spilled-read.ts
2209
+ var SPILLED_RESULT = /\/tool-results\/[^/]+\.txt$/u;
2210
+ function read_input(call) {
2211
+ if (call.name !== "Read" || !json_value.is_record(call.input)) return void 0;
2212
+ return call.input;
2213
+ }
2214
+ function reads_spilled_result(input) {
2215
+ const { file_path: file_path2 } = input;
2216
+ return typeof file_path2 === "string" && SPILLED_RESULT.test(file_path2);
2217
+ }
2218
+ function is_paginated(input) {
2219
+ return input["offset"] !== void 0 || input["limit"] !== void 0;
2220
+ }
2221
+ function reaches_spilled_result(call) {
2222
+ const input = read_input(call);
2223
+ return input !== void 0 && reads_spilled_result(input);
2224
+ }
2225
+ function keeps_narrowed_read(call) {
2226
+ const input = read_input(call);
2227
+ return input !== void 0 && reads_spilled_result(input) && is_paginated(input);
2228
+ }
2229
+ function is_whole_spilled_read(call, is_lane_child = lane_child_marker.is_child_of(process.cwd())) {
2230
+ if (!reaches_spilled_result(call) || keeps_narrowed_read(call)) return false;
2231
+ return is_lane_child;
2232
+ }
2233
+ var LANE_SPILLED_READ_REASON = "\u26D4 lane child reading a spilled Bash result whole: `tool-results/<id>.txt` is the output that overflowed the cap, and reading it back whole fills the context the cut is measured on. Read only the lines you need: `grep -n <pattern> <path>` or `sed -n <from>,<to>p <path>`. Refused on every occurrence.";
2234
+ var ROW19 = {
2235
+ id: "lane-spilled-read",
2236
+ is_trigger: is_whole_spilled_read,
2237
+ reason: LANE_SPILLED_READ_REASON,
2238
+ decide: () => true,
2239
+ keeps: keeps_narrowed_read,
2240
+ reaches: reaches_spilled_result,
2241
+ about: {
2242
+ title: "Lane child reading a spilled Bash result whole",
2243
+ topic: "`prompts/collaboration-workflow/output-bounds.md`",
2244
+ fires: "a dispatched lane child's `Read` of a `tool-results/<id>.txt` file with no `offset` or `limit`. Every occurrence",
2245
+ quiet: "not a lane child, another file, or a paginated `Read`"
2246
+ }
2247
+ };
2248
+ var lane_spilled_read = {
2249
+ LANE_SPILLED_READ_REASON,
2250
+ ROW: ROW19,
2251
+ is_whole_spilled_read,
2252
+ keeps_narrowed_read,
2253
+ reaches_spilled_result
2254
+ };
1938
2255
 
1939
2256
  // scripts/rules/lane-split-park.ts
1940
2257
  var EPIC_COMMANDS = /* @__PURE__ */ new Set(["epic"]);
@@ -1944,7 +2261,7 @@ var LABEL_TARGETS = [
1944
2261
  /\bissue\s+edit\s+["']?(?<issue>\d+)\b/u,
1945
2262
  /\brun:park\s+["']?(?<issue>\d+)\b/u
1946
2263
  ];
1947
- var ISSUE_URL = /--issue-url[=\s]+["']?([^\s"']+)/u;
2264
+ var ISSUE_URL2 = /--issue-url[=\s]+["']?([^\s"']+)/u;
1948
2265
  var URL_ISSUE = /\/issues\/(\d+)\b/u;
1949
2266
  function promoted_in(segment) {
1950
2267
  if (!shell_segments.is_josh_command(segment, EPIC_COMMANDS)) return void 0;
@@ -1966,7 +2283,7 @@ function is_park_of(command, issue) {
1966
2283
  return lane_park.records_the_park(command) && label_target(command) === issue;
1967
2284
  }
1968
2285
  function notified_issue(command) {
1969
- const [, url = ""] = ISSUE_URL.exec(command) ?? [];
2286
+ const [, url = ""] = ISSUE_URL2.exec(command) ?? [];
1970
2287
  const [, issue] = URL_ISSUE.exec(url) ?? [];
1971
2288
  return issue;
1972
2289
  }
@@ -1985,7 +2302,7 @@ function decide3(_call, run) {
1985
2302
  return issue !== void 0 && promoted_issues(commands).has(issue);
1986
2303
  }
1987
2304
  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").';
1988
- var ROW13 = {
2305
+ var ROW20 = {
1989
2306
  id: "lane-split-park",
1990
2307
  is_trigger: bash_triggers.on_bash_command(is_candidate2),
1991
2308
  reason: LANE_SPLIT_PARK_REASON,
@@ -1999,7 +2316,7 @@ var ROW13 = {
1999
2316
  };
2000
2317
  var lane_split_park = {
2001
2318
  LANE_SPLIT_PARK_REASON,
2002
- ROW: ROW13,
2319
+ ROW: ROW20,
2003
2320
  promoted_issues
2004
2321
  };
2005
2322
 
@@ -2013,13 +2330,13 @@ var NON_COLLIDING_FLAGS = /* @__PURE__ */ new Set([
2013
2330
  "-d",
2014
2331
  "--detach"
2015
2332
  ]);
2016
- var FLAG_PREFIX2 = "-";
2333
+ var FLAG_PREFIX4 = "-";
2017
2334
  function switch_target(segment) {
2018
2335
  const call = git_argv.parse(segment);
2019
2336
  if (call?.subcommand !== SWITCH_SUBCOMMAND || call.args.some((argument) => NON_COLLIDING_FLAGS.has(argument))) {
2020
2337
  return void 0;
2021
2338
  }
2022
- return call.args.find((argument) => !argument.startsWith(FLAG_PREFIX2));
2339
+ return call.args.find((argument) => !argument.startsWith(FLAG_PREFIX4));
2023
2340
  }
2024
2341
  function switch_targets(command) {
2025
2342
  return shell_segments.segments_of(command).map((segment) => switch_target(segment)).filter((target) => target !== void 0);
@@ -2032,8 +2349,8 @@ function is_switch_away_from_lane(command, is_lane_child = lane_child_marker.is_
2032
2349
  if (!is_lane_child || own_branch === void 0) return false;
2033
2350
  return switch_targets(command).some((target) => target !== own_branch);
2034
2351
  }
2035
- 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`).";
2036
- var ROW14 = {
2352
+ 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 ship`'s sync brings the default in before its gate. Refused on every occurrence (`backlogrun-lanes.md`, `backlogrun-child.md`).";
2353
+ var ROW21 = {
2037
2354
  id: "lane-switch-main",
2038
2355
  is_trigger: bash_triggers.on_bash_command(is_switch_away_from_lane),
2039
2356
  reason: LANE_SWITCH_MAIN_REASON,
@@ -2047,7 +2364,7 @@ var ROW14 = {
2047
2364
  };
2048
2365
  var lane_switch_main = {
2049
2366
  LANE_SWITCH_MAIN_REASON,
2050
- ROW: ROW14,
2367
+ ROW: ROW21,
2051
2368
  is_switch_away_from_lane,
2052
2369
  switch_target,
2053
2370
  switch_targets
@@ -2225,7 +2542,7 @@ var DECISION_ORACLES = [
2225
2542
  {
2226
2543
  name: "review:round2",
2227
2544
  decision: "Whether the second /code-review round is due",
2228
- args: "[--round-1-closed]",
2545
+ args: "[--record <N>]",
2229
2546
  vocabulary: [REQUIRED, SKIP],
2230
2547
  single_source: CHAIN_RULE_MD
2231
2548
  },
@@ -2465,7 +2782,7 @@ function command_line(oracle) {
2465
2782
  const command = decision_oracle.get_command(oracle);
2466
2783
  return oracle.args ? `pnpm josh ${command} ${oracle.args}` : `pnpm josh ${command}`;
2467
2784
  }
2468
- function reason_for2(oracle, firing_point) {
2785
+ function reason_for(oracle, firing_point) {
2469
2786
  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.`;
2470
2787
  }
2471
2788
  var ABOUT = {
@@ -2479,14 +2796,14 @@ function row_for(oracle, firing_point) {
2479
2796
  return {
2480
2797
  id: `oracle-consulted:${oracle.name}`,
2481
2798
  is_trigger: bash_triggers.on_bash_command(firing_point.governs),
2482
- reason: reason_for2(oracle, firing_point),
2799
+ reason: reason_for(oracle, firing_point),
2483
2800
  already_satisfied: (tail, _call) => tail_commands.prior_bash_commands(tail).some((command) => runs_the_oracle(command, names)),
2484
2801
  keeps: bash_triggers.on_bash_command((command) => runs_the_oracle(command, names)),
2485
2802
  about: ABOUT
2486
2803
  };
2487
2804
  }
2488
2805
  var ROWS = oracle_firing.firing_oracles().map(({ oracle, firing_point }) => row_for(oracle, firing_point));
2489
- var oracle_consulted = { ROWS, reason_for: reason_for2 };
2806
+ var oracle_consulted = { ROWS, reason_for };
2490
2807
 
2491
2808
  // scripts/rules/destructive-command.ts
2492
2809
  var RM_COMMAND = "rm";
@@ -2502,34 +2819,28 @@ function is_recursive_forced_rm(segment) {
2502
2819
  if (args === void 0) return false;
2503
2820
  return has_flag(args, RECURSIVE_LONG, RECURSIVE_LETTERS) && has_flag(args, FORCE_LONG, FORCE_LETTER);
2504
2821
  }
2505
- var GH_COMMAND2 = "gh";
2822
+ var GH_COMMAND3 = "gh";
2506
2823
  var REPO_GROUP2 = "repo";
2507
2824
  var REPO_REMOVALS = /* @__PURE__ */ new Set(["delete", "archive"]);
2508
- var REPO_EDIT = "edit";
2509
- var VISIBILITY_FLAG = "--visibility";
2510
2825
  var PR_GROUP2 = "pr";
2511
2826
  var PR_CLOSE = "close";
2512
- function is_destructive_repo_action(action, args) {
2513
- if (REPO_REMOVALS.has(action)) return true;
2514
- return action === REPO_EDIT && args.some((argument) => argument.startsWith(VISIBILITY_FLAG));
2515
- }
2516
2827
  function is_destructive_gh_command(args) {
2517
2828
  const [group, action = ""] = args;
2518
2829
  if (group === PR_GROUP2) return action === PR_CLOSE;
2519
- return group === REPO_GROUP2 && is_destructive_repo_action(action, args);
2830
+ return group === REPO_GROUP2 && REPO_REMOVALS.has(action);
2520
2831
  }
2521
2832
  var DELETE_METHOD = "DELETE";
2522
2833
  var REPOSITORY_PATH = "repos/";
2523
2834
  var ISSUE_LABEL_PATH = /^['"]?\/?repos\/[^/\s'"]+\/[^/\s'"]+\/issues\/\d+\/labels\/[^/\s'"]+['"]?$/u;
2524
2835
  function is_destructive_api_call(args) {
2525
- const call = [GH_COMMAND2, ...args].join(" ");
2836
+ const call = [GH_COMMAND3, ...args].join(" ");
2526
2837
  if (!gh_api.is_gh_api(call) || gh_api.method_of(call) !== DELETE_METHOD) return false;
2527
2838
  return args.some(
2528
2839
  (argument) => argument.includes(REPOSITORY_PATH) && !ISSUE_LABEL_PATH.test(argument)
2529
2840
  );
2530
2841
  }
2531
2842
  function is_destructive_gh(segment) {
2532
- const args = git_argv.arguments_of(segment, GH_COMMAND2);
2843
+ const args = git_argv.arguments_of(segment, GH_COMMAND3);
2533
2844
  if (args === void 0) return false;
2534
2845
  return is_destructive_gh_command(args) || is_destructive_api_call(args);
2535
2846
  }
@@ -2539,8 +2850,8 @@ function is_destructive_segment2(segment) {
2539
2850
  function is_destructive_command(command) {
2540
2851
  return shell_segments.commands_of(command).some((segment) => is_destructive_segment2(segment));
2541
2852
  }
2542
- 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`).";
2543
- var ROW15 = {
2853
+ var DESTRUCTIVE_COMMAND_REASON = "\u26D4 destructive command: a recursive forced `rm`, a `gh repo delete` / `archive`, `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`).";
2854
+ var ROW22 = {
2544
2855
  id: "destructive-command",
2545
2856
  is_trigger: bash_triggers.on_bash_command(is_destructive_command),
2546
2857
  reason: DESTRUCTIVE_COMMAND_REASON,
@@ -2554,7 +2865,7 @@ var ROW15 = {
2554
2865
  quiet: "`gh issue close` or removing an Issue label \u2014 the workflow's own steps"
2555
2866
  }
2556
2867
  };
2557
- var destructive_command = { DESTRUCTIVE_COMMAND_REASON, ROW: ROW15, is_destructive_command };
2868
+ var destructive_command = { DESTRUCTIVE_COMMAND_REASON, ROW: ROW22, is_destructive_command };
2558
2869
 
2559
2870
  // scripts/rules/worktree-guard.ts
2560
2871
  var CHECKOUT_SUBCOMMAND = "checkout";
@@ -2615,21 +2926,25 @@ function is_unauthorized_segment(segment) {
2615
2926
  function is_unauthorized_worktree_change(command) {
2616
2927
  return shell_segments.commands_of(command).some((segment) => is_unauthorized_segment(segment));
2617
2928
  }
2618
- var WORKTREE_MUTATION_REASON = '\u26D4 unauthorized working-tree change: `git checkout --`, `git restore`, `git clean -f`, `git stash`. A documented flow stashes with `pnpm josh stash:push "<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`).';
2619
- var ROW16 = {
2929
+ var SINGLE_FILE_RESTORE = "git diff -- <path> | git apply -R";
2930
+ var WORKTREE_MUTATION_REASON = `\u26D4 unauthorized working-tree change: undo only this run's edit: \`${SINGLE_FILE_RESTORE}\`. A documented flow stashes with \`pnpm josh stash:push "<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\`).`;
2931
+ var FORMER_OPENING = "\u26D4 unauthorized working-tree change: `git checkout --`, `git restore`";
2932
+ var ROW23 = {
2620
2933
  id: "worktree-mutation",
2621
2934
  is_trigger: bash_triggers.on_bash_command(is_unauthorized_worktree_change),
2622
2935
  reason: WORKTREE_MUTATION_REASON,
2936
+ former_reasons: [FORMER_OPENING],
2623
2937
  decide: () => true,
2624
2938
  about: {
2625
2939
  title: "Unauthorized work-tree mutation",
2626
2940
  topic: "`operating-rules.md`",
2627
2941
  fires: "`git checkout -- <path>` / `git restore <path>` / forced `git clean -f` / an unauthorized `git stash` (bare, push without a message, positional pop). Every occurrence",
2628
- quiet: "`git stash push -m`, `git stash list`, `pnpm josh git`, `pnpm josh stash:push`, `pnpm josh stash:pop`"
2942
+ quiet: `\`git stash push -m\`, \`git stash list\`, \`pnpm josh git\`, \`pnpm josh stash:push\`, \`pnpm josh stash:pop\`, \`${SINGLE_FILE_RESTORE}\``
2629
2943
  }
2630
2944
  };
2631
2945
  var worktree_guard = {
2632
- ROW: ROW16,
2946
+ ROW: ROW23,
2947
+ SINGLE_FILE_RESTORE,
2633
2948
  WORKTREE_MUTATION_REASON,
2634
2949
  is_unauthorized_worktree_change,
2635
2950
  touches_worktree
@@ -2658,7 +2973,7 @@ function is_index_mutation(command) {
2658
2973
  return shell_segments.commands_of(command).some((segment) => is_index_segment(segment));
2659
2974
  }
2660
2975
  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\").";
2661
- var ROW17 = {
2976
+ var ROW24 = {
2662
2977
  id: "index-mutation",
2663
2978
  is_trigger: bash_triggers.on_bash_command(is_index_mutation),
2664
2979
  reason: INDEX_MUTATION_REASON,
@@ -2670,14 +2985,14 @@ var ROW17 = {
2670
2985
  quiet: "`pnpm josh git` \u2014 the approved commit flow"
2671
2986
  }
2672
2987
  };
2673
- var index_guard = { INDEX_MUTATION_REASON, ROW: ROW17, is_index_mutation };
2988
+ var index_guard = { INDEX_MUTATION_REASON, ROW: ROW24, is_index_mutation };
2674
2989
 
2675
2990
  // scripts/rules/pr-merge.ts
2676
- var GH_COMMAND3 = "gh";
2991
+ var GH_COMMAND4 = "gh";
2677
2992
  var CURL_COMMAND = "curl";
2678
2993
  var PR_GROUP3 = "pr";
2679
2994
  var MERGE_ACTION = "merge";
2680
- var API_SUBCOMMAND3 = "api";
2995
+ var API_SUBCOMMAND4 = "api";
2681
2996
  var GRAPHQL_ENDPOINT = "graphql";
2682
2997
  var MERGE_PATH = /pulls\/[^/\s'"]+\/merge(?![\w-])/u;
2683
2998
  var MERGE_MUTATIONS = /\b(?:mergePullRequest|enablePullRequestAutoMerge)\b/u;
@@ -2694,11 +3009,11 @@ function is_pr_merge_cli_call(args) {
2694
3009
  }
2695
3010
  function is_github_api_call(name, args) {
2696
3011
  if (name === CURL_COMMAND) return true;
2697
- return name === GH_COMMAND3 && positional_words2(args)[0] === API_SUBCOMMAND3;
3012
+ return name === GH_COMMAND4 && positional_words2(args)[0] === API_SUBCOMMAND4;
2698
3013
  }
2699
3014
  function is_graphql_call(name, args) {
2700
3015
  if (args.some((argument) => GRAPHQL_URL.test(argument))) return true;
2701
- return name === GH_COMMAND3 && positional_words2(args)[1] === GRAPHQL_ENDPOINT;
3016
+ return name === GH_COMMAND4 && positional_words2(args)[1] === GRAPHQL_ENDPOINT;
2702
3017
  }
2703
3018
  function is_unreadable_body(argument) {
2704
3019
  if (FILE_BODY_FLAGS.has(argument)) return true;
@@ -2715,39 +3030,47 @@ function is_api_merge(command, segment, call) {
2715
3030
  function is_pr_merge_segment(command, segment) {
2716
3031
  const call = git_argv.program_of(segment);
2717
3032
  if (call === void 0) return false;
2718
- if (call.name === GH_COMMAND3 && is_pr_merge_cli_call(call.args)) return true;
3033
+ if (call.name === GH_COMMAND4 && is_pr_merge_cli_call(call.args)) return true;
2719
3034
  return is_github_api_call(call.name, call.args) && is_api_merge(command, segment, call);
2720
3035
  }
2721
3036
  function is_pr_merge(command) {
2722
3037
  return shell_segments.commands_of(command).some((segment) => is_pr_merge_segment(command, segment));
2723
3038
  }
2724
- var PR_MERGE_REASON = "\u26D4 direct PR merge: `gh pr merge`, a `gh api` / `curl` call to `\u2026/pulls/<N>/merge`, a `mergePullRequest` / `enablePullRequestAutoMerge` mutation, or a GraphQL body read from a file merges outside the verified route, and a merge is Tier C. Merge with `pnpm josh followup` when the run authorizes it; otherwise leave it to the user (`CLAUDE.md` \u2192 Git Rules). This rule fires on every occurrence.";
2725
- var ROW18 = {
3039
+ var PR_MERGE_REASON = "\u26D4 direct PR merge: `gh pr merge`, a `gh api` / `curl` call to `\u2026/pulls/<N>/merge`, a `mergePullRequest` / `enablePullRequestAutoMerge` mutation, or a GraphQL body read from a file merges outside the verified route, and a merge is Tier C. Merge with `pnpm josh followup` if the run authorizes it; otherwise leave it to the user (`operating-rules.md` \u2192 git-rules). This rule fires on every occurrence.";
3040
+ var ROW25 = {
2726
3041
  id: "pr-merge",
2727
3042
  is_trigger: bash_triggers.on_bash_command(is_pr_merge),
2728
3043
  reason: PR_MERGE_REASON,
2729
3044
  decide: () => true,
2730
3045
  about: {
2731
3046
  title: "Direct PR merge",
2732
- topic: "`CLAUDE.md` \u2192 Git Rules",
3047
+ topic: '`operating-rules.md` \u2192 "git-rules"',
2733
3048
  fires: "`gh pr merge`, a `gh api` / `curl` call to `\u2026/pulls/<N>/merge`, a `mergePullRequest` / `enablePullRequestAutoMerge` mutation, or a GraphQL body read from a file. Every occurrence",
2734
3049
  quiet: "`pnpm josh followup` and `pnpm josh observations:flush`, which merge in their own process"
2735
3050
  }
2736
3051
  };
2737
- var pr_merge = { PR_MERGE_REASON, ROW: ROW18, is_pr_merge };
3052
+ var pr_merge = { PR_MERGE_REASON, ROW: ROW25, is_graphql_call, is_pr_merge };
2738
3053
 
2739
3054
  // scripts/rules/protected-files.ts
2740
- import path3 from "node:path";
2741
- var READ_TOOL2 = "Read";
2742
- var EDIT_TOOLS2 = /* @__PURE__ */ new Set(["Edit", "Write"]);
3055
+ import path2 from "node:path";
3056
+ var READ_TOOL = "Read";
3057
+ var { is_edit_tool: is_edit_tool4 } = edit_tool;
2743
3058
  var SECRET_FILES = /* @__PURE__ */ new Set([".env", ".dev.vars"]);
2744
3059
  var ENVIRONMENT_VARIANT_PREFIX = ".env.";
2745
3060
  var TRACKED_VARIANTS = /* @__PURE__ */ new Set([".env.example", ".env.test"]);
2746
- var SETTINGS_FILE = path3.join(".claude", "settings.json");
3061
+ var SETTINGS_FILE = path2.join(".claude", "settings.json");
2747
3062
  var WHITESPACE2 = /\s+/u;
2748
3063
  var TOKEN_QUOTES = /["']/gu;
2749
- var INPUT_REDIRECT = "<";
2750
- var SHELL_READERS = /* @__PURE__ */ new Set([
3064
+ var REDIRECT = />\||[<>]/gu;
3065
+ var REDIRECT_MARK = "<";
3066
+ var SHELLS = /* @__PURE__ */ new Set(["bash", "eval", "sh", "zsh"]);
3067
+ var SUBSTITUTION = /\$\((?<dollar>[^()]*)\)|`(?<backtick>[^`]*)`/gu;
3068
+ var SHELL_PROGRAMS = /* @__PURE__ */ new Set([
3069
+ "cp",
3070
+ "install",
3071
+ "ln",
3072
+ "mv",
3073
+ "tee",
2751
3074
  ".",
2752
3075
  "awk",
2753
3076
  "base64",
@@ -2784,43 +3107,73 @@ var SHELL_READERS = /* @__PURE__ */ new Set([
2784
3107
  "xxd",
2785
3108
  "yq"
2786
3109
  ]);
2787
- function file_path_of(call) {
3110
+ function file_path_of2(call) {
2788
3111
  if (!json_value.is_record(call.input)) return "";
2789
3112
  const value = call.input["file_path"];
2790
3113
  return typeof value === "string" ? value : "";
2791
3114
  }
2792
3115
  function is_environment_path(token) {
2793
- const name = path3.basename(token.replaceAll(TOKEN_QUOTES, ""));
3116
+ const name = path2.basename(token.replaceAll(TOKEN_QUOTES, ""));
2794
3117
  if (TRACKED_VARIANTS.has(name)) return false;
2795
3118
  return SECRET_FILES.has(name) || name.startsWith(ENVIRONMENT_VARIANT_PREFIX);
2796
3119
  }
2797
- function is_environment_read_segment(segment) {
2798
- const words = segment.replaceAll(INPUT_REDIRECT, " < ").split(WHITESPACE2).filter((word) => word !== "");
2799
- const is_redirected = words.some(
2800
- (word, index) => word === INPUT_REDIRECT && is_environment_path(words[index + 1] ?? "")
3120
+ function redirect_marks_of(segment, program) {
3121
+ if (program !== void 0 && SHELLS.has(program)) return segment;
3122
+ return time_shell.blanked(segment) ?? segment;
3123
+ }
3124
+ function spaced_redirects(segment, program) {
3125
+ const marks = redirect_marks_of(segment, program);
3126
+ return segment.replaceAll(
3127
+ REDIRECT,
3128
+ (mark3, offset) => marks.startsWith(mark3, offset) ? ` ${REDIRECT_MARK} ` : mark3
2801
3129
  );
3130
+ }
3131
+ function is_environment_shell_segment(segment) {
2802
3132
  const call = git_argv.program_of(segment);
3133
+ const words = spaced_redirects(segment, call?.name).split(WHITESPACE2).filter((word) => word !== "");
3134
+ const is_redirected = words.some(
3135
+ (word, index) => word === REDIRECT_MARK && is_environment_path(words[index + 1] ?? "")
3136
+ );
2803
3137
  if (is_redirected || call === void 0) return is_redirected;
2804
- return SHELL_READERS.has(call.name) && call.args.some((word) => is_environment_path(word));
3138
+ return SHELL_PROGRAMS.has(call.name) && call.args.some((word) => is_environment_path(word));
3139
+ }
3140
+ function script_commands_of(segment) {
3141
+ const program = git_argv.program_of(segment)?.name;
3142
+ if (program === void 0 || !SHELLS.has(program)) return [];
3143
+ return shell_segments.commands_of(segment);
3144
+ }
3145
+ function substitution_commands_of(segment) {
3146
+ return [...segment.matchAll(SUBSTITUTION)].flatMap(
3147
+ (match) => shell_segments.commands_of(match.groups?.["dollar"] ?? match.groups?.["backtick"] ?? "")
3148
+ );
2805
3149
  }
2806
- function is_environment_shell_read(command) {
2807
- return shell_segments.commands_of(command).some((segment) => is_environment_read_segment(segment));
3150
+ function segments_of2(command) {
3151
+ return shell_segments.quote_aware_commands_of(command).flatMap((segment) => [
3152
+ segment,
3153
+ ...script_commands_of(segment),
3154
+ ...substitution_commands_of(segment)
3155
+ ]);
2808
3156
  }
2809
- function is_environment_read(call) {
2810
- if (call.name === READ_TOOL2) return is_environment_path(file_path_of(call));
2811
- return bash_triggers.on_bash_command(is_environment_shell_read)(call);
3157
+ function is_environment_shell_access(command) {
3158
+ return segments_of2(command).some((segment) => is_environment_shell_segment(segment));
3159
+ }
3160
+ function is_environment_access(call) {
3161
+ if (call.name === READ_TOOL || is_edit_tool4(call.name)) {
3162
+ return is_environment_path(file_path_of2(call));
3163
+ }
3164
+ return bash_triggers.on_bash_command(is_environment_shell_access)(call);
2812
3165
  }
2813
3166
  function is_consumer_settings_edit(call, is_kit_consumer) {
2814
- if (!EDIT_TOOLS2.has(call.name)) return false;
2815
- const file_path = path3.resolve(file_path_of(call));
2816
- if (!file_path.endsWith(`${path3.sep}${SETTINGS_FILE}`)) return false;
2817
- return is_kit_consumer(path3.dirname(path3.dirname(file_path)));
3167
+ if (!is_edit_tool4(call.name)) return false;
3168
+ const file_path2 = path2.resolve(file_path_of2(call));
3169
+ if (!file_path2.endsWith(`${path2.sep}${SETTINGS_FILE}`)) return false;
3170
+ return is_kit_consumer(path2.dirname(path2.dirname(file_path2)));
2818
3171
  }
2819
3172
  function is_protected_file_call(call, is_kit_consumer = doctor_consumer.is_kit_consumer) {
2820
- return is_environment_read(call) || is_consumer_settings_edit(call, is_kit_consumer);
3173
+ return is_environment_access(call) || is_consumer_settings_edit(call, is_kit_consumer);
2821
3174
  }
2822
- 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.";
2823
- var ROW19 = {
3175
+ var PROTECTED_FILE_REASON = "\u26D4 protected file: `.env`, a `.env.*` variant and `.dev.vars` hold secrets and are written only by the person, 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.";
3176
+ var ROW26 = {
2824
3177
  id: "protected-file",
2825
3178
  is_trigger: is_protected_file_call,
2826
3179
  reason: PROTECTED_FILE_REASON,
@@ -2830,11 +3183,78 @@ var ROW19 = {
2830
3183
  about: {
2831
3184
  title: "Protected file",
2832
3185
  topic: "`operating-rules.md`",
2833
- fires: "a `Read` of `.env`, or an `Edit` / `Write` of `.claude/settings.json` outside kit. Every occurrence",
3186
+ fires: "a `Read`, `Edit` or `Write` of `.env` (or a shell read or write of it), or an `Edit` / `Write` of `.claude/settings.json` outside kit. Every occurrence",
2834
3187
  quiet: "kit itself, or the user's `~/.claude/settings.json`"
2835
3188
  }
2836
3189
  };
2837
- var protected_files = { ROW: ROW19, is_protected_file_call };
3190
+ var protected_files = { ROW: ROW26, is_protected_file_call };
3191
+
3192
+ // scripts/rules/repo-admin.ts
3193
+ var GH_COMMAND5 = "gh";
3194
+ var DELETE_METHOD2 = "DELETE";
3195
+ var PATCH_METHOD = "PATCH";
3196
+ var REPOSITORY_PREFIX = String.raw`^['"]?(?:https?:\/\/[^/\s'"]+)?\/?repos\/[^/\s'"]+\/[^/\s'"]+`;
3197
+ var ADMIN_PATH = new RegExp(
3198
+ REPOSITORY_PREFIX + String.raw`\/(?:rulesets|branches\/[^\s'"]+\/protection|collaborators|invitations|hooks|keys|` + String.raw`transfer|actions\/permissions|environments|` + String.raw`(?:actions|dependabot|codespaces)\/(?:secrets|variables))(?:[/?'"]|$)`,
3199
+ "u"
3200
+ );
3201
+ var REPOSITORY_BODY = new RegExp(String.raw`${REPOSITORY_PREFIX}\/?['"]?$`, "u");
3202
+ var ADMIN_MUTATIONS = /\b(?:updateRepository|archiveRepository|(?:create|update|delete)(?:BranchProtectionRule|RepositoryRuleset|Environment))\b/u;
3203
+ function is_admin_target(argument, method) {
3204
+ return ADMIN_PATH.test(argument) || method === PATCH_METHOD && REPOSITORY_BODY.test(argument);
3205
+ }
3206
+ function is_admin_graphql_call(command, args) {
3207
+ return pr_merge.is_graphql_call(GH_COMMAND5, args) && ADMIN_MUTATIONS.test(command);
3208
+ }
3209
+ function is_admin_api_call(command, args) {
3210
+ const call = [GH_COMMAND5, ...args].join(" ");
3211
+ if (!gh_api.is_gh_api(call) || !gh_api.is_write(call)) return false;
3212
+ if (is_admin_graphql_call(command, args)) return true;
3213
+ const method = gh_api.method_of(call);
3214
+ if (method === DELETE_METHOD2) return false;
3215
+ return args.some((argument) => is_admin_target(argument, method));
3216
+ }
3217
+ var READ_ACTIONS = /* @__PURE__ */ new Set(["list", "ls", "get", "view"]);
3218
+ var ACTION_POSITION = 1;
3219
+ var SUB_ACTION_POSITION = 2;
3220
+ var VERB_POSITIONS = /* @__PURE__ */ new Map([
3221
+ ["secret", ACTION_POSITION],
3222
+ ["variable", ACTION_POSITION],
3223
+ ["repo edit", ACTION_POSITION],
3224
+ ["repo deploy-key", SUB_ACTION_POSITION]
3225
+ ]);
3226
+ function admin_verb(args) {
3227
+ const words = direct_pr_create.positional_words(args, direct_pr_create.API_VALUE_TAKING_FLAGS);
3228
+ const position = VERB_POSITIONS.get(words[0] ?? "") ?? VERB_POSITIONS.get(words.slice(0, SUB_ACTION_POSITION).join(" "));
3229
+ if (position === void 0) return void 0;
3230
+ return words[position] ?? "";
3231
+ }
3232
+ function is_admin_cli_call(args) {
3233
+ const verb = admin_verb(args);
3234
+ return verb !== void 0 && !READ_ACTIONS.has(verb);
3235
+ }
3236
+ function is_admin_segment(command, segment) {
3237
+ const args = git_argv.arguments_of(segment, GH_COMMAND5);
3238
+ if (args === void 0) return false;
3239
+ return is_admin_cli_call(args) || is_admin_api_call(command, args);
3240
+ }
3241
+ function is_repo_admin_write(command) {
3242
+ return shell_segments.commands_of(command).some((segment) => is_admin_segment(command, segment));
3243
+ }
3244
+ var REPO_ADMIN_REASON = "\u26D4 repository administration: a write to the rulesets, branch protection, collaborators, invitations, secrets, variables, webhooks, deploy keys or repository settings moves the boundary an unattended run relies on, which is Tier C. Ask the user to run it. It fires on every occurrence (`prompts/collaboration-workflow/operating-rules.md`).";
3245
+ var ROW27 = {
3246
+ id: "repo-admin",
3247
+ is_trigger: bash_triggers.on_bash_command(is_repo_admin_write),
3248
+ reason: REPO_ADMIN_REASON,
3249
+ decide: () => true,
3250
+ about: {
3251
+ title: "Repository administration",
3252
+ topic: "`operating-rules.md`",
3253
+ fires: "a `gh api` POST / PUT / PATCH (path or URL) to the rulesets, branch protection, collaborators, invitations, secrets, variables, hooks, keys, environments, actions permissions or transfer, a `PATCH` of the repository, the matching GraphQL mutations, `gh secret` / `gh variable` writes, `gh repo edit` / `deploy-key` writes. Every occurrence",
3254
+ quiet: "`gh secret list`, `gh variable get`, a `gh api` read \u2014 a `DELETE` is `destructive-command`"
3255
+ }
3256
+ };
3257
+ var repo_admin = { REPO_ADMIN_REASON, ROW: ROW27, is_repo_admin_write };
2838
3258
 
2839
3259
  // scripts/rules/permission-guards.ts
2840
3260
  var ROWS2 = [
@@ -2842,13 +3262,15 @@ var ROWS2 = [
2842
3262
  destructive_command.ROW,
2843
3263
  protected_files.ROW,
2844
3264
  direct_pr_create.ROW,
2845
- pr_merge.ROW
3265
+ pr_merge.ROW,
3266
+ repo_admin.ROW
2846
3267
  ];
2847
3268
  var permission_guards = { ROWS: ROWS2 };
2848
3269
 
2849
3270
  // scripts/rules/piped-verification.ts
2850
3271
  var VERIFICATION_COMMANDS = /* @__PURE__ */ new Set([
2851
3272
  "check",
3273
+ "check:related",
2852
3274
  "cspell",
2853
3275
  "cspell:dot",
2854
3276
  "eval",
@@ -2874,6 +3296,11 @@ var PIPED_VERIFICATION_REASON = "\u26D4 piped verification: a pipeline exits wit
2874
3296
  function pipelines_of(command) {
2875
3297
  return shell_segments.segments_of(time_shell.unquoted(command)).map((segment) => segment.split("|").map((piece) => piece.trim()));
2876
3298
  }
3299
+ function check_filters(command) {
3300
+ return pipelines_of(command).flatMap(
3301
+ ([head = "", ...filters]) => is_verification_command(head) ? filters : []
3302
+ );
3303
+ }
2877
3304
  function command_pieces(command) {
2878
3305
  return pipelines_of(command).flat();
2879
3306
  }
@@ -2886,25 +3313,47 @@ function keeps_verdict_intact(command) {
2886
3313
  var PIPEFAIL_OPEN = "(set -o pipefail; ";
2887
3314
  var PIPEFAIL_CLOSE = ")";
2888
3315
  var COMMENT_START = /(?:^|\s)#/u;
2889
- var FULL_READING_FILTER = /^(?:tail|grep|cut|sort|uniq|wc)\b/u;
3316
+ var FULL_READING_FILTER = /^(?:tail|grep|cut|sort|uniq|wc|tee)(?=\s|$)/u;
2890
3317
  var EARLY_EXIT_GREP = /^grep\b.*\s(?:-[a-zA-Z]*[lLmq]|--(?:quiet|silent|max-count|files-with(?:out)?-match))/u;
3318
+ var AWK_PROGRAM = /^awk\s+(?:-F\s*\S+\s+)?'[^']*'$/u;
3319
+ var AWK_EARLY_EXIT = /\b(?:exit|nextfile|system|getline)\b|\|/u;
3320
+ var SED_TEXT = String.raw`(?:[^/\\']|\\.)*`;
3321
+ var SED_ADDRESS = String.raw`(?:\d+|\$|/${SED_TEXT}/)`;
3322
+ var SED_COMMAND = String.raw`(?:${SED_ADDRESS}(?:,${SED_ADDRESS})?)?!?(?:p|d|s/${SED_TEXT}/${SED_TEXT}/[gip\d]*)`;
3323
+ var SED_PROGRAM = new RegExp(
3324
+ String.raw`^sed\s+(?:-[nEr]+\s+)*'${SED_COMMAND}(?:;\s*${SED_COMMAND})*'$`,
3325
+ "u"
3326
+ );
2891
3327
  var ALLOWED_OPERATORS = /2>&1|&&/gu;
2892
3328
  var SIDE_EFFECT_SYNTAX = /[&<>`]|\$\(/u;
2893
3329
  var GROUP_SYNTAX = /[()]/u;
2894
3330
  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`.";
2895
- function reads_to_the_end(piece) {
2896
- return FULL_READING_FILTER.test(piece) && !EARLY_EXIT_GREP.test(piece);
3331
+ function is_full_reading_script(typed) {
3332
+ if (AWK_PROGRAM.test(typed)) return !AWK_EARLY_EXIT.test(typed);
3333
+ return SED_PROGRAM.test(typed);
3334
+ }
3335
+ function reads_to_the_end(piece, typed) {
3336
+ if (FULL_READING_FILTER.test(piece)) return !EARLY_EXIT_GREP.test(piece);
3337
+ return is_full_reading_script(typed);
3338
+ }
3339
+ function typed_slices(raw, pieces, gap) {
3340
+ const offset = { at: 0 };
3341
+ return pieces.map((piece) => {
3342
+ const typed = raw.slice(offset.at, offset.at + piece.length);
3343
+ offset.at += piece.length + gap;
3344
+ return typed;
3345
+ });
2897
3346
  }
2898
- function is_filtered_check(pipeline) {
2899
- const [head = "", ...filters] = pipeline;
2900
- return is_verification_command(head) && filters.every((piece) => reads_to_the_end(piece));
3347
+ function is_filtered_check(pieces, typed) {
3348
+ const [head = "", ...filters] = pieces.map((piece) => piece.trim());
3349
+ return is_verification_command(head) && filters.every((piece, index) => reads_to_the_end(piece, typed[index + 1]?.trim() ?? ""));
2901
3350
  }
2902
3351
  function has_side_effect_syntax(command) {
2903
3352
  return SIDE_EFFECT_SYNTAX.test(command.replaceAll(ALLOWED_OPERATORS, " "));
2904
3353
  }
2905
3354
  function is_rewritable_part(raw, judged) {
2906
- const pipeline = judged.split("|").map((piece) => piece.trim());
2907
- return !has_side_effect_syntax(raw) && !GROUP_SYNTAX.test(judged) && is_filtered_check(pipeline);
3355
+ const pieces = judged.split("|");
3356
+ return !has_side_effect_syntax(raw) && !GROUP_SYNTAX.test(judged) && is_filtered_check(pieces, typed_slices(raw, pieces, 1));
2908
3357
  }
2909
3358
  function code_end(judged) {
2910
3359
  const comment = COMMENT_START.exec(judged);
@@ -2923,12 +3372,10 @@ function rewritten_part(raw, judged) {
2923
3372
  function pipefail_command(command) {
2924
3373
  const judged = time_shell.blanked(command);
2925
3374
  if (judged === void 0 || !is_masked_verification(command)) return void 0;
2926
- const offset = { at: 0 };
2927
- const parts = shell_segments.parts_of(judged).map((part) => {
2928
- const raw = command.slice(offset.at, offset.at + part.length);
2929
- offset.at += part.length;
2930
- return rewritten_part(raw, part);
2931
- });
3375
+ const pieces = shell_segments.parts_of(judged);
3376
+ const parts = typed_slices(command, pieces, 0).map(
3377
+ (raw, index) => rewritten_part(raw, pieces[index] ?? "")
3378
+ );
2932
3379
  const rewritten = parts.includes(void 0) ? void 0 : parts.join("");
2933
3380
  return rewritten === command ? void 0 : rewritten;
2934
3381
  }
@@ -2938,9 +3385,11 @@ function pipefail_input(call) {
2938
3385
  return command === void 0 ? void 0 : { ...call.input, command };
2939
3386
  }
2940
3387
  var piped_verification = {
3388
+ EARLY_EXIT_GREP,
2941
3389
  PIPED_VERIFICATION_REASON,
2942
3390
  PIPEFAIL_NOTE,
2943
3391
  VERIFICATION_COMMANDS,
3392
+ check_filters,
2944
3393
  is_masked_verification,
2945
3394
  keeps_verdict_intact,
2946
3395
  pipefail_input,
@@ -2959,9 +3408,39 @@ var PROBE_HEADERS = [
2959
3408
  String.raw`(?:kill\s+-0|pgrep)\b`,
2960
3409
  String.raw`grep\s+-[A-Za-z]*q`
2961
3410
  ].map((probe) => new RegExp(`${WHILE_HEADER}${probe}`, "u"));
3411
+ var COUNTED_FOR = new RegExp(
3412
+ String.raw`${COMMAND_POSITION}for(?:\s*\(\(|\s+\w+\s+in\s+(?:\$\(\s*seq\b|\{\d+\.\.))`,
3413
+ "u"
3414
+ );
3415
+ var THROUGH_FIRST_DONE = /^[\s\S]*?\bdone\b/u;
3416
+ var SETTLE_SECONDS = 10;
3417
+ var NO_SECONDS2 = 0;
3418
+ var READ_COMMAND = /^(?:tail|cat|head|ps|stat|grep|wc|ls|pgrep|test|kill\s+-0|\[\[?)(?=\s|$)/u;
3419
+ function slept_seconds(segments) {
3420
+ return segments.reduce(
3421
+ (total, segment) => total + (early_heartbeat.sleep_seconds(segment) ?? NO_SECONDS2),
3422
+ NO_SECONDS2
3423
+ );
3424
+ }
3425
+ function is_sleep_then_read(plain) {
3426
+ const segments = shell_segments.commands_of(plain).filter((segment) => segment !== "");
3427
+ const first_work = segments.findIndex((segment) => !early_heartbeat.is_wait_or_inert(segment));
3428
+ const waited = first_work === -1 ? NO_SECONDS2 : slept_seconds(segments.slice(0, first_work));
3429
+ if (waited < SETTLE_SECONDS) return false;
3430
+ return segments.slice(first_work).every((segment) => early_heartbeat.is_wait_or_inert(segment) || READ_COMMAND.test(segment));
3431
+ }
2962
3432
  function is_state_wait(plain) {
2963
3433
  return UNTIL_HEADER.test(plain) || PROBE_HEADERS.some((header) => header.test(plain));
2964
3434
  }
3435
+ function is_counted_sleep(plain) {
3436
+ const header = COUNTED_FOR.exec(plain);
3437
+ if (header === null) return false;
3438
+ const rest = plain.slice(header.index);
3439
+ return SLEEP_COMMAND.test(THROUGH_FIRST_DONE.exec(rest)?.[0] ?? rest);
3440
+ }
3441
+ function is_sleeping_loop(plain) {
3442
+ return LOOP_KEYWORD2.test(plain) && SLEEP_COMMAND.test(plain) || is_counted_sleep(plain);
3443
+ }
2965
3444
  var STREAM_READER = /\bwhile\s+read\b/u;
2966
3445
  function is_stream_reader(plain) {
2967
3446
  return STREAM_READER.test(plain);
@@ -2970,10 +3449,10 @@ function is_poll_loop(command) {
2970
3449
  const plain = time_shell.unquoted(command);
2971
3450
  if (is_stream_reader(plain)) return false;
2972
3451
  if (is_state_wait(plain)) return true;
2973
- return LOOP_KEYWORD2.test(plain) && SLEEP_COMMAND.test(plain);
3452
+ return is_sleeping_loop(plain) || is_sleep_then_read(plain);
2974
3453
  }
2975
- 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.";
2976
- var ROW20 = {
3454
+ var POLL_LOOP_REASON = "\u26D4 output poll loop: a `while`/`until` or counted `for` loop, or a `sleep` chain before a read, does not know when the task finished. `run_in_background` and end the turn \u2014 its exit re-invokes you; a background `Agent` is waited on by its completion notification (lane child: bare `sleep 20`, `pnpm josh run:liveness`); CI by `pnpm josh followup` (`background-commands.md`). Fires on every occurrence.";
3455
+ var ROW28 = {
2977
3456
  id: "poll-loop",
2978
3457
  is_trigger: bash_triggers.on_bash_command(is_poll_loop),
2979
3458
  reason: POLL_LOOP_REASON,
@@ -2981,13 +3460,13 @@ var ROW20 = {
2981
3460
  about: {
2982
3461
  title: "Output poll loop",
2983
3462
  topic: "`.claude/skills/workflow-commands/background-commands.md`",
2984
- fires: "a hand-written `while` / `until` wait loop over a command's output, a marker file or a process. Every occurrence",
3463
+ fires: "a hand-written `while` / `until` wait loop over a command's output, a marker file or a process, a counted `for` that sleeps, or a `sleep` chain of 10 s or more before a read of a file or process. Every occurrence",
2985
3464
  quiet: "the command started with `run_in_background` \u2014 its exit ends the wait"
2986
3465
  }
2987
3466
  };
2988
3467
  var poll_loop = {
2989
3468
  POLL_LOOP_REASON,
2990
- ROW: ROW20,
3469
+ ROW: ROW28,
2991
3470
  is_poll_loop
2992
3471
  };
2993
3472
 
@@ -2997,7 +3476,7 @@ function posts_at_path_literally(command) {
2997
3476
  return RAW_FIELD_BODY_AT.test(command);
2998
3477
  }
2999
3478
  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`).";
3000
- var ROW21 = {
3479
+ var ROW29 = {
3001
3480
  id: "raw-field-body",
3002
3481
  is_trigger: bash_triggers.on_bash_command(posts_at_path_literally),
3003
3482
  reason: RAW_FIELD_BODY_REASON,
@@ -3009,16 +3488,125 @@ var ROW21 = {
3009
3488
  quiet: "`-F` / `--field body=@`, a body without `@`, or `pnpm josh issue:comment`"
3010
3489
  }
3011
3490
  };
3012
- var raw_field_body = { RAW_FIELD_BODY_REASON, ROW: ROW21, posts_at_path_literally };
3491
+ var raw_field_body = { RAW_FIELD_BODY_REASON, ROW: ROW29, posts_at_path_literally };
3492
+
3493
+ // scripts/rules/reviewer-foreground.ts
3494
+ var REVIEWER_AGENTS = /* @__PURE__ */ new Set(["reviewer", "reviewer-verification"]);
3495
+ var BACKGROUND_FIELD2 = "run_in_background";
3496
+ var CHAIN_RULE_STEP = '`.claude/skills/workflow-commands/chain-rule.md` \u2192 "A lane child"';
3497
+ var KILLED = "reviewer foreground: a lane child\u2019s turn end kills a background `reviewer`";
3498
+ var FOREGROUND = `\`${BACKGROUND_FIELD2}: false\``;
3499
+ var REVIEWER_FOREGROUND_NOTE = `\u21BB ${KILLED}, so the call ran with ${FOREGROUND} (${CHAIN_RULE_STEP}).`;
3500
+ var REVIEWER_FOREGROUND_REASON = `\u26D4 ${KILLED}. Reissue the call with ${FOREGROUND} (${CHAIN_RULE_STEP}). It fires on every occurrence.`;
3501
+ function is_reviewer(call) {
3502
+ if (!agent_model.SUBAGENT_TOOLS.has(call.name) || !json_value.is_record(call.input)) return false;
3503
+ const type = call.input["subagent_type"];
3504
+ return typeof type === "string" && REVIEWER_AGENTS.has(type.split(":").at(-1) ?? "");
3505
+ }
3506
+ function is_foreground2(call) {
3507
+ return json_value.is_record(call.input) && call.input[BACKGROUND_FIELD2] === false;
3508
+ }
3509
+ function is_background_reviewer(call, is_lane_child = lane_child_marker.is_child_of(process.cwd())) {
3510
+ return is_lane_child && is_reviewer(call) && !is_foreground2(call);
3511
+ }
3512
+ function foreground_input(call, is_lane_child = lane_child_marker.is_child_of(process.cwd())) {
3513
+ if (!is_background_reviewer(call, is_lane_child) || !json_value.is_record(call.input)) {
3514
+ return void 0;
3515
+ }
3516
+ return { ...call.input, [BACKGROUND_FIELD2]: false };
3517
+ }
3518
+ var ROW30 = {
3519
+ id: "reviewer-foreground",
3520
+ is_trigger: (call) => is_background_reviewer(call),
3521
+ reason: REVIEWER_FOREGROUND_REASON,
3522
+ decide: () => true,
3523
+ keeps: (call) => is_reviewer(call) && is_foreground2(call),
3524
+ reaches: is_reviewer,
3525
+ rewrite: (call) => foreground_input(call),
3526
+ rewrite_note: REVIEWER_FOREGROUND_NOTE,
3527
+ about: {
3528
+ title: "Lane child reviewer dispatched in the foreground",
3529
+ topic: CHAIN_RULE_STEP,
3530
+ fires: "a dispatched lane child\u2019s `Agent` / `Task` call of the `reviewer` or `reviewer-verification` type without `run_in_background: false` \u2014 rewritten with it, every occurrence",
3531
+ quiet: "not a lane child, another agent type, or `run_in_background: false` set"
3532
+ }
3533
+ };
3534
+ var reviewer_foreground = {
3535
+ REVIEWER_FOREGROUND_NOTE,
3536
+ REVIEWER_FOREGROUND_REASON,
3537
+ ROW: ROW30,
3538
+ foreground_input,
3539
+ is_background_reviewer
3540
+ };
3541
+
3542
+ // scripts/rules/round-one-check-related.ts
3543
+ var CHECK_COMMANDS = /* @__PURE__ */ new Set(["check:related"]);
3544
+ var REVIEW_COMMANDS = /* @__PURE__ */ new Set(["run:review"]);
3545
+ var SHIP_COMMANDS = /* @__PURE__ */ new Set(["ship"]);
3546
+ var JOIN_FLAG = /(?:^|\s)--join(?:[=\s]|$)/u;
3547
+ var READING_MODE_FLAG = /(?:^|\s)--(?:pinned|push)(?:[=\s]|$)/u;
3548
+ var DETACH_FLAG = /(?:^|\s)--detach(?:[=\s]|$)/u;
3549
+ function review_event(segment) {
3550
+ if (JOIN_FLAG.test(segment)) return "close";
3551
+ return READING_MODE_FLAG.test(segment) ? "none" : "brief";
3552
+ }
3553
+ function segment_event(segment) {
3554
+ if (shell_segments.is_josh_command(segment, REVIEW_COMMANDS)) return review_event(segment);
3555
+ const is_detach = shell_segments.is_josh_command(segment, SHIP_COMMANDS) && DETACH_FLAG.test(segment);
3556
+ return is_detach ? "close" : "none";
3557
+ }
3558
+ function block_events(block) {
3559
+ if (tail_commands.is_bash_use(block)) {
3560
+ const segments = shell_segments.segments_of(time_shell.bash_command(block.input));
3561
+ return segments.map((segment) => segment_event(segment));
3562
+ }
3563
+ const is_edit = time_markers.tool_marker(block.name, block.input) === time_markers.EDIT_MARKER;
3564
+ return is_edit ? ["edit"] : [];
3565
+ }
3566
+ function fixes_an_open_round(tail) {
3567
+ const blocks = time_transcript_line.parse_text(tail).flatMap((line) => line.blocks);
3568
+ const events = blocks.flatMap((block) => block_events(block));
3569
+ const brief_at = events.lastIndexOf("brief");
3570
+ if (brief_at === -1) return false;
3571
+ const since_brief = new Set(events.slice(brief_at));
3572
+ return since_brief.has("edit") && !since_brief.has("close");
3573
+ }
3574
+ function runs_check_related(command) {
3575
+ return shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, CHECK_COMMANDS));
3576
+ }
3577
+ function is_candidate3(command) {
3578
+ if (!runs_check_related(command)) return false;
3579
+ return lane_child_marker.is_child_of(process.cwd());
3580
+ }
3581
+ function decide4(_call, run) {
3582
+ return fixes_an_open_round(time_density_hook.read_tail(run.transcript));
3583
+ }
3584
+ var ROUND_ONE_CHECK_RELATED_REASON = "\u26D4 round-one check:related: round 1's fixes need no `check:related` \u2014 ship's pre-detach reruns the scoped pair on the fixed tree; refused on every occurrence. Go on to `pnpm josh run:review --join`; `--join --fixing` while round 1 has fixes to make (`.claude/skills/workflow-commands/chain-rule.md` \u2192 \"A lane child\").";
3585
+ var ROW31 = {
3586
+ id: "round-one-check-related",
3587
+ is_trigger: bash_triggers.on_bash_command(is_candidate3),
3588
+ reason: ROUND_ONE_CHECK_RELATED_REASON,
3589
+ decide: decide4,
3590
+ about: {
3591
+ title: "No check:related on round 1's fixes",
3592
+ topic: '`.claude/skills/workflow-commands/chain-rule.md` \u2192 "A lane child"',
3593
+ fires: "`pnpm josh check:related` (`josh cr`) in a lane child after an `Edit` / `Write` that follows `run:review`'s brief, before `run:review --join` or `ship --detach`. Every occurrence",
3594
+ quiet: "no fix since the brief, already joined or detached, or not a lane child"
3595
+ }
3596
+ };
3597
+ var round_one_check_related = {
3598
+ ROUND_ONE_CHECK_RELATED_REASON,
3599
+ ROW: ROW31,
3600
+ fixes_an_open_round
3601
+ };
3013
3602
 
3014
3603
  // scripts/rules/rule-body-guard.ts
3015
- var EDIT_TOOL = "Edit";
3016
- var WRITE_TOOL = "Write";
3604
+ var { WRITE: WRITE3, is_edit_tool: is_edit_tool5, string_field: string_field3 } = edit_tool;
3017
3605
  var CLAUDE_DOCUMENT = /(?:^|\/)CLAUDE\.md$/u;
3018
3606
  var PROMPT_DOCUMENT = /(?:^|\/)prompts\/.+\.md$/u;
3019
3607
  var SKILL_DOCUMENT = /(?:^|\/)\.claude\/skills\/.+\.md$/u;
3020
- function is_rule_document(file_path) {
3021
- return CLAUDE_DOCUMENT.test(file_path) || PROMPT_DOCUMENT.test(file_path) || SKILL_DOCUMENT.test(file_path);
3608
+ function is_rule_document(file_path2) {
3609
+ return CLAUDE_DOCUMENT.test(file_path2) || PROMPT_DOCUMENT.test(file_path2) || SKILL_DOCUMENT.test(file_path2);
3022
3610
  }
3023
3611
  var LINK_TARGET = /\]\([^)]*\)/gu;
3024
3612
  var BARE_URL = /https?:\/\/\S+/gu;
@@ -3029,31 +3617,24 @@ var MIN_APPENDED_PROSE = 120;
3029
3617
  function adds_rule_prose(old_text, new_text) {
3030
3618
  return prose_of(new_text).length - prose_of(old_text).length >= MIN_APPENDED_PROSE;
3031
3619
  }
3032
- function string_field(input, key) {
3033
- const value = input[key];
3034
- return typeof value === "string" ? value : "";
3035
- }
3036
- function read_existing(file_path) {
3037
- return file_reader.read_file_or_empty(file_path);
3620
+ function read_existing(file_path2) {
3621
+ return file_reader.read_file_or_empty(file_path2);
3038
3622
  }
3039
3623
  function texts_of(name, input, read2) {
3040
- if (name === WRITE_TOOL) {
3624
+ if (name === WRITE3) {
3041
3625
  return {
3042
- old_text: read2(string_field(input, "file_path")),
3043
- new_text: string_field(input, "content")
3626
+ old_text: read2(string_field3(input, "file_path")),
3627
+ new_text: string_field3(input, "content")
3044
3628
  };
3045
3629
  }
3046
3630
  return {
3047
- old_text: string_field(input, "old_string"),
3048
- new_text: string_field(input, "new_string")
3631
+ old_text: string_field3(input, "old_string"),
3632
+ new_text: string_field3(input, "new_string")
3049
3633
  };
3050
3634
  }
3051
- function is_edit_tool2(name) {
3052
- return name === EDIT_TOOL || name === WRITE_TOOL;
3053
- }
3054
3635
  function rule_document_input(call) {
3055
- if (!is_edit_tool2(call.name) || !json_value.is_record(call.input)) return void 0;
3056
- return is_rule_document(string_field(call.input, "file_path")) ? call.input : void 0;
3636
+ if (!is_edit_tool5(call.name) || !json_value.is_record(call.input)) return void 0;
3637
+ return is_rule_document(string_field3(call.input, "file_path")) ? call.input : void 0;
3057
3638
  }
3058
3639
  function writes_rule_prose(call, read2 = read_existing) {
3059
3640
  const input = rule_document_input(call);
@@ -3080,7 +3661,7 @@ function runs_placement_oracle(command) {
3080
3661
  (names) => shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, names))
3081
3662
  );
3082
3663
  }
3083
- var ROW22 = {
3664
+ var ROW32 = {
3084
3665
  id: "rule-body",
3085
3666
  is_trigger: writes_rule_prose,
3086
3667
  reason: RULE_BODY_REASON,
@@ -3095,26 +3676,26 @@ var ROW22 = {
3095
3676
  };
3096
3677
  var rule_body_guard = {
3097
3678
  RULE_BODY_REASON,
3098
- ROW: ROW22,
3679
+ ROW: ROW32,
3099
3680
  answered_placement,
3100
3681
  is_rule_document,
3101
3682
  writes_rule_prose
3102
3683
  };
3103
3684
 
3104
3685
  // scripts/rules/shell-body-trigger.ts
3105
- var FIELD_FLAGS = "(?:-f|-F|--field|--raw-field)";
3686
+ var FIELD_FLAGS2 = "(?:-f|-F|--field|--raw-field)";
3106
3687
  var MESSAGE_FLAGS = "(?:--body|--notify-message|--recommended|--alternative)";
3107
- var FIELD_OPENING = String.raw`${FIELD_FLAGS}[\s=]*(?:'?body="|"body=)`;
3688
+ var FIELD_OPENING = String.raw`${FIELD_FLAGS2}[\s=]*(?:'?body="|"body=)`;
3108
3689
  var MESSAGE_OPENING = String.raw`${MESSAGE_FLAGS}[\s=]+"`;
3109
3690
  var NESTED_SUBSTITUTION = String.raw`\$\([^()]*\)`;
3110
- var SUBSTITUTION = String.raw`\$\((?:[^()$]|\$(?!\()|${NESTED_SUBSTITUTION})*\)`;
3111
- var QUOTED_VALUE = String.raw`(?:[^"\\$]|\\.|\$(?!\()|${SUBSTITUTION})*`;
3691
+ var SUBSTITUTION2 = String.raw`\$\((?:[^()$]|\$(?!\()|${NESTED_SUBSTITUTION})*\)`;
3692
+ var QUOTED_VALUE = String.raw`(?:[^"\\$]|\\.|\$(?!\()|${SUBSTITUTION2})*`;
3112
3693
  var INLINE_BODY_VALUE = new RegExp(
3113
3694
  `(?:${FIELD_OPENING}|${MESSAGE_OPENING})(${QUOTED_VALUE})"`,
3114
3695
  "gu"
3115
3696
  );
3116
3697
  var SHELL_EVALUATED = /[`$]/u;
3117
- var WHOLE_VALUE_SUBSTITUTION = new RegExp(`^${SUBSTITUTION}$`, "u");
3698
+ var WHOLE_VALUE_SUBSTITUTION = new RegExp(`^${SUBSTITUTION2}$`, "u");
3118
3699
  var BACKTICK = "`";
3119
3700
  var WHOLE_VALUE_FILE_REFERENCE = /^@\S+$/u;
3120
3701
  var ESCAPED_PAIR = /\\./gu;
@@ -3136,11 +3717,11 @@ function is_shell_evaluated_body(command) {
3136
3717
  return false;
3137
3718
  }
3138
3719
  var FILE_FLAG = /(?:--body-file|--notify-message-file)(?:[\s=]|$)/u;
3139
- var FIELD_FILE_REFERENCE = new RegExp(String.raw`${FIELD_FLAGS}[\s=]*body=@\S`, "u");
3720
+ var FIELD_FILE_REFERENCE = new RegExp(String.raw`${FIELD_FLAGS2}[\s=]*body=@\S`, "u");
3140
3721
  var ANSI_C_BODY = /(?:(?:--body|--notify-message)[\s=]+|body=)\$'/u;
3141
3722
  var DOUBLE_QUOTED_SPAN = /"[^"]*"/gu;
3142
3723
  var BODY_FLAG = new RegExp(
3143
- String.raw`${FIELD_FLAGS}[\s=]*'?"?body=|${MESSAGE_FLAGS}[\s=]|--body-file[\s=]|--notify-message-file[\s=]`,
3724
+ String.raw`${FIELD_FLAGS2}[\s=]*'?"?body=|${MESSAGE_FLAGS}[\s=]|--body-file[\s=]|--notify-message-file[\s=]`,
3144
3725
  "u"
3145
3726
  );
3146
3727
  var QUOTED_SPAN = /"[^"]*"|'[^']*'/gu;
@@ -3171,79 +3752,6 @@ var shell_body_trigger = {
3171
3752
  keeps_body_safe
3172
3753
  };
3173
3754
 
3174
- // scripts/test/test-declared-logic.ts
3175
- var TEST_SUFFIXES = [".e2e.ts", ".test.ts"];
3176
- var EXEMPT_PATHS = [".editorconfig"];
3177
- var EXEMPT_PREFIXES = [".idea/", ".vscode/", "prompts/"];
3178
- var EXEMPT_SUFFIXES = [".md"];
3179
- var BASIC_VISUAL_SUFFIXES = [".html", ".css"];
3180
- var BASIC_REQUIRED_SUFFIXES = [
3181
- ".ts",
3182
- ".tsx",
3183
- ".mts",
3184
- ".cts",
3185
- ".js",
3186
- ".jsx",
3187
- ".mjs",
3188
- ".cjs",
3189
- ".svelte",
3190
- ".json",
3191
- ".jsonc",
3192
- ".json5",
3193
- ".yaml",
3194
- ".yml",
3195
- ".toml"
3196
- ];
3197
- function normalize(paths) {
3198
- return paths.map((path5) => path5.trim()).filter((path5) => path5 !== "");
3199
- }
3200
- function is_test_file(path5) {
3201
- return TEST_SUFFIXES.some((suffix) => path5.endsWith(suffix));
3202
- }
3203
- function is_basic_manual(path5, is_basic) {
3204
- const lower = path5.toLowerCase();
3205
- return is_basic && BASIC_REQUIRED_SUFFIXES.every((suffix) => !lower.endsWith(suffix));
3206
- }
3207
- function is_basic_visual(path5) {
3208
- const lower = path5.toLowerCase();
3209
- return BASIC_VISUAL_SUFFIXES.some((suffix) => lower.endsWith(suffix));
3210
- }
3211
- function is_exempt(path5, is_basic = false) {
3212
- if (EXEMPT_PATHS.includes(path5) || is_basic_manual(path5, is_basic)) return true;
3213
- return EXEMPT_SUFFIXES.some((suffix) => path5.endsWith(suffix)) || EXEMPT_PREFIXES.some((prefix) => path5.startsWith(prefix));
3214
- }
3215
- function is_runtime(path5, is_basic = false) {
3216
- return !is_test_file(path5) && !is_exempt(path5, is_basic);
3217
- }
3218
- function verdict_for2(paths, is_basic = false) {
3219
- const changed = normalize(paths);
3220
- if (changed.some((path5) => is_test_file(path5))) return "satisfied";
3221
- if (changed.some((path5) => is_runtime(path5, is_basic))) return "required";
3222
- return "exempt";
3223
- }
3224
- function runtime_files(paths, is_basic = false) {
3225
- return normalize(paths).filter((path5) => is_runtime(path5, is_basic));
3226
- }
3227
- function exempt_files(paths, is_basic = false) {
3228
- return normalize(paths).filter((path5) => is_exempt(path5, is_basic));
3229
- }
3230
- function manual_check_files(paths, is_basic) {
3231
- return normalize(paths).filter((path5) => is_exempt(path5, is_basic) && !is_exempt(path5));
3232
- }
3233
- var test_declared_logic = {
3234
- EXEMPT_PATHS,
3235
- EXEMPT_PREFIXES,
3236
- EXEMPT_SUFFIXES,
3237
- exempt_files,
3238
- is_basic_visual,
3239
- is_exempt,
3240
- is_runtime,
3241
- is_test_file,
3242
- manual_check_files,
3243
- runtime_files,
3244
- verdict_for: verdict_for2
3245
- };
3246
-
3247
3755
  // scripts/test/test-declared-changed.ts
3248
3756
  var RENAME_ARROW = " -> ";
3249
3757
  var STATUS_PREFIX_LENGTH = 3;
@@ -3287,19 +3795,66 @@ function is_untested_commit(call) {
3287
3795
  if (!run_tail_rule.is_push_step_call(call)) return false;
3288
3796
  return test_declared_changed.current_verdict() === "required";
3289
3797
  }
3290
- var REASON3 = "\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).";
3291
- var ROW23 = {
3798
+ 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).";
3799
+ var ROW33 = {
3292
3800
  id: "test-declared",
3293
3801
  is_trigger: is_untested_commit,
3294
- reason: REASON3,
3802
+ reason: REASON,
3295
3803
  about: {
3296
3804
  title: "Test declaration",
3297
- topic: "`CLAUDE.md` \u2192 Code Change Rules",
3805
+ topic: '`testing-guide.md` \u2192 "0. Per-Requirement Test Coverage (MANDATORY)"',
3298
3806
  fires: "`pnpm josh git -y` (`josh g`) while `pnpm josh test:declared` answers `required`",
3299
3807
  quiet: "`exempt` (non-runtime only) or `satisfied` (tests included)"
3300
3808
  }
3301
3809
  };
3302
- var test_declared_commit = { REASON: REASON3, ROW: ROW23 };
3810
+ var test_declared_commit = { REASON, ROW: ROW33 };
3811
+
3812
+ // scripts/rules/gh-repo-selector.ts
3813
+ var GH_COMMAND6 = "gh";
3814
+ var API_GROUP = "api";
3815
+ var SELECTOR_FLAGS = /* @__PURE__ */ new Set(["-R", "--repo"]);
3816
+ var ATTACHED_SELECTOR = /^(?:-R=?|--repo=)(.+)$/u;
3817
+ var QUOTES3 = /['"]/gu;
3818
+ var NEXT2 = 1;
3819
+ var READ_GROUPS = /* @__PURE__ */ new Set(["browse", "search", "status"]);
3820
+ var READ_ACTIONS2 = /* @__PURE__ */ new Set([
3821
+ "check",
3822
+ "checkout",
3823
+ "checks",
3824
+ "clone",
3825
+ "diff",
3826
+ "download",
3827
+ "get",
3828
+ "list",
3829
+ "status",
3830
+ "verify",
3831
+ "verify-asset",
3832
+ "view",
3833
+ "watch"
3834
+ ]);
3835
+ var GH_REPO_ASSIGNMENT = /(?:^|\s)GH_REPO=(\S+)/gu;
3836
+ var SELECTOR_GROUP = 1;
3837
+ function selector_value(word, next) {
3838
+ if (SELECTOR_FLAGS.has(word)) return next;
3839
+ return ATTACHED_SELECTOR.exec(word)?.[1];
3840
+ }
3841
+ function selectors_of(args) {
3842
+ return args.map((word, index) => selector_value(word, args[index + NEXT2])).filter((value) => value !== void 0).map((value) => value.replaceAll(QUOTES3, ""));
3843
+ }
3844
+ function is_write_call(args) {
3845
+ const [group = "", action = ""] = direct_pr_create.positional_words(args, SELECTOR_FLAGS);
3846
+ if (group === API_GROUP || READ_GROUPS.has(group)) return false;
3847
+ return !READ_ACTIONS2.has(action);
3848
+ }
3849
+ function assigned_selectors(segments) {
3850
+ return segments.flatMap((segment) => [...segment.matchAll(GH_REPO_ASSIGNMENT)]).map((match) => (match[SELECTOR_GROUP] ?? "").replaceAll(QUOTES3, ""));
3851
+ }
3852
+ function write_selectors(segments, index) {
3853
+ const args = git_argv.arguments_of(segments[index] ?? "", GH_COMMAND6);
3854
+ if (args === void 0 || !is_write_call(args)) return [];
3855
+ return [...selectors_of(args), ...assigned_selectors(segments.slice(0, index + NEXT2))];
3856
+ }
3857
+ var gh_repo_selector = { write_selectors };
3303
3858
 
3304
3859
  // scripts/rules/third-party-write.ts
3305
3860
  function is_third_party_write_segment(segment, session_owner, resolve_placeholder_owners) {
@@ -3321,23 +3876,57 @@ function candidates_at(segments, index, resolve_placeholder_owners) {
3321
3876
  const has_moved = segments.slice(0, index).some((segment) => GH_CONTEXT_CHANGE.test(segment));
3322
3877
  return has_moved ? unreadable_candidates : resolve_placeholder_owners;
3323
3878
  }
3324
- function writes_third_party(command, resolve_owner = repo_party.current_owner, resolve_placeholder_owners = repo_party.placeholder_owners) {
3325
- const segments = shell_segments.segments_of(command);
3326
- const has_write = segments.some(
3327
- (segment) => gh_api.is_gh_api(segment) && gh_api.is_write(segment)
3328
- );
3329
- if (!has_write) return false;
3879
+ function is_claimed_by_narrower_row(command) {
3880
+ return pr_merge.is_pr_merge(command) || repo_admin.is_repo_admin_write(command);
3881
+ }
3882
+ var GRAPHQL_MUTATION = /\bmutation\b/u;
3883
+ var EXPANDED_QUERY = /^(?:-[fF])?"?query=(?!')[\s\S]*\$/u;
3884
+ function is_hidden_or_mutation(command, args) {
3885
+ return GRAPHQL_MUTATION.test(command) || args.some((argument) => EXPANDED_QUERY.test(argument));
3886
+ }
3887
+ function graphql_args(segment) {
3888
+ const call = git_argv.program_of(segment);
3889
+ if (call === void 0 || !gh_api.is_gh_api(segment)) return void 0;
3890
+ return pr_merge.is_graphql_call(call.name, call.args) ? call.args : void 0;
3891
+ }
3892
+ function is_graphql_mutation(command, segment) {
3893
+ const args = graphql_args(segment);
3894
+ if (args === void 0) return false;
3895
+ return is_hidden_or_mutation(command, args) && !is_claimed_by_narrower_row(command);
3896
+ }
3897
+ function is_third_party_selector_write(command, segments, index, session_owner) {
3898
+ const selectors = gh_repo_selector.write_selectors(segments, index);
3899
+ const is_third_party = selectors.some((selector) => {
3900
+ const owner = repo_party.selector_owner(selector);
3901
+ return owner === void 0 || repo_party.classify(session_owner, owner) === repo_party.THIRD_PARTY;
3902
+ });
3903
+ return is_third_party && !is_claimed_by_narrower_row(command);
3904
+ }
3905
+ function is_owner_dependent_write(segments, index) {
3906
+ const segment = segments[index] ?? "";
3907
+ if (gh_api.is_gh_api(segment)) return gh_api.is_write(segment);
3908
+ return gh_repo_selector.write_selectors(segments, index).length > 0;
3909
+ }
3910
+ function writes_to_third_party_owner(command, segments, resolve_owner, resolve_placeholder_owners) {
3911
+ if (segments.every((_segment, index) => !is_owner_dependent_write(segments, index))) return false;
3330
3912
  const session_owner = resolve_owner();
3331
3913
  return segments.some(
3332
- (segment, index) => is_third_party_write_segment(
3914
+ (segment, index) => is_third_party_selector_write(command, segments, index, session_owner) || is_third_party_write_segment(
3333
3915
  segment,
3334
3916
  session_owner,
3335
3917
  candidates_at(segments, index, resolve_placeholder_owners)
3336
3918
  )
3337
3919
  );
3338
3920
  }
3339
- 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`).";
3340
- var ROW24 = {
3921
+ function writes_third_party(command, resolve_owner = repo_party.current_owner, resolve_placeholder_owners = repo_party.placeholder_owners) {
3922
+ const segments = shell_segments.quote_aware_commands_of(
3923
+ shell_segments.joined_continuations(command)
3924
+ );
3925
+ if (segments.some((segment) => is_graphql_mutation(command, segment))) return true;
3926
+ return writes_to_third_party_owner(command, segments, resolve_owner, resolve_placeholder_owners);
3927
+ }
3928
+ var THIRD_PARTY_WRITE_REASON = "\u26D4 third-party write: this `gh` call writes to another owner's repository, or one it cannot read (a GraphQL mutation, an unreadable `-R`) \u2014 Tier C. Check `pnpm josh repo:party <owner/repo>`; first-party, use a REST `repos/<owner>/<repo>` path. Else record it on our Issue and draft it for a person to post. Fires on every occurrence (`upstream-interrupt.md`).";
3929
+ var ROW34 = {
3341
3930
  id: "third-party-write",
3342
3931
  is_trigger: bash_triggers.on_bash_command((command) => writes_third_party(command)),
3343
3932
  reason: THIRD_PARTY_WRITE_REASON,
@@ -3345,65 +3934,36 @@ var ROW24 = {
3345
3934
  about: {
3346
3935
  title: "Third-party write",
3347
3936
  topic: "`upstream-interrupt.md`",
3348
- fires: "a `gh api` write (field flag or a write method) to a repository whose owner is not this session. Every occurrence",
3349
- quiet: "a read, or a first-party owner (`pnpm josh repo:party`)"
3937
+ fires: "a `gh api` write (field flag or a write method) or a `gh <group> <action>` write selected by `-R` / `GH_REPO` to a repository whose owner is not this session, or a `gh api graphql` call whose line names `mutation` or whose query the shell expands, outside the merge and administration rows. Every occurrence",
3938
+ quiet: "a read, a GraphQL query, or a first-party owner (pinned at session start, `pnpm josh repo:party`)"
3350
3939
  }
3351
3940
  };
3352
3941
  var third_party_write = {
3353
- ROW: ROW24,
3942
+ ROW: ROW34,
3354
3943
  THIRD_PARTY_WRITE_REASON,
3355
3944
  writes_third_party
3356
3945
  };
3357
3946
 
3358
- // scripts/rules/timed-rerun.ts
3359
- var TIMED_SEGMENT = /^\s*(?:[A-Za-z_]\w*=\S*\s+)*(?:\S*\/)?(?:time|hyperfine)\s/u;
3360
- function is_timed_run(command) {
3361
- return shell_segments.quote_aware_commands_of(command).some((segment) => TIMED_SEGMENT.test(segment));
3362
- }
3363
- var RECORD_READ = /\bgh\s+api\b.*\/actions\/runs\b|\bjosh\s+(?:lane:stats|lst)\b/u;
3364
- function reads_the_record(command) {
3365
- return RECORD_READ.test(command);
3366
- }
3367
- function measures_duration(command) {
3368
- return reads_the_record(command) || is_timed_run(command);
3369
- }
3370
- var TIMED_RERUN_REASON = "\u26D4 timed re-run: a CI or gate duration is already on record \u2014 read the Actions jobs with `gh api \u2026/actions/runs/<run_id>/jobs` or the gate timings with `pnpm josh lane:stats`. Re-run only when the record cannot answer: reissue the same command once, and post the result on the Issue (`prompts/collaboration-workflow/measurement.md`).";
3371
- var ROW25 = {
3372
- id: "timed-rerun",
3373
- is_trigger: bash_triggers.on_bash_command(is_timed_run),
3374
- reason: TIMED_RERUN_REASON,
3375
- keeps: bash_triggers.on_bash_command(reads_the_record),
3376
- reaches: bash_triggers.on_bash_command(measures_duration),
3377
- about: {
3378
- title: "Timed re-run",
3379
- topic: "`measurement.md`",
3380
- fires: "a command timed with `time` / `/usr/bin/time` / `hyperfine`",
3381
- quiet: "the duration is read from the Actions jobs or `pnpm josh lane:stats`"
3382
- }
3383
- };
3384
- var timed_rerun = {
3385
- ROW: ROW25,
3386
- TIMED_RERUN_REASON,
3387
- is_timed_run,
3388
- reads_the_record
3389
- };
3390
-
3391
3947
  // scripts/rules/delivered-rules.ts
3392
3948
  var SWITCH_ENV_KEY3 = "JOSH_RULE_GUARD";
3393
3949
  var PRETOOL_GUARD = "pnpm josh pretool:guard";
3394
- var STAMP_PREFIX3 = "josh-rule-guard-";
3950
+ var STAMP_PREFIX2 = "josh-rule-guard-";
3395
3951
  var { is_issue_filing, on_bash_command } = bash_triggers;
3396
3952
  var { SHELL_BODY_REASON: SHELL_BODY_REASON2, carries_a_body: carries_a_body2, is_shell_evaluated_body: is_shell_evaluated_body2, keeps_body_safe: keeps_body_safe2 } = shell_body_trigger;
3397
3953
  var DELIVERED_RULES = [
3398
3954
  // First: a third-party write is stopped before the first-party filing rows count anything.
3399
3955
  third_party_write.ROW,
3400
- // The direct call only; `filing-cap` below triggers on `issue:file`. The WIP cap and the fold
3401
- // question are `issue:file`'s own steps, so no row gates them.
3956
+ // Before every first-party row: a credential is refused whatever the call's other faults, and the
3957
+ // commands those rows point at run the same scan in-process.
3958
+ credential_body.ROW,
3959
+ // The direct call only. The filing cap, the WIP cap and the fold question are `issue:file`'s own
3960
+ // steps, so no row gates them.
3402
3961
  direct_filing.ROW,
3403
3962
  // Before the body rows: a comment posted by hand is refused whatever its body, and the command it
3404
3963
  // points at takes the body by path, so the body rows have nothing left to say.
3405
3964
  direct_issue_comment.ROW,
3406
- filing_cap.ROW,
3965
+ // Before `issue-comments`, whose rewrite would add the unfiltered comments to the same read.
3966
+ direct_issue_read.ROW,
3407
3967
  issue_comments.ROW,
3408
3968
  {
3409
3969
  id: "shell-body",
@@ -3419,6 +3979,8 @@ var DELIVERED_RULES = [
3419
3979
  }
3420
3980
  },
3421
3981
  raw_field_body.ROW,
3982
+ // Before `piped-verification`, whose rewrite would run under `pipefail` a check that must not run.
3983
+ round_one_check_related.ROW,
3422
3984
  {
3423
3985
  id: "piped-verification",
3424
3986
  is_trigger: on_bash_command(piped_verification.is_masked_verification),
@@ -3431,7 +3993,7 @@ var DELIVERED_RULES = [
3431
3993
  about: {
3432
3994
  title: "Piped verification",
3433
3995
  topic: "`output-bounds.md`",
3434
- fires: "a pass/fail josh check (`gate` / `check` / `lint*` / `cspell*` / `test*` / `eval` / `overrides` / `ranges`) in front of a pipe \u2014 rewritten under `set -o pipefail` when every filter is `tail` or a full-reading `grep`, refused otherwise",
3996
+ fires: "a pass/fail josh check (`gate` / `check*` / `lint*` / `cspell*` / `test*` / `eval` / `overrides` / `ranges`) in front of a pipe \u2014 rewritten under `set -o pipefail` when every filter reads to the end (`tail`, a full-reading `grep`, `cut`, `sort`, `uniq`, `wc`, `tee`, an `awk` / `sed` that cannot exit early and carries no `<` / `>` / `&`), refused otherwise",
3435
3997
  quiet: "the exit code reaches the caller \u2014 a failure reads as a failure"
3436
3998
  }
3437
3999
  },
@@ -3490,24 +4052,30 @@ var DELIVERED_RULES = [
3490
4052
  lane_park.ROW,
3491
4053
  lane_interactive_ask.ROW,
3492
4054
  agent_model.ROW,
4055
+ investigator_foreground.ROW,
4056
+ reviewer_foreground.ROW,
3493
4057
  lane_carry_conflict.ROW,
3494
4058
  lane_switch_main.ROW,
3495
4059
  // Before `lane-direct-tool`, its one overlap (a timed tool in a lane): the record is the right first
3496
4060
  // act, and `lane-direct-tool` delivers on the reissue of a `time`-prefixed tool.
3497
4061
  timed_rerun.ROW,
3498
4062
  lane_direct_tool.ROW,
4063
+ lane_spilled_read.ROW,
3499
4064
  // Disjoint from `run-tail` by the `-y` flag.
3500
4065
  josh_git_bare.ROW,
3501
4066
  // The Bash-string gaps the deny glob cannot express, read by argv; `delivered-rules-bash.test.ts`
3502
4067
  // pins that no two rows claim one command.
3503
4068
  git_force.ROW,
3504
4069
  worktree_guard.ROW,
3505
- file_body.ROW,
4070
+ ...file_edit_rows.SHELL_ROWS,
3506
4071
  ...permission_guards.ROWS,
3507
4072
  poll_loop.ROW,
3508
4073
  // Before `rule-body`, its one overlap: the cut is the right first act, and `rule-body` delivers on
3509
4074
  // the reissue in the fresh process.
3510
4075
  implementation_cut.ROW,
4076
+ // Before `rule-body`, its one overlap (a whole-file `Write` of a rule document): the right tool is
4077
+ // chosen before the placement questions are asked of its text.
4078
+ file_edit_rows.REWRITE_ROW,
3511
4079
  rule_body_guard.ROW,
3512
4080
  ...oracle_consulted.ROWS
3513
4081
  ];
@@ -3541,31 +4109,32 @@ var INVESTIGATION_RULE = {
3541
4109
  var MEASURED_RULES = [
3542
4110
  ...DELIVERED_RULES,
3543
4111
  BATCHING_RULE,
3544
- INVESTIGATION_RULE
4112
+ INVESTIGATION_RULE,
4113
+ // Delivered by `stop:guard`; listed here so `rule:value` counts its refusals.
4114
+ concise_reply.ROW
3545
4115
  ];
3546
4116
  var BATCH_STAMP = hook_decision.create_refusal_stamp(time_batch_guard.STAMP_PREFIX);
3547
4117
  var BATCH_REFUSAL_WINDOW_MS = 1e4;
3548
4118
  function will_batch_guard_refuse(tail, call, run) {
3549
- if (lane_guard_policy.mode_here("batching") !== "refuse") return false;
3550
- const refused_at_ms = BATCH_STAMP.last_ms(BATCH_STAMP.path(run.transcript));
3551
- if (run.now_ms - refused_at_ms < BATCH_REFUSAL_WINDOW_MS) return true;
3552
- return time_batch_guard.should_block(tail, call, refused_at_ms);
4119
+ const refused_at_ms2 = BATCH_STAMP.last_ms(BATCH_STAMP.path(run.transcript));
4120
+ if (run.now_ms - refused_at_ms2 < BATCH_REFUSAL_WINDOW_MS) return true;
4121
+ return will_refuse2(tail, call, run);
3553
4122
  }
3554
4123
  function is_first_delivery(tail, call, delivered_at_ms, run) {
3555
4124
  return delivered_at_ms === hook_decision.NEVER_MS && !will_batch_guard_refuse(tail, call, run);
3556
4125
  }
3557
4126
  function delivery_decision(rule) {
3558
- const { decide: decide4, already_satisfied } = rule;
3559
- if (decide4 === void 0 && already_satisfied === void 0) return is_first_delivery;
3560
- return function should_block3(tail, call, delivered_at_ms, run) {
3561
- if (already_satisfied?.(tail, call) === true) return false;
3562
- if (decide4 === void 0) return true;
3563
- return decide4(call, run, !will_batch_guard_refuse(tail, call, run), delivered_at_ms);
4127
+ const { decide: decide5, already_satisfied } = rule;
4128
+ if (decide5 === void 0 && already_satisfied === void 0) return is_first_delivery;
4129
+ return function should_block2(tail, call, delivered_at_ms, run) {
4130
+ if (already_satisfied?.(tail, call, run) === true) return false;
4131
+ if (decide5 === void 0) return true;
4132
+ return decide5(call, run, !will_batch_guard_refuse(tail, call, run), delivered_at_ms);
3564
4133
  };
3565
4134
  }
3566
4135
  function guard_of(rule) {
3567
4136
  return hook_decision.create_transcript_guard({
3568
- prefix: `${STAMP_PREFIX3}${rule.id}-`,
4137
+ prefix: `${STAMP_PREFIX2}${rule.id}-`,
3569
4138
  switch_key: SWITCH_ENV_KEY3,
3570
4139
  is_candidate: rule.is_trigger,
3571
4140
  should_block: delivery_decision(rule),
@@ -3578,8 +4147,8 @@ function delivery_path(rule_id, transcript_path) {
3578
4147
  }
3579
4148
  function delivery(raw_payload, now_ms = Date.now()) {
3580
4149
  for (const guard of GUARDS.values()) {
3581
- const reason = guard.refusal(raw_payload, now_ms);
3582
- if (reason !== void 0) return reason;
4150
+ const reason2 = guard.refusal(raw_payload, now_ms);
4151
+ if (reason2 !== void 0) return reason2;
3583
4152
  }
3584
4153
  return void 0;
3585
4154
  }
@@ -3589,9 +4158,8 @@ function is_enabled3() {
3589
4158
  var delivered_rules = {
3590
4159
  DELIVERED_RULES,
3591
4160
  EARLY_HEARTBEAT_REASON: early_heartbeat.EARLY_HEARTBEAT_REASON,
3592
- FILE_BODY_REASON: file_body.FILE_BODY_REASON,
4161
+ FILE_BODY_REASON: file_edit_rows.FILE_BODY_REASON,
3593
4162
  DIRECT_FILING_REASON: direct_filing.DIRECT_FILING_REASON,
3594
- FILING_CAP_REASON: filing_cap.FILING_CAP_REASON,
3595
4163
  GIT_FORCE_REASON: git_force.GIT_FORCE_REASON,
3596
4164
  ISSUE_COMMENTS_REASON: issue_comments.ISSUE_COMMENTS_REASON,
3597
4165
  LANE_INTERACTIVE_ASK_REASON: lane_interactive_ask.LANE_INTERACTIVE_ASK_REASON,
@@ -3621,8 +4189,8 @@ var ANOTHER_COMMAND = "the right act is another command, whose arguments the cal
3621
4189
  var NO_RUN = "the call must not run at all, or only on a person's word \u2014 refusing is the remedy";
3622
4190
  var STOP_NOTIFY = "the hook sends the notify itself; it blocks only when the send fails";
3623
4191
  var HOLD_RELEASE = "the hook releases the hold itself; it blocks only when that release fails";
3624
- var ISSUE_CITATION = "the hook prints the citation lines itself; it blocks only on a number it cannot cite";
3625
4192
  var FILING_OFFER = "the filing needs a title and a body only the model can write";
4193
+ var CONCISE_REPLY = "the shorter reply is prose only the model can write";
3626
4194
  function rewrite() {
3627
4195
  return { remedy: "rewrite", why: REWRITTEN };
3628
4196
  }
@@ -3637,30 +4205,44 @@ var GUARD_REMEDIES = {
3637
4205
  // the same Issue's comments read on the same line
3638
4206
  "lane-background": rewrite(),
3639
4207
  // a lane child's `ship` run in the foreground (`pretool-guard.ts`)
4208
+ "reviewer-foreground": rewrite(),
4209
+ // a lane child's `reviewer` given `run_in_background: false`
3640
4210
  "file-body": judgment(ANOTHER_TOOL),
3641
4211
  // Edit or Write
4212
+ "file-rewrite": judgment(ANOTHER_TOOL),
4213
+ // Edit
4214
+ "sed-in-place": judgment(ANOTHER_TOOL),
4215
+ // Edit
3642
4216
  "shell-body": judgment(
3643
4217
  "which part of the body the shell was meant to expand is the author's, not the hook's"
3644
4218
  ),
3645
4219
  "raw-field-body": judgment("the body moves to a file the call does not name"),
4220
+ "credential-body": judgment(
4221
+ "which text is the credential, and what replaces it, is the author\u2019s"
4222
+ ),
3646
4223
  "rule-body": judgment("a trigger-plus-pointer line is written, not derived"),
3647
4224
  "direct-filing": judgment(ANOTHER_COMMAND),
3648
4225
  "direct-issue-comment": judgment(ANOTHER_COMMAND),
4226
+ "direct-issue-read": judgment(ANOTHER_COMMAND),
3649
4227
  "direct-pr-create": judgment(ANOTHER_COMMAND),
3650
4228
  "pr-merge": judgment(ANOTHER_COMMAND),
3651
4229
  "josh-git-bare": judgment(ANOTHER_COMMAND),
3652
4230
  "run-tail": judgment(ANOTHER_COMMAND),
3653
4231
  "pre-gate-cut": judgment(ANOTHER_COMMAND),
3654
4232
  "implementation-cut": judgment(ANOTHER_COMMAND),
4233
+ "round-one-check-related": judgment(ANOTHER_COMMAND),
3655
4234
  "early-heartbeat": judgment(ANOTHER_COMMAND),
3656
4235
  "poll-loop": judgment(ANOTHER_COMMAND),
3657
4236
  "lane-park": judgment(ANOTHER_COMMAND),
3658
4237
  "lane-split-park": judgment(ANOTHER_COMMAND),
3659
4238
  "lane-interactive-ask": judgment(ANOTHER_COMMAND),
3660
4239
  "agent-model": judgment("which model fits the unit is the author's call"),
4240
+ "investigator-foreground": judgment("which reads go beside the dispatch is the author\u2019s call"),
3661
4241
  "lane-carry-conflict": judgment(ANOTHER_COMMAND),
3662
4242
  "lane-switch-main": judgment(ANOTHER_COMMAND),
3663
- "lane-direct-tool": judgment(ANOTHER_COMMAND),
4243
+ "lane-direct-tool": rewrite(),
4244
+ // a one-command line behind `pnpm josh reserved-run <weight> --`
4245
+ "lane-spilled-read": judgment(ANOTHER_COMMAND),
3664
4246
  "timed-rerun": judgment(ANOTHER_COMMAND),
3665
4247
  "oracle-consulted": judgment(ANOTHER_COMMAND),
3666
4248
  "test-declared": judgment(ANOTHER_COMMAND),
@@ -3671,17 +4253,17 @@ var GUARD_REMEDIES = {
3671
4253
  "parent-cut": judgment(ANOTHER_COMMAND),
3672
4254
  "issue-watch": judgment(ANOTHER_COMMAND),
3673
4255
  "third-party-write": judgment(NO_RUN),
3674
- "filing-cap": judgment(NO_RUN),
3675
4256
  "git-force": judgment(NO_RUN),
3676
4257
  "worktree-mutation": judgment(NO_RUN),
3677
4258
  "index-mutation": judgment(NO_RUN),
3678
4259
  "destructive-command": judgment(NO_RUN),
4260
+ "repo-admin": judgment(NO_RUN),
3679
4261
  "protected-file": judgment(NO_RUN),
3680
4262
  // The `Stop` rows: the hook judges a finished reply, so it has no call to rewrite.
3681
4263
  "stop-notify": judgment(STOP_NOTIFY),
3682
- "issue-citation": judgment(ISSUE_CITATION),
3683
4264
  "filing-offer": judgment(FILING_OFFER),
3684
- "hold-release": judgment(HOLD_RELEASE)
4265
+ "hold-release": judgment(HOLD_RELEASE),
4266
+ "concise-reply": judgment(CONCISE_REPLY)
3685
4267
  };
3686
4268
  var FAMILY_SEPARATOR = ":";
3687
4269
  function entry_of(id) {
@@ -3940,13 +4522,13 @@ var run_watcher_guard = {
3940
4522
 
3941
4523
  // scripts/run/run-watcher-hook.ts
3942
4524
  var SWITCH_ENV_KEY4 = "JOSH_WATCHER_GUARD";
3943
- var STAMP_PREFIX4 = "josh-watcher-guard-";
4525
+ var STAMP_PREFIX3 = "josh-watcher-guard-";
3944
4526
  var payload_schema = z5.object({ transcript_path: z5.string().min(1) });
3945
4527
  function transcript_of(raw_payload) {
3946
4528
  return json_value.parse_with(raw_payload, payload_schema)?.transcript_path;
3947
4529
  }
3948
4530
  function fires_once(transcript, now_ms) {
3949
- const stamp = hook_decision.create_refusal_stamp(STAMP_PREFIX4);
4531
+ const stamp = hook_decision.create_refusal_stamp(STAMP_PREFIX3);
3950
4532
  const target = stamp.path(transcript);
3951
4533
  if (stamp.last_ms(target) !== hook_decision.NEVER_MS) return false;
3952
4534
  return stamp.record(target, now_ms);
@@ -3969,7 +4551,7 @@ var run_watcher_hook = { transcript_of, watcher_hook_reason };
3969
4551
 
3970
4552
  // scripts/run/run-parent-cut-hook.ts
3971
4553
  var SWITCH_ENV_KEY5 = "JOSH_PARENT_CUT_GUARD";
3972
- var STAMP_PREFIX5 = "josh-parent-cut-guard-";
4554
+ var STAMP_PREFIX4 = "josh-parent-cut-guard-";
3973
4555
  var QUIET_WINDOW_MS = 6e5;
3974
4556
  function current_state2() {
3975
4557
  return {
@@ -3977,19 +4559,19 @@ function current_state2() {
3977
4559
  verdict: () => implementation_cut_verdict.reused_verdict(() => cost_cli.session_verdict())
3978
4560
  };
3979
4561
  }
3980
- var THRESHOLD_TEXT2 = `${figure_format.format_tokens(CONTEXT_CUT_THRESHOLD)}-token`;
3981
- 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.`;
4562
+ var THRESHOLD_TEXT2 = "`pnpm josh cost --cut`";
4563
+ var PARENT_CUT_REASON = `\u26D4 backlogrun parent hand-off: this session drives a \`backlogrun\` and its recent context 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.`;
3982
4564
  async function is_due2(state) {
3983
4565
  const carry = await state.carry();
3984
4566
  if (carry === void 0 || run_headless.is_cut_capped(carry)) return false;
3985
4567
  return state.verdict() === cost_verdict.OVER_VERDICT;
3986
4568
  }
3987
4569
  function is_quiet(target, now_ms) {
3988
- const last_ms = hook_decision.create_refusal_stamp(STAMP_PREFIX5).last_ms(target);
4570
+ const last_ms = hook_decision.create_refusal_stamp(STAMP_PREFIX4).last_ms(target);
3989
4571
  return last_ms !== hook_decision.NEVER_MS && now_ms - last_ms < QUIET_WINDOW_MS;
3990
4572
  }
3991
4573
  async function refusal_for(transcript, now_ms, state) {
3992
- const stamp = hook_decision.create_refusal_stamp(STAMP_PREFIX5);
4574
+ const stamp = hook_decision.create_refusal_stamp(STAMP_PREFIX4);
3993
4575
  const target = stamp.path(transcript);
3994
4576
  if (is_quiet(target, now_ms) || !await is_due2(state)) return void 0;
3995
4577
  return stamp.record(target, now_ms) ? PARENT_CUT_REASON : void 0;
@@ -4002,11 +4584,11 @@ function is_unit_call(raw_payload) {
4002
4584
  return false;
4003
4585
  }
4004
4586
  }
4005
- function is_exempt2(raw_payload) {
4587
+ function is_exempt(raw_payload) {
4006
4588
  return !hook_decision.is_switch_enabled(SWITCH_ENV_KEY5) || is_unit_call(raw_payload);
4007
4589
  }
4008
4590
  async function parent_cut_reason(raw_payload, now_ms = Date.now(), state = current_state2()) {
4009
- if (is_exempt2(raw_payload)) return void 0;
4591
+ if (is_exempt(raw_payload)) return void 0;
4010
4592
  const transcript = run_watcher_hook.transcript_of(raw_payload);
4011
4593
  if (transcript === void 0) return void 0;
4012
4594
  return await refusal_for(transcript, now_ms, state);
@@ -4047,14 +4629,14 @@ var WRITING_FLAGS = /* @__PURE__ */ new Set([
4047
4629
  "--stop",
4048
4630
  "--wait"
4049
4631
  ]);
4050
- var FLAG_PREFIX3 = "-";
4632
+ var FLAG_PREFIX5 = "-";
4051
4633
  var NAME_WORDS = 2;
4052
4634
  var SIGNATURE_SEPARATOR = " ";
4053
4635
  function read_key(signature) {
4054
4636
  const words = signature.split(SIGNATURE_SEPARATOR);
4055
4637
  const name = words.slice(0, NAME_WORDS).join(SIGNATURE_SEPARATOR);
4056
4638
  if (!STATE_READS.has(name) || words.some((word) => WRITING_FLAGS.has(word))) return void 0;
4057
- return words.filter((word) => !word.startsWith(FLAG_PREFIX3)).join(SIGNATURE_SEPARATOR);
4639
+ return words.filter((word) => !word.startsWith(FLAG_PREFIX5)).join(SIGNATURE_SEPARATOR);
4058
4640
  }
4059
4641
  function read_keys(signatures) {
4060
4642
  return signatures.map((signature) => read_key(signature)).filter((key) => key !== void 0);
@@ -4105,111 +4687,80 @@ var run_poll_hook = {
4105
4687
  safe_poll_reason
4106
4688
  };
4107
4689
 
4108
- // scripts/hooks/batch-guard.ts
4109
- var SWITCH_ENV_KEY7 = "JOSH_BATCH_GUARD";
4110
- function is_batch_candidate(call) {
4111
- return time_batch_guard.is_guarded_call(call) || time_batch_guard.is_notice_call(call);
4112
- }
4113
- function batching_mode() {
4114
- return lane_guard_policy.mode_here("batching");
4115
- }
4116
- function is_candidate3(call) {
4117
- return is_batch_candidate(call) && batching_mode() !== "off";
4118
- }
4119
- function should_block_here2(tail, call, refused_at_ms) {
4120
- if (batching_mode() !== "refuse") return false;
4121
- return time_batch_guard.should_block(tail, call, refused_at_ms);
4122
- }
4123
- function should_notify_here2(tail, call, notified_at_ms) {
4124
- if (time_batch_guard.should_notify(tail, call, notified_at_ms)) return true;
4125
- if (batching_mode() !== "notice") return false;
4126
- return time_batch_guard.should_block(
4127
- tail,
4128
- call,
4129
- notified_at_ms,
4130
- time_batch_guard.NOTICE_REFIRE_EVERY
4131
- );
4132
- }
4133
- function notice_text(call, tail) {
4134
- const base = time_batch_guard.is_notice_call(call) ? time_batch_guard.NOTICE : time_batch_guard.LANE_NOTICE;
4135
- return `${base}${time_batch_guard.recent_candidates(tail)}`;
4136
- }
4137
- var GUARD3 = hook_decision.create_transcript_guard({
4138
- prefix: time_batch_guard.STAMP_PREFIX,
4139
- switch_key: SWITCH_ENV_KEY7,
4140
- is_candidate: is_candidate3,
4141
- should_block: should_block_here2,
4142
- reason: time_batch_guard.REASON,
4143
- // The whole-file write is notified rather than refused, on a record of its own so it never spends
4144
- // the refusal's stamp; the lane-child refusable-call notice kit#2164 added
4145
- // here fires again since kit#2276 restored batching to `notice` in a lane child.
4146
- notify: {
4147
- prefix: time_batch_guard.NOTICE_STAMP_PREFIX,
4148
- should_notify: should_notify_here2,
4149
- text: notice_text
4150
- }
4151
- });
4152
- var batch_refusal = GUARD3.refusal;
4153
- var batch_outcome = GUARD3.outcome;
4154
- var { is_enabled: is_enabled4, refusal_path: refusal_path3 } = GUARD3;
4155
- var notice_refusal_path2 = hook_decision.create_refusal_stamp(
4156
- time_batch_guard.NOTICE_STAMP_PREFIX
4157
- ).path;
4158
- var { deny_envelope: deny_envelope3, load_environment_file: load_environment_file2, DISABLED_VALUES: DISABLED_VALUES3 } = hook_decision;
4159
-
4160
- // scripts/hooks/shell-rules-notice.ts
4161
- var STAMP_PREFIX6 = "josh-shell-rules-notice-";
4162
- var NOTICE2 = "Shell rules: change an existing file with Edit or Write, never a script or heredoc that writes it (file-edits.md); run a josh check with no pipe or under `set -o pipefail`, never `| head` (output-bounds.md).";
4163
- var STAMP = hook_decision.create_refusal_stamp(STAMP_PREFIX6);
4164
- function notice(raw_payload) {
4165
- const payload = hook_decision.parse_hook_payload(raw_payload);
4166
- if (payload?.tool_name !== cost_blocks.BASH_TOOL) return void 0;
4167
- const target = STAMP.path(payload.transcript_path);
4168
- if (STAMP.last_ms(target) !== hook_decision.NEVER_MS || !STAMP.record(target, Date.now())) {
4169
- return void 0;
4170
- }
4171
- return NOTICE2;
4172
- }
4173
- var shell_rules_notice = { NOTICE: NOTICE2, notice, stamp_path: STAMP.path };
4174
-
4175
4690
  // scripts/hooks/step-zero-notice.ts
4176
- import path4 from "node:path";
4177
- var STAMP_PREFIX7 = "josh-step-zero-notice-";
4178
- var EDIT_TOOLS3 = /* @__PURE__ */ new Set(["Edit", "Write"]);
4691
+ import path3 from "node:path";
4692
+ var STAMP_PREFIX5 = "josh-step-zero-notice-";
4693
+ var EDIT_TOOLS2 = /* @__PURE__ */ new Set(["Edit", "Write"]);
4179
4694
  var PARENT_PREFIX = "..";
4180
- var NOTICE3 = "Before writing any implementation code: present the two-layer work summary (Now / Change / Check, then every change with its test) once per Issue. Full rule: Code Change Rules Step 0 in CLAUDE.md.";
4181
- var STAMP2 = hook_decision.create_refusal_stamp(STAMP_PREFIX7);
4695
+ var NOTICE = "Before writing any implementation code: present the two-layer work summary (Now / Change / Check, then every change with its test) once per Issue. Full rule: Code Change Rules Step 0 in CLAUDE.md.";
4696
+ var STAMP3 = hook_decision.create_refusal_stamp(STAMP_PREFIX5);
4182
4697
  function edited_paths(tool_input) {
4183
4698
  if (!json_value.is_record(tool_input)) return [];
4184
- const { file_path, file_paths } = tool_input;
4699
+ const { file_path: file_path2, file_paths } = tool_input;
4185
4700
  const listed = Array.isArray(file_paths) ? file_paths : [];
4186
- return [file_path, ...listed].filter(
4701
+ return [file_path2, ...listed].filter(
4187
4702
  (entry) => typeof entry === "string" && entry.length > 0
4188
4703
  );
4189
4704
  }
4190
- function is_runtime_file(file_path, root) {
4191
- const relative = path4.relative(root, path4.resolve(root, file_path));
4192
- if (relative.startsWith(PARENT_PREFIX) || path4.isAbsolute(relative)) return false;
4193
- return !test_declared_logic.is_exempt(relative.split(path4.sep).join("/"));
4705
+ function is_runtime_file(file_path2, root) {
4706
+ const relative = path3.relative(root, path3.resolve(root, file_path2));
4707
+ if (relative.startsWith(PARENT_PREFIX) || path3.isAbsolute(relative)) return false;
4708
+ return !test_declared_logic.is_exempt(relative.split(path3.sep).join("/"));
4194
4709
  }
4195
4710
  function is_candidate4(raw_payload, root) {
4196
4711
  const payload = hook_decision.parse_hook_payload(raw_payload);
4197
- if (payload === void 0 || !EDIT_TOOLS3.has(payload.tool_name)) return void 0;
4712
+ if (payload === void 0 || !EDIT_TOOLS2.has(payload.tool_name)) return void 0;
4198
4713
  const has_runtime_file = edited_paths(payload.tool_input).some(
4199
- (file_path) => is_runtime_file(file_path, root)
4714
+ (file_path2) => is_runtime_file(file_path2, root)
4200
4715
  );
4201
4716
  return has_runtime_file ? payload.transcript_path : void 0;
4202
4717
  }
4203
- function notice2(raw_payload, root = process.cwd()) {
4718
+ function is_runtime_edit(raw_payload, root = process.cwd()) {
4719
+ return is_candidate4(raw_payload, root) !== void 0;
4720
+ }
4721
+ function notice(raw_payload, root = process.cwd()) {
4204
4722
  const transcript = is_candidate4(raw_payload, root);
4205
4723
  if (transcript === void 0) return void 0;
4206
- const target = STAMP2.path(transcript);
4207
- if (STAMP2.last_ms(target) !== hook_decision.NEVER_MS || !STAMP2.record(target, Date.now())) {
4724
+ const target = STAMP3.path(transcript);
4725
+ if (STAMP3.last_ms(target) !== hook_decision.NEVER_MS || !STAMP3.record(target, Date.now())) {
4726
+ return void 0;
4727
+ }
4728
+ return NOTICE;
4729
+ }
4730
+ var step_zero_notice = { NOTICE, is_runtime_edit, notice, stamp_path: STAMP3.path };
4731
+
4732
+ // scripts/hooks/lane-plan-guard.ts
4733
+ var COMMAND_DOC = '`docs/josh-commands-run.md` \u2192 "`josh lane:plan`"';
4734
+ var OUTCOMES = "on `yield` end the session with no edit (its lane is given back); on `wait` run it again";
4735
+ function reason_text(issue) {
4736
+ const command = `\`pnpm josh lane:plan ${issue} <path>...\``;
4737
+ return `\u26D4 lane plan: run ${command} naming every file the work summary changes before the first implementation edit \u2014 ${OUTCOMES} (${COMMAND_DOC}).`;
4738
+ }
4739
+ function lane_child_edit(raw_payload, directory, source) {
4740
+ if (!lane_child_marker.is_child_of(directory, source)) return void 0;
4741
+ return step_zero_notice.is_runtime_edit(raw_payload, directory) ? lane_child_marker.marked_issue(source) : void 0;
4742
+ }
4743
+ function reason(raw_payload, directory = process.cwd(), source = process.env) {
4744
+ const issue = lane_child_edit(raw_payload, directory, source);
4745
+ if (issue === void 0) return void 0;
4746
+ return lane_plan.is_uncleared(Number(issue), directory) ? reason_text(issue) : void 0;
4747
+ }
4748
+ var lane_plan_guard = { reason };
4749
+
4750
+ // scripts/hooks/shell-rules-notice.ts
4751
+ var STAMP_PREFIX6 = "josh-shell-rules-notice-";
4752
+ var NOTICE2 = "Shell rules: change an existing file with Edit or Write, never a script or heredoc that writes it (file-edits.md); run a josh check with no pipe or under `set -o pipefail`, never `| head` (output-bounds.md).";
4753
+ var STAMP4 = hook_decision.create_refusal_stamp(STAMP_PREFIX6);
4754
+ function notice2(raw_payload) {
4755
+ const payload = hook_decision.parse_hook_payload(raw_payload);
4756
+ if (payload?.tool_name !== cost_blocks.BASH_TOOL) return void 0;
4757
+ const target = STAMP4.path(payload.transcript_path);
4758
+ if (STAMP4.last_ms(target) !== hook_decision.NEVER_MS || !STAMP4.record(target, Date.now())) {
4208
4759
  return void 0;
4209
4760
  }
4210
- return NOTICE3;
4761
+ return NOTICE2;
4211
4762
  }
4212
- var step_zero_notice = { NOTICE: NOTICE3, notice: notice2, stamp_path: STAMP2.path };
4763
+ var shell_rules_notice = { NOTICE: NOTICE2, notice: notice2, stamp_path: STAMP4.path };
4213
4764
 
4214
4765
  // scripts/hooks/pretool-guard.ts
4215
4766
  var CLEAR = { reason: void 0, notice: void 0, fault: void 0 };
@@ -4217,8 +4768,8 @@ function is_clear(outcome) {
4217
4768
  return outcome.reason === void 0 && outcome.notice === void 0 && outcome.fault === void 0;
4218
4769
  }
4219
4770
  function combine_outcomes(outcomes, rule_reason) {
4220
- const reason = outcomes.find((outcome) => outcome.reason !== void 0)?.reason ?? rule_reason;
4221
- if (reason !== void 0) return { reason, notice: void 0, fault: void 0 };
4771
+ const reason2 = outcomes.find((outcome) => outcome.reason !== void 0)?.reason ?? rule_reason;
4772
+ if (reason2 !== void 0) return { reason: reason2, notice: void 0, fault: void 0 };
4222
4773
  return outcomes.find((outcome) => !is_clear(outcome)) ?? CLEAR;
4223
4774
  }
4224
4775
  function with_session_notice(outcome, raw_payload) {
@@ -4230,7 +4781,8 @@ function guard_outcome(raw_payload) {
4230
4781
  const outcomes = [
4231
4782
  batch_outcome(raw_payload),
4232
4783
  investigation_outcome(raw_payload),
4233
- duplicate_read_outcome(raw_payload)
4784
+ duplicate_read_outcome(raw_payload),
4785
+ { reason: lane_plan_guard.reason(raw_payload), notice: void 0, fault: void 0 }
4234
4786
  ];
4235
4787
  return combine_outcomes(outcomes, delivered_rules.delivery(raw_payload));
4236
4788
  }
@@ -4249,8 +4801,8 @@ async function async_reason(raw_payload) {
4249
4801
  async function pretool_outcome_async(raw_payload) {
4250
4802
  const base = guard_outcome(raw_payload);
4251
4803
  if (!is_clear(base)) return base;
4252
- const reason = await async_reason(raw_payload);
4253
- if (reason !== void 0) return { reason, notice: void 0, fault: void 0 };
4804
+ const reason2 = await async_reason(raw_payload);
4805
+ if (reason2 !== void 0) return { reason: reason2, notice: void 0, fault: void 0 };
4254
4806
  const outcome = with_session_notice(base, raw_payload);
4255
4807
  await mark_phase(outcome);
4256
4808
  return outcome;