@joshuafolkken/kit 1.1455.0 → 1.1514.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (374) hide show
  1. package/.claude/settings.json +6 -6
  2. package/.claude/skills/dependency-update/SKILL.md +26 -55
  3. package/.claude/skills/epic-commands/SKILL.md +83 -156
  4. package/.claude/skills/epic-commands/epic-bundle.md +51 -79
  5. package/.claude/skills/epic-commands/execution-waves.md +43 -81
  6. package/.claude/skills/verify-ui/SKILL.md +24 -50
  7. package/.claude/skills/workflow-commands/SKILL.md +7 -9
  8. package/.claude/skills/workflow-commands/background-commands.md +31 -46
  9. package/.claude/skills/workflow-commands/backlogrun-child.md +65 -85
  10. package/.claude/skills/workflow-commands/backlogrun-lanes.md +86 -125
  11. package/.claude/skills/workflow-commands/backlogrun-park.md +68 -118
  12. package/.claude/skills/workflow-commands/backlogrun-progress.md +17 -28
  13. package/.claude/skills/workflow-commands/backlogrun-recovery.md +17 -26
  14. package/.claude/skills/workflow-commands/backlogrun-steps.md +35 -52
  15. package/.claude/skills/workflow-commands/backlogrun.md +31 -50
  16. package/.claude/skills/workflow-commands/chain-rule.md +31 -40
  17. package/.claude/skills/workflow-commands/delegation.md +12 -8
  18. package/.claude/skills/workflow-commands/entry-sequence.md +1 -3
  19. package/.claude/skills/workflow-commands/followup.md +55 -77
  20. package/.claude/skills/workflow-commands/fullrun-steps.md +3 -5
  21. package/.claude/skills/workflow-commands/fullrun.md +4 -6
  22. package/.claude/skills/workflow-commands/halfrun.md +3 -7
  23. package/.claude/skills/workflow-commands/into-target.md +1 -2
  24. package/.claude/skills/workflow-commands/issue-comments.md +43 -60
  25. package/.claude/skills/workflow-commands/issue-scout.md +3 -2
  26. package/.claude/skills/workflow-commands/kickoff.md +65 -74
  27. package/.claude/skills/workflow-commands/latest-gate.md +2 -6
  28. package/.claude/skills/workflow-commands/needs-human-review.md +2 -5
  29. package/.claude/skills/workflow-commands/observation-filing.md +59 -118
  30. package/.claude/skills/workflow-commands/observation-ledger.md +52 -112
  31. package/.claude/skills/workflow-commands/pre-gate-cut.md +88 -142
  32. package/.claude/skills/workflow-commands/prerequisite.md +11 -17
  33. package/.claude/skills/workflow-commands/progress-watcher.md +23 -33
  34. package/.claude/skills/workflow-commands/prrun.md +3 -5
  35. package/.claude/skills/workflow-commands/retrospective.md +35 -54
  36. package/.claude/skills/workflow-commands/split-assessment.md +32 -58
  37. package/.claude/skills/workflow-commands/target-repository.md +1 -2
  38. package/.claude/skills/workflow-commands/working-tree-hold.md +54 -73
  39. package/.codex/hooks.json +6 -6
  40. package/.github/actions/setup-node/action.yml +1 -1
  41. package/.github/workflows/ci.yml +1 -1
  42. package/.github/workflows/publish.yml +2 -2
  43. package/CLAUDE.md +7 -6
  44. package/cspell/index.yaml +7 -0
  45. package/dist/CLAUDE.md +7 -6
  46. package/dist/commands/document-section-cli.js +67 -34
  47. package/dist/hooks/batch-guard.js +6 -6
  48. package/dist/hooks/chunk-4KKD2UTK.js +66 -0
  49. package/dist/hooks/{chunk-GPLGZ6IC.js → chunk-6TS2JD6D.js} +14 -3
  50. package/dist/hooks/chunk-7DSBJVFH.js +50 -0
  51. package/dist/hooks/{chunk-5H4SSDSD.js → chunk-A65Y4YOR.js} +3 -3
  52. package/dist/hooks/{chunk-3C4XRYG6.js → chunk-APUO6U4J.js} +102 -19
  53. package/dist/hooks/{chunk-IX6TTTI6.js → chunk-EFXE6C37.js} +8 -22
  54. package/dist/hooks/chunk-GP4IEC4O.js +20 -0
  55. package/dist/hooks/chunk-JP45SOCJ.js +56 -0
  56. package/dist/hooks/{chunk-OLMO72ON.js → chunk-LAOV7NLM.js} +1 -1
  57. package/dist/hooks/chunk-M4HGZV7U.js +83 -0
  58. package/dist/hooks/{chunk-TBJFTSUY.js → chunk-O6GDRNJB.js} +9 -7
  59. package/dist/hooks/{chunk-HFGWYGUV.js → chunk-PF2NZL2A.js} +5 -5
  60. package/dist/hooks/{chunk-WNLVGT7E.js → chunk-PTICX4OL.js} +4 -5
  61. package/dist/hooks/{chunk-HHI6FO3R.js → chunk-RT27QRJW.js} +763 -1515
  62. package/dist/hooks/{chunk-7WQBPLUN.js → chunk-SYOTDYCI.js} +1149 -1095
  63. package/dist/hooks/{chunk-LIAFCXL7.js → chunk-TNUMY6NL.js} +1519 -1332
  64. package/dist/hooks/chunk-UPUZF3AN.js +279 -0
  65. package/dist/hooks/{chunk-Z4XI7CZS.js → chunk-WK6SMBIC.js} +1932 -784
  66. package/dist/hooks/codex-hook-adapter.js +17 -14
  67. package/dist/hooks/duplicate-read-guard.js +7 -7
  68. package/dist/hooks/format-edited.js +5 -4
  69. package/dist/hooks/inputs.json +302 -247
  70. package/dist/hooks/investigation-guard.js +8 -11
  71. package/dist/hooks/pretool-guard.js +16 -13
  72. package/dist/hooks/session-lang.js +372 -26
  73. package/dist/hooks/stop-guard.js +2593 -189
  74. package/dist/josh.js +2128 -282
  75. package/dist/managed-marker/index.js +1 -2
  76. package/dist/version/index.js +15 -12
  77. package/eslint/base.js +2 -1
  78. package/eslint/rules/unicorn.js +16 -0
  79. package/lefthook/base.yml +5 -3
  80. package/package.json +14 -12
  81. package/prompts/coding-standards.md +12 -22
  82. package/prompts/collaboration-workflow/file-edits.md +25 -45
  83. package/prompts/collaboration-workflow/gh-rest.md +19 -36
  84. package/prompts/collaboration-workflow/glossary.md +5 -7
  85. package/prompts/collaboration-workflow/issue-citation.md +6 -14
  86. package/prompts/collaboration-workflow/issue-template.md +40 -32
  87. package/prompts/collaboration-workflow/lane-instructions.md +35 -0
  88. package/prompts/collaboration-workflow/operating-rules.md +33 -46
  89. package/prompts/collaboration-workflow/output-bounds.md +15 -15
  90. package/prompts/collaboration-workflow/overview.md +14 -19
  91. package/prompts/collaboration-workflow/plan-comment.md +13 -13
  92. package/prompts/collaboration-workflow/principles.md +57 -54
  93. package/prompts/collaboration-workflow/proposal-request.md +1 -9
  94. package/prompts/collaboration-workflow/report-format.md +41 -49
  95. package/prompts/collaboration-workflow/residency.md +15 -11
  96. package/prompts/collaboration-workflow/rule-delivery.md +7 -15
  97. package/prompts/collaboration-workflow/shell-body.md +10 -20
  98. package/prompts/collaboration-workflow/turn-batching.md +8 -14
  99. package/prompts/collaboration-workflow/upstream-interrupt.md +26 -34
  100. package/prompts/collaboration-workflow/wip-cap.md +26 -32
  101. package/prompts/collaboration-workflow.md +25 -25
  102. package/prompts/refactoring.md +33 -43
  103. package/prompts/review-rubric.md +84 -129
  104. package/prompts/review.md +110 -141
  105. package/prompts/sonar-hotspot-handling.md +22 -50
  106. package/prompts/testing-guide.md +48 -99
  107. package/scripts/adopt/adopt-toolkits.ts +2 -4
  108. package/scripts/agent/agent-argv.ts +20 -4
  109. package/scripts/agent/agent-exit-record.ts +23 -1
  110. package/scripts/agent/agent-role-profile.ts +101 -45
  111. package/scripts/agent/agent-safe-text.ts +60 -0
  112. package/scripts/agent/claude-agent-argv.ts +14 -0
  113. package/scripts/agent/codex-agent-argv.ts +9 -2
  114. package/scripts/agent/lane-instructions.ts +144 -0
  115. package/scripts/backlog/backlog-budget-cli.ts +1 -2
  116. package/scripts/backlog/backlog-budget.ts +4 -4
  117. package/scripts/backlog/backlog-drive-carried.ts +26 -0
  118. package/scripts/backlog/backlog-drive-cli.ts +10 -5
  119. package/scripts/backlog/backlog-drive-named.ts +3 -5
  120. package/scripts/backlog/backlog-drive-restore.ts +28 -2
  121. package/scripts/backlog/backlog-drive-seats.ts +27 -0
  122. package/scripts/backlog/backlog-drive.ts +11 -6
  123. package/scripts/backlog/backlog-occupied.ts +56 -0
  124. package/scripts/backlog/backlog-ready.ts +59 -15
  125. package/scripts/behavior/behavior-rules.ts +3 -2
  126. package/scripts/cost-runtime/codex-usage.ts +4 -2
  127. package/scripts/cost-runtime/cost-cli.ts +1 -2
  128. package/scripts/delegation/duplicate-read-guard.ts +2 -2
  129. package/scripts/delegation/investigation-guard.ts +15 -65
  130. package/scripts/delegation/investigation-reads.ts +1 -2
  131. package/scripts/discovery/repo-discovery.ts +4 -2
  132. package/scripts/discovery/repo-origin.ts +2 -4
  133. package/scripts/doctor/doctor-logic.ts +6 -3
  134. package/scripts/document/document-byte-budget.ts +28 -27
  135. package/scripts/document/document-byte-check.ts +1 -2
  136. package/scripts/document/document-section-cli.ts +17 -5
  137. package/scripts/document/entry-read-budget.ts +3 -3
  138. package/scripts/document/read-set-cli.ts +4 -1
  139. package/scripts/epic/epic-audit-orphans.ts +5 -8
  140. package/scripts/epic/epic-audit-rationale.ts +8 -2
  141. package/scripts/epic/epic-audit.ts +52 -1
  142. package/scripts/epic/epic-bundle-cli.ts +1 -2
  143. package/scripts/epic/epic-bundle-evidence.ts +49 -26
  144. package/scripts/epic/epic-bundle-referenced.ts +14 -3
  145. package/scripts/epic/epic-bundle.ts +17 -9
  146. package/scripts/epic/epic-chains.ts +6 -2
  147. package/scripts/epic/epic-close-comment.ts +1 -2
  148. package/scripts/followup/git-followup-workflow.ts +19 -1
  149. package/scripts/gate/check-inputs.ts +129 -0
  150. package/scripts/gate/check-reuse.ts +162 -0
  151. package/scripts/gate/core-budget.ts +93 -44
  152. package/scripts/gate/core-wait.ts +130 -0
  153. package/scripts/gate/free-cores.ts +49 -0
  154. package/scripts/gate/gate-ledger.ts +17 -3
  155. package/scripts/gate/gate-plan.ts +23 -1
  156. package/scripts/gate/gate-report.ts +15 -2
  157. package/scripts/gate/gate-run-steps.ts +150 -0
  158. package/scripts/gate/held-mark.ts +55 -0
  159. package/scripts/gate/machine-capacity.ts +70 -11
  160. package/scripts/gate/pre-commit-type-check.ts +14 -1
  161. package/scripts/gate/verification-gate.ts +65 -127
  162. package/scripts/gh/git-gh-issue-rest.ts +1 -0
  163. package/scripts/gh/git-gh-issue-write.ts +24 -2
  164. package/scripts/gh/git-pr.ts +19 -3
  165. package/scripts/git/git-common-directory.ts +3 -3
  166. package/scripts/git/git-diff-reads.ts +16 -9
  167. package/scripts/git/git-execa-error.ts +1 -2
  168. package/scripts/git/main-merge-regenerate.ts +78 -0
  169. package/scripts/git/main-merge.ts +14 -5
  170. package/scripts/hooks/batch-guard.ts +5 -8
  171. package/scripts/hooks/format-edited-file.ts +10 -4
  172. package/scripts/hooks/pre-commit-lint.ts +91 -0
  173. package/scripts/hooks/pre-push-unit.ts +15 -4
  174. package/scripts/hooks/pretool-guard.ts +48 -79
  175. package/scripts/hooks/reserved-run.ts +1 -0
  176. package/scripts/hooks/run-hook.sh +10 -12
  177. package/scripts/hooks/step-zero-notice.ts +3 -2
  178. package/scripts/hooks/stop-actions.ts +204 -0
  179. package/scripts/hooks/stop-guard.ts +62 -21
  180. package/scripts/init/hook-command-rewrite.ts +6 -2
  181. package/scripts/init/hook-launch.ts +12 -7
  182. package/scripts/init/init-logic-deploy-vps.ts +4 -2
  183. package/scripts/issue/behavior-change-lint.ts +12 -6
  184. package/scripts/issue/expected-effect-lint.ts +59 -0
  185. package/scripts/issue/issue-cite-cli.ts +35 -15
  186. package/scripts/issue/issue-classification.ts +1 -1
  187. package/scripts/issue/issue-lint-cli.ts +5 -3
  188. package/scripts/issue/issue-scout-cli.ts +22 -16
  189. package/scripts/issue/markdown-section.ts +19 -5
  190. package/scripts/josh/command-usage.ts +110 -0
  191. package/scripts/josh/file-map-stamp.ts +1 -3
  192. package/scripts/josh/hook-decision.ts +17 -2
  193. package/scripts/josh/josh-command-map.ts +2 -0
  194. package/scripts/josh/josh-command-types.ts +55 -9
  195. package/scripts/josh/josh-commands-ai.ts +13 -0
  196. package/scripts/josh/josh-commands-development.ts +1 -1
  197. package/scripts/josh/josh-commands-hooks.ts +6 -0
  198. package/scripts/josh/josh-commands-lane.ts +10 -1
  199. package/scripts/josh/josh-commands-workflow.ts +5 -0
  200. package/scripts/josh/josh-logic.ts +6 -15
  201. package/scripts/josh/josh-usage-automation.ts +121 -0
  202. package/scripts/josh/josh-usage-backlog.ts +180 -0
  203. package/scripts/josh/josh-usage-developer.ts +230 -0
  204. package/scripts/josh/josh-usage-hooks.ts +38 -0
  205. package/scripts/josh/josh-usage-issue.ts +205 -0
  206. package/scripts/josh/josh-usage-review.ts +101 -0
  207. package/scripts/josh/josh-usage-run.ts +252 -0
  208. package/scripts/josh/josh-usage-session.ts +238 -0
  209. package/scripts/josh/josh-usage-workflow.ts +212 -0
  210. package/scripts/josh/josh.ts +14 -5
  211. package/scripts/josh/package-directory.ts +20 -0
  212. package/scripts/lane/lane-await.ts +3 -2
  213. package/scripts/lane/lane-capacity.ts +8 -2
  214. package/scripts/lane/lane-check-stats.ts +56 -0
  215. package/scripts/lane/lane-child-invocation.ts +49 -5
  216. package/scripts/lane/lane-dispatch.ts +3 -3
  217. package/scripts/lane/lane-guard-policy.ts +5 -5
  218. package/scripts/lane/lane-handoff.ts +8 -1
  219. package/scripts/lane/lane-install.ts +12 -4
  220. package/scripts/lane/lane-ledger.ts +52 -1
  221. package/scripts/lane/lane-limit-cli.ts +8 -9
  222. package/scripts/lane/lane-load.ts +9 -2
  223. package/scripts/lane/lane-measure-cli.ts +4 -0
  224. package/scripts/lane/lane-parked.ts +18 -0
  225. package/scripts/lane/lane-preserve.ts +29 -1
  226. package/scripts/lane/lane-process-load.ts +125 -0
  227. package/scripts/lane/lane-process-stats.ts +93 -0
  228. package/scripts/lane/lane-registry.ts +17 -10
  229. package/scripts/lane/lane-restart.ts +5 -26
  230. package/scripts/lane/lane-session-action.ts +146 -0
  231. package/scripts/lane/lane-session-broker.ts +68 -125
  232. package/scripts/lane/lane-spent.ts +14 -3
  233. package/scripts/lane/lane-stats.ts +1 -0
  234. package/scripts/lane/lane-withdraw-cli.ts +71 -0
  235. package/scripts/lane/lane-withdraw.ts +182 -0
  236. package/scripts/lint/format.ts +4 -4
  237. package/scripts/lint/lint-parallel.ts +10 -3
  238. package/scripts/lint/lint-related-scope.ts +4 -1
  239. package/scripts/managed-marker/managed-marker-logic.ts +1 -2
  240. package/scripts/notify/telegram-test.ts +10 -5
  241. package/scripts/overrides/lockfile-overrides.ts +1 -3
  242. package/scripts/propagate/propagate.ts +1 -2
  243. package/scripts/release/release-progress.ts +3 -2
  244. package/scripts/report/report-format-reference.ts +2 -0
  245. package/scripts/report/report-lint.ts +7 -4
  246. package/scripts/review/acceptance-map-check.ts +61 -0
  247. package/scripts/review/acceptance-map.ts +205 -0
  248. package/scripts/review/review-brief-cli.ts +1 -1
  249. package/scripts/review/review-record-cli.ts +6 -2
  250. package/scripts/rules/agent-model.ts +59 -0
  251. package/scripts/rules/delivered-rules.ts +11 -2
  252. package/scripts/rules/destructive-command.ts +6 -9
  253. package/scripts/rules/direct-filing.ts +3 -7
  254. package/scripts/rules/early-heartbeat.ts +7 -18
  255. package/scripts/rules/file-body.ts +6 -13
  256. package/scripts/rules/filing-cap.ts +4 -6
  257. package/scripts/rules/firing-point.ts +6 -5
  258. package/scripts/rules/git-force.ts +5 -9
  259. package/scripts/rules/guard-remedy.ts +117 -0
  260. package/scripts/rules/implementation-cut.ts +37 -35
  261. package/scripts/rules/index-guard.ts +4 -7
  262. package/scripts/rules/issue-comments.ts +67 -16
  263. package/scripts/rules/josh-git-bare.ts +3 -6
  264. package/scripts/rules/lane-background.ts +10 -21
  265. package/scripts/rules/lane-carry-conflict.ts +7 -17
  266. package/scripts/rules/lane-interactive-ask.ts +6 -11
  267. package/scripts/rules/lane-park.ts +5 -13
  268. package/scripts/rules/lane-split-park.ts +2 -7
  269. package/scripts/rules/lane-switch-main.ts +9 -12
  270. package/scripts/rules/last-prompt.ts +50 -5
  271. package/scripts/rules/oracle-consulted.ts +4 -5
  272. package/scripts/rules/oracle-list-cli.ts +7 -0
  273. package/scripts/rules/piped-verification.ts +84 -32
  274. package/scripts/rules/poll-loop.ts +7 -17
  275. package/scripts/rules/pre-gate-cut.ts +7 -21
  276. package/scripts/rules/prior-comment-read.ts +7 -5
  277. package/scripts/rules/protected-files.ts +5 -9
  278. package/scripts/rules/raw-field-body.ts +3 -7
  279. package/scripts/rules/rule-body-guard.ts +4 -11
  280. package/scripts/rules/rule-list.ts +4 -1
  281. package/scripts/rules/rule-registry-rows.ts +10 -0
  282. package/scripts/rules/rule-registry.ts +7 -4
  283. package/scripts/rules/rule-rewrite.ts +114 -0
  284. package/scripts/rules/rule-value.ts +7 -1
  285. package/scripts/rules/run-tail-rule.ts +6 -22
  286. package/scripts/rules/shell-body-trigger.ts +4 -6
  287. package/scripts/rules/shell-segments.ts +22 -0
  288. package/scripts/rules/stop-decision.ts +77 -0
  289. package/scripts/rules/stop-rules.ts +164 -77
  290. package/scripts/rules/tail-commands.ts +15 -1
  291. package/scripts/rules/test-declared-commit.ts +4 -10
  292. package/scripts/rules/third-party-write.ts +5 -10
  293. package/scripts/rules/worktree-guard.ts +5 -12
  294. package/scripts/run/board/run-board-cli.ts +2 -0
  295. package/scripts/run/board/run-board-closed.ts +8 -2
  296. package/scripts/run/board/run-board-github.ts +19 -3
  297. package/scripts/run/board/run-board-header.ts +4 -1
  298. package/scripts/run/board/run-board-idle.ts +31 -0
  299. package/scripts/run/board/run-board-labels.ts +2 -0
  300. package/scripts/run/board/run-board-layout.ts +1 -1
  301. package/scripts/run/board/run-board-read.ts +25 -12
  302. package/scripts/run/board/run-board-reason.ts +17 -4
  303. package/scripts/run/board/run-board-render-legend.ts +1 -0
  304. package/scripts/run/board/run-board-render-slot.ts +1 -1
  305. package/scripts/run/board/run-board-render-waits.ts +41 -0
  306. package/scripts/run/board/run-board-render.ts +6 -1
  307. package/scripts/run/board/run-board-row-yield.ts +1 -0
  308. package/scripts/run/board/run-board-state.ts +9 -0
  309. package/scripts/run/board/run-board-status.ts +89 -23
  310. package/scripts/run/board/run-board-thanks.ts +7 -2
  311. package/scripts/run/board/run-board-tick.ts +37 -8
  312. package/scripts/run/carry/run-carry-args.ts +1 -3
  313. package/scripts/run/carry/run-carry-conversation.ts +7 -2
  314. package/scripts/run/cut/run-cut-cli.ts +18 -22
  315. package/scripts/run/cut/run-cut-decline.ts +72 -0
  316. package/scripts/run/cut/run-cut-ready.ts +74 -0
  317. package/scripts/run/cut/run-cut-report.ts +29 -0
  318. package/scripts/run/cut/run-cut.ts +10 -1
  319. package/scripts/run/detached-launch.ts +23 -4
  320. package/scripts/run/entry/run-entry-cli.ts +8 -1
  321. package/scripts/run/event/run-event-render.ts +1 -0
  322. package/scripts/run/event/run-event-scope.ts +19 -1
  323. package/scripts/run/event/run-event-stream-emit.ts +13 -6
  324. package/scripts/run/event/run-event-stream.ts +4 -0
  325. package/scripts/run/hold/run-hold-cli.ts +14 -3
  326. package/scripts/run/hold/run-hold-session.ts +46 -0
  327. package/scripts/run/hold/run-hold.ts +13 -4
  328. package/scripts/run/launch-priority.ts +32 -0
  329. package/scripts/run/merge/run-merge-cli.ts +16 -42
  330. package/scripts/run/merge/run-merge-ship.ts +59 -1
  331. package/scripts/run/merge/run-merge-signals.ts +99 -0
  332. package/scripts/run/merge/run-merge-steps.ts +11 -1
  333. package/scripts/run/merge/run-merge.ts +22 -9
  334. package/scripts/run/run-ending.ts +31 -3
  335. package/scripts/run/run-liveness.ts +1 -1
  336. package/scripts/run/run-parent-cut-hook.ts +5 -10
  337. package/scripts/run/run-poll-hook.ts +157 -0
  338. package/scripts/run/run-preflight.ts +1 -2
  339. package/scripts/run/run-step-cli.ts +22 -15
  340. package/scripts/run/run-step-section.ts +85 -0
  341. package/scripts/run/run-stop-notify.ts +1 -2
  342. package/scripts/run/run-watcher-guard.ts +6 -3
  343. package/scripts/run/ship/run-ship-cli.ts +5 -12
  344. package/scripts/run/ship/run-ship-detach.ts +9 -0
  345. package/scripts/run/ship/run-ship-next.ts +10 -2
  346. package/scripts/run/ship/run-ship-overlap.ts +195 -0
  347. package/scripts/run/ship/run-ship-pre-detach.ts +33 -15
  348. package/scripts/run/ship/run-ship-preflight.ts +28 -2
  349. package/scripts/run/ship/run-ship-return.ts +31 -7
  350. package/scripts/run/ship/run-ship-review-steps.ts +25 -14
  351. package/scripts/run/ship/run-ship-review.ts +15 -2
  352. package/scripts/run/ship/run-ship-steps.ts +1 -1
  353. package/scripts/run/ship/run-ship-stop-context.ts +206 -0
  354. package/scripts/run/ship/run-ship-sync.ts +14 -6
  355. package/scripts/run/tidy/run-tidy-cli.ts +3 -3
  356. package/scripts/run/tidy/run-tidy-lanes.ts +3 -2
  357. package/scripts/safe-chain/project-config.ts +7 -2
  358. package/scripts/sync/sync-hook-safety.ts +8 -2
  359. package/scripts/test/test-e2e-guard.ts +30 -5
  360. package/scripts/test/test-unit-guard.ts +16 -1
  361. package/scripts/test/unit-worker-share.ts +26 -3
  362. package/scripts/time-runtime/time-batch-guard.ts +3 -3
  363. package/scripts/time-runtime/time-hook-transcript.ts +3 -2
  364. package/scripts/time-runtime/time-shell.ts +39 -4
  365. package/scripts/time-runtime/time-spans.ts +1 -0
  366. package/scripts/time-runtime/time-tool-call.ts +7 -0
  367. package/scripts/version/effective-upstream.ts +3 -4
  368. package/scripts/version/kit-version-config.ts +8 -2
  369. package/scripts/version/latest-corepack.ts +1 -2
  370. package/scripts/version/release-hold.ts +11 -4
  371. package/scripts/version/version-commands.ts +12 -4
  372. package/templates/gitignore +6 -2
  373. package/dist/hooks/chunk-D5QBTOCZ.js +0 -68
  374. package/dist/hooks/chunk-UT7YC3N2.js +0 -56
@@ -58,8 +58,12 @@ interface Parsed {
58
58
  // A finding spec is `<category>:<severity>:<file>`; the file field keeps any `:line` a citation
59
59
  // carries, so only the first two colons separate fields.
60
60
  function to_finding(category: string, severity: string, file: string): Finding | undefined {
61
- if (!review_finding_ledger.is_category(category)) return undefined
62
- if (!review_finding_ledger.is_severity(severity)) return undefined
61
+ if (
62
+ !review_finding_ledger.is_category(category) ||
63
+ !review_finding_ledger.is_severity(severity)
64
+ ) {
65
+ return undefined
66
+ }
63
67
 
64
68
  return review_finding_ledger.is_valid_file(file) ? { category, severity, file } : undefined
65
69
  }
@@ -0,0 +1,59 @@
1
+ import { json_value } from '#scripts/lib/json-value'
2
+ import type { GuardedCall } from '#scripts/time-runtime/time-batch-guard'
3
+
4
+ // A `general-purpose` subagent dispatched without a `model` is refused until it names one
5
+ // (joshuafolkken/kit#3927).
6
+ //
7
+ // **The omission is the cost, not the dispatch.** A subagent with no `model` inherits the parent's,
8
+ // so a routine unit runs on the main line's model — measured at 94 such dispatches on Opus. A named
9
+ // agent type carries its own `model` in its definition, so only the catch-all type is in scope; an
10
+ // absent `subagent_type` is that type, the harness's default.
11
+ //
12
+ // **It fires on every occurrence**: the remedy is one field on the same call, so a refusal costs one
13
+ // round trip and never wedges the run.
14
+
15
+ // Both spellings of the subagent tool are in use: `Task` (published) and `Agent` (this harness).
16
+ const SUBAGENT_TOOLS: ReadonlySet<string> = new Set(['Agent', 'Task'])
17
+ const GENERAL_PURPOSE = 'general-purpose'
18
+
19
+ // A dispatch of the catch-all type — the one whose model is otherwise inherited.
20
+ function is_general_purpose(call: GuardedCall): boolean {
21
+ if (!SUBAGENT_TOOLS.has(call.name) || !json_value.is_record(call.input)) return false
22
+
23
+ const type = call.input['subagent_type']
24
+
25
+ return type === undefined || type === GENERAL_PURPOSE
26
+ }
27
+
28
+ function names_model(call: GuardedCall): boolean {
29
+ const model = json_value.is_record(call.input) ? call.input['model'] : undefined
30
+
31
+ return typeof model === 'string' && model.length > 0
32
+ }
33
+
34
+ function is_unnamed_model(call: GuardedCall): boolean {
35
+ return is_general_purpose(call) && !names_model(call)
36
+ }
37
+
38
+ function keeps_model(call: GuardedCall): boolean {
39
+ return is_general_purpose(call) && names_model(call)
40
+ }
41
+
42
+ const AGENT_MODEL_REASON =
43
+ '⛔ agent model: a `general-purpose` subagent with no `model` inherits the parent model. ' +
44
+ 'Reissue the call with `model` set — `sonnet` for routine work, `opus` for judgment such as a review. ' +
45
+ 'It fires on every occurrence (`.claude/skills/workflow-commands/delegation.md`).'
46
+
47
+ // `reaches` is every catch-all dispatch, so `rule-value.ts` scores the named-model ones against it.
48
+ const ROW = {
49
+ id: 'agent-model',
50
+ is_trigger: is_unnamed_model,
51
+ reason: AGENT_MODEL_REASON,
52
+ decide: (): boolean => true,
53
+ keeps: keeps_model,
54
+ reaches: is_general_purpose,
55
+ }
56
+
57
+ const agent_model = { AGENT_MODEL_REASON, ROW, is_unnamed_model }
58
+
59
+ export { agent_model }
@@ -2,6 +2,7 @@ import { investigation_reads } from '#scripts/delegation/investigation-reads'
2
2
  import { hook_decision, type GuardRun, type TranscriptGuardSpec } from '#scripts/josh/hook-decision'
3
3
  import { lane_guard_policy } from '#scripts/lane/lane-guard-policy'
4
4
  import { time_batch_guard, type GuardedCall } from '#scripts/time-runtime/time-batch-guard'
5
+ import { agent_model } from './agent-model'
5
6
  import { bash_triggers } from './bash-triggers'
6
7
  import { direct_filing } from './direct-filing'
7
8
  import { early_heartbeat } from './early-heartbeat'
@@ -46,6 +47,9 @@ type CallTest = (
46
47
  run: ReadonlyArray<GuardedCall>,
47
48
  ) => boolean
48
49
 
50
+ // The tail is read lazily: most calls no rewrite applies to never need it.
51
+ type CallRewrite = (call: GuardedCall, tail: () => string) => Record<string, unknown> | undefined
52
+
49
53
  // `id` keys the rule's own refusal record, so one rule firing never spends another's budget.
50
54
  interface DeliveredRule {
51
55
  id: string
@@ -63,6 +67,9 @@ interface DeliveredRule {
63
67
  // The occasion the rule governs, for a row whose trigger is the violation itself (else `is_trigger`
64
68
  // would only count runs that broke it).
65
69
  reaches?: CallTest
70
+ // The call made right, for a row `guard-remedy.ts` classes as a rewrite: `rule-rewrite.ts` runs it
71
+ // in place of the refusal. `undefined` where this call has no mechanical fix, so it is refused.
72
+ rewrite?: CallRewrite
66
73
  // The note a hook attaches where it rewrites a violating call instead of refusing it, so
67
74
  // `rule-value.ts` counts the rewrite as this row's delivery.
68
75
  rewrite_note?: string
@@ -107,7 +114,8 @@ const DELIVERED_RULES: ReadonlyArray<DeliveredRule> = [
107
114
  reason: piped_verification.PIPED_VERIFICATION_REASON,
108
115
  keeps: on_bash_command(piped_verification.keeps_verdict_intact),
109
116
  reaches: on_bash_command(piped_verification.runs_verification),
110
- // A `| tail` / `| grep` masking is rewritten under `pipefail` by `pretool-guard.ts`, never refused.
117
+ // A masking whose filter reads to the end is rewritten under `pipefail`, never refused.
118
+ rewrite: piped_verification.pipefail_input,
111
119
  rewrite_note: piped_verification.PIPEFAIL_NOTE,
112
120
  },
113
121
  {
@@ -146,6 +154,7 @@ const DELIVERED_RULES: ReadonlyArray<DeliveredRule> = [
146
154
  lane_split_park.ROW,
147
155
  lane_park.ROW,
148
156
  lane_interactive_ask.ROW,
157
+ agent_model.ROW,
149
158
  lane_carry_conflict.ROW,
150
159
  lane_switch_main.ROW,
151
160
  // Disjoint from `run-tail` by the `-y` flag.
@@ -304,5 +313,5 @@ const delivered_rules = {
304
313
  is_issue_filing,
305
314
  }
306
315
 
307
- export type { DeliveredRule, MeasuredRule }
316
+ export type { CallRewrite, DeliveredRule, MeasuredRule }
308
317
  export { delivered_rules }
@@ -97,21 +97,18 @@ function is_destructive_command(command: string): boolean {
97
97
  }
98
98
 
99
99
  const DESTRUCTIVE_COMMAND_REASON =
100
- '⛔ destructive command: a recursive forced `rm` (`rm -rf`, `rm -fr`, `rm -r -f`, `rm --recursive ' +
101
- '--force`), `gh repo delete` / `archive`, `gh repo edit --visibility`, `gh pr close`, or a `gh api ' +
102
- '-X DELETE repos/…` call deletes or closes something that cannot be restored, which is Tier C and ' +
103
- 'needs explicit current-turn user instruction (`prompts/collaboration-workflow/operating-rules.md`). ' +
104
- 'The `.claude/settings.json` deny list matches prefix globs, so it misses these spellings; this row ' +
105
- 'reads the argv instead. Remove a file or directory one path at a time without `-f`, or ask the user ' +
106
- 'to run the command in their own terminal. `gh issue close` and removing a label from an Issue ' +
107
- '(`DELETE repos/<o>/<r>/issues/<N>/labels/<name>`) are not refused. **This rule fires on every ' +
108
- 'occurrence, not once per run.**'
100
+ '⛔ destructive command: a recursive forced `rm`, a `gh repo delete` / `archive` / visibility edit, ' +
101
+ '`gh pr close` or a `gh api -X DELETE` cannot be undone, which is Tier C. Remove one path at a ' +
102
+ 'time without `-f`, or ask the user to run it. It fires on every occurrence ' +
103
+ '(`prompts/collaboration-workflow/operating-rules.md`).'
109
104
 
110
105
  const ROW = {
111
106
  id: 'destructive-command',
112
107
  is_trigger: bash_triggers.on_bash_command(is_destructive_command),
113
108
  reason: DESTRUCTIVE_COMMAND_REASON,
114
109
  decide: (): boolean => true,
110
+ // The opening the reason carried before it was cut to the action and the next command.
111
+ former_reasons: ['⛔ destructive command: a recursive forced `rm` (`rm -rf`'],
115
112
  }
116
113
 
117
114
  const destructive_command = { DESTRUCTIVE_COMMAND_REASON, ROW, is_destructive_command }
@@ -9,13 +9,9 @@ import { bash_triggers } from './bash-triggers'
9
9
 
10
10
  const DIRECT_FILING_REASON =
11
11
  '⛔ direct filing: an Issue is filed with `pnpm josh issue:file "<title>" --body-file <path> ' +
12
- '--depth <0|1|2> [--route <route>] [--label <name>] [--repo <owner/repo>]`, never with `gh api …/issues` ' +
13
- 'or `gh issue create`. The command runs every filing step in order — the duplicate scout, the body ' +
14
- 'lint and its classification labels, the depth and `route:` labels, the `## Origin` check when filing ' +
15
- 'into another repository, and `epic:bundle` after the create — so none of them is a step to remember. ' +
16
- 'When the scout prints candidates it holds the filing: read each, then reissue with `--distinct <N,…>` ' +
17
- 'for the ones that are separate deliverables. Reference: `docs/josh-commands-backlog.md` → "`josh issue:file`". ' +
18
- '**This rule fires on every occurrence, not once per run.**'
12
+ '--depth <0|1|2>`, never with `gh api …/issues` or `gh issue create` — it runs the scout, the lint, ' +
13
+ 'the labels and `epic:bundle` itself. It fires on every occurrence (`docs/josh-commands-backlog.md` ' +
14
+ '→ "`josh issue:file`").'
19
15
 
20
16
  function is_any_filing(command: string): boolean {
21
17
  return bash_triggers.is_direct_filing(command) || bash_triggers.is_issue_filing(command)
@@ -178,25 +178,14 @@ function decide(call: GuardedCall, run: GuardRun, can_record: boolean): boolean
178
178
  }
179
179
 
180
180
  // The instruction in the shape a refusal can carry: what the call is about to do, what already does it
181
- // properly, and the one command an explicit ask is answered with. The observed failure is named
182
- // because it is what makes the rule believable.
181
+ // properly, and the one command an explicit ask is answered with. The observed failure behind it is in
182
+ // `docs/maintainers/rule-delivery-rationale.md`.
183
183
  const EARLY_HEARTBEAT_REASON =
184
- '⛔ early heartbeat: this call arms a wait timer of its own, and the progress clock is not yours ' +
185
- 'to keep. `pnpm josh run:progress --wait` keeps it for you ' +
186
- '(`JOSH_PROGRESS_INTERVAL_MINUTES`, or `josh.progress_interval_minutes` in `package.json`, ' +
187
- 'default 20 minutes): start it once in the background and it reports the five labelled lines to ' +
188
- "the run's event stream on its own, without waking you — relay nothing, and start it again only " +
189
- 'after it exits, which it does when newly runnable work arrives. Two timers armed at ' +
190
- 'once is what produced reports 3–5 minutes apart on a 15-minute setting ' +
191
- '(joshuafolkken/kit#1570): a new one was armed on the turn a timer fired and again on the turn a ' +
192
- "child's completion woke the run, and `--mark` recorded each report without anything refusing the " +
193
- 'early ones. **An explicit ask is not a heartbeat**: if the person asked for progress now, run ' +
194
- '`pnpm josh run:progress --once`, which prints the five labelled lines whatever the clock says — ' +
195
- 'under `backlogrun`, `pnpm josh run:board --chat` instead. If you are ' +
196
- 'waiting on something rather than on a clock, wait on the thing itself — `pnpm josh followup` ' +
197
- 'waits for CI. The procedure is `.claude/skills/workflow-commands/backlogrun.md` → "Progress while ' +
198
- 'the run is quiet". **This rule fires on every early arm, not once per run**, so reissuing the ' +
199
- 'same sleep will be refused again.'
184
+ '⛔ early heartbeat: this call arms a wait timer of its own. Start `pnpm josh run:progress --wait` ' +
185
+ "once in the background: it reports to the run's event stream on its own — relay nothing. When " +
186
+ 'asked, `pnpm josh run:progress --once` — under `backlogrun`, `pnpm josh run:board --chat`; ' +
187
+ 'for CI, `pnpm josh followup`. It fires on every occurrence ' +
188
+ '(`.claude/skills/workflow-commands/progress-watcher.md`).'
200
189
 
201
190
  // **Keeping this rule is letting the watcher hold the clock** — `pnpm josh run:progress --wait` waits
202
191
  // the interval out, and `--once` answers an explicit ask. `--mark` is neither: it records a report
@@ -171,20 +171,13 @@ function carries_a_file_body(
171
171
  )
172
172
  }
173
173
 
174
- // The instruction in the shape a refusal can carry: what the command is doing, the safe tool, and the
175
- // file forms that stay allowed. The measured cost is named because it is the half that reads as
176
- // surprising — the command body is billed on every later request of the run.
174
+ // The instruction in the shape a refusal can carry: what the command is doing and the safe tool. The
175
+ // measured cost behind it is in `file-edits.md`.
177
176
  const FILE_BODY_REASON =
178
- "⛔ file body in a shell command: this carries a file's new text inline — a heredoc rewriting an " +
179
- 'existing file, an interpreter (`python3 - <<`, `node -e`) whose body writes, a script file (`cat > ' +
180
- 'edit.py <<EOF`) whose body writes, or an in-place `perl -0pi -e`. `CLAUDE.md` forbids it because ' +
181
- 'the command body is re-read on every later request of the run, so an editing script written early ' +
182
- 'is billed for the rest of it (`file-edits.md` measured one run at ~2m45s spent retyping existing ' +
183
- 'lines). Edit a region with the Edit tool (old / new); a change scattered so widely that the ' +
184
- 'partial edits would carry more than the file is rewritten whole with the Write tool, never a ' +
185
- 'heredoc. Pass a body by path (`--field body=@<path>`, `--body-file <path>`). A new-file creation (`cat > ' +
186
- '<new-file> <<EOF`), a read-only heredoc (`cat <<EOF`), and a short `sed -i` stay allowed. **This ' +
187
- 'rule fires on every occurrence, not once per run.**'
177
+ "⛔ file body in a shell command: this carries a file's new text inline, which is re-read on every " +
178
+ 'later request. Edit a region with the Edit tool, rewrite a whole file with the Write tool, and ' +
179
+ 'pass a body by path (`--body-file <path>`); a new-file `cat > <new-file> <<EOF` stays allowed. ' +
180
+ 'This rule fires on every occurrence (`prompts/collaboration-workflow/file-edits.md`).'
188
181
 
189
182
  const ROW = {
190
183
  id: 'file-body',
@@ -104,12 +104,9 @@ function decide(_call: unknown, run: GuardRun): boolean {
104
104
  }
105
105
 
106
106
  const FILING_CAP_REASON =
107
- `⛔ filing cap: this run has already filed ${String(FILING_CAP)} Issues, the per-run ceiling — on ` +
108
- 'reaching it, stop and report rather than filing more. Automatic filing is capped so a run cannot ' +
109
- 'manufacture Issues faster than they can be closed (`.claude/skills/workflow-commands/SKILL.md` → ' +
110
- "`prerequisite.md`, `observation-filing.md`). A further Issue that genuinely must exist is a person's call, not " +
111
- "this run's. **This rule fires on every filing past the cap, not once per run**, so reissuing the " +
112
- 'same filing will be refused again.'
107
+ `⛔ filing cap: this run has already filed ${String(FILING_CAP)} Issues, the per-run ceiling. Stop ` +
108
+ "filing and report; a further Issue is a person's call. It fires on every occurrence " +
109
+ '(`.claude/skills/workflow-commands/observation-filing.md`).'
113
110
 
114
111
  // The enumeration row itself, so `delivered-rules.ts` spreads one entry rather than restating the
115
112
  // trigger and reason it already single-sources here. It declares no `keeps`: staying under a cap is
@@ -126,6 +123,7 @@ const filing_cap = {
126
123
  FILING_CAP,
127
124
  FILING_CAP_REASON,
128
125
  ROW,
126
+ current_turn,
129
127
  is_prompt_line,
130
128
  prior_filing_count,
131
129
  turn_filing_count,
@@ -9,15 +9,18 @@
9
9
  // time is the whole point of the check.
10
10
 
11
11
  // **The hook-deliverable set — the single source is the list `pnpm josh rule:list` renders.** The
12
- // `PreToolUse` guard is wired to `Bash|Edit|Read|Write` (`scripts/hooks/pretool-guard.ts`), and the
13
- // lane-child interactive-ask row triggers on `AskUserQuestion`. A firing point naming one of these can
14
- // be delivered at the moment it binds; anything else cannot.
12
+ // `PreToolUse` guard is wired to `Bash|Edit|Read|Write` (`scripts/hooks/pretool-guard.ts`), the
13
+ // lane-child interactive-ask row triggers on `AskUserQuestion`, and the `agent-model` row on
14
+ // `Agent|Task`. A firing point naming one of these can be delivered at the moment it binds; anything
15
+ // else cannot.
15
16
  const HOOK_DELIVERABLE_TOOLS: ReadonlyArray<string> = [
16
17
  'Bash',
17
18
  'Edit',
18
19
  'Read',
19
20
  'Write',
20
21
  'AskUserQuestion',
22
+ 'Agent',
23
+ 'Task',
21
24
  ]
22
25
 
23
26
  // **Known Claude Code tool calls that a hook cannot deliver a rule on.** They are real tools, so a
@@ -31,8 +34,6 @@ const KNOWN_UNDELIVERABLE_TOOLS: ReadonlyArray<string> = [
31
34
  'WebFetch',
32
35
  'WebSearch',
33
36
  'TodoWrite',
34
- 'Task',
35
- 'Agent',
36
37
  'NotebookEdit',
37
38
  ]
38
39
 
@@ -69,19 +69,15 @@ function is_force_or_delete(command: string): boolean {
69
69
  return shell_segments.segments_of(command).some((segment) => is_destructive_segment(segment))
70
70
  }
71
71
 
72
- // The instruction in the shape a refusal can carry: what the command does, why the deny list did not
73
- // stop it, and the one route left. There is no "safe reissue" the way a backgrounded push is for
72
+ // The instruction in the shape a refusal can carry: what the command does and the one route left. The
73
+ // spellings the deny glob misses are pinned on `is_force_or_delete`, not repeated in the text. There is no "safe reissue" the way a backgrounded push is for
74
74
  // `run-tail` — a force push and a branch delete are Tier C, so the honest next move is a person's, in
75
75
  // their own terminal.
76
76
  const GIT_FORCE_REASON =
77
77
  '⛔ force push or branch delete: this rewrites or removes shared history, which is Tier C and needs ' +
78
- 'explicit current-turn user instruction — never an AI decision, even to fix a problem you caused ' +
79
- '(`prompts/collaboration-workflow/operating-rules.md`). The `.claude/settings.json` deny list ' +
80
- 'matches globs, so it misses this spelling: a combined short cluster (`git push -uf`), a `git -C ' +
81
- '<path>` prefix, and the colon-form delete (`git push origin :branch`) all pass it. This row reads ' +
82
- 'the argv instead, so the spelling does not matter. For an ordinary push use `pnpm josh git`; a ' +
83
- "force push, a `git push --delete` / `:branch`, or a `git branch -d` / `-D` is the user's to run in " +
84
- 'their own terminal. **This rule fires on every occurrence, not once per run.**'
78
+ 'explicit current-turn user instruction. Push with `pnpm josh git`; leave a force push or branch ' +
79
+ "delete to the user's own terminal. This rule fires on every occurrence " +
80
+ '(`prompts/collaboration-workflow/operating-rules.md`).'
85
81
 
86
82
  // The row itself, so `delivered-rules.ts` spreads one entry. `decide` returns true so it refuses every
87
83
  // occurrence; it declares no `keeps` — not force-pushing is the absence of a call, not a call, so the
@@ -0,0 +1,117 @@
1
+ // Every `PreToolUse` and `Stop` guard, classed by what its refusal asks for (joshuafolkken/kit#3920),
2
+ // with the reason it cannot run itself instead.
3
+ //
4
+ // **A refusal whose right form follows from the call alone is a wasted round trip.** The model reissues
5
+ // the call the refusal already described, at the run's whole context. Where that form is mechanical
6
+ // and stays the same tool, the hook runs it instead (`rule-rewrite.ts`) and says what it changed. Every
7
+ // other refusal asks for something the call does not carry: another tool, another command and its
8
+ // arguments, or a decision.
9
+ //
10
+ // `rewrite` means the row rewrites where it can and refuses where it cannot; `judgment` means it always
11
+ // refuses. **`why` is required**: a guard is the third rung of `principles.md` → "automate-first", so
12
+ // each row says why a command or the hook running the step cannot replace it. `guard-remedy.test.ts`
13
+ // pins that every guard is classed with a non-empty `why` and that each `rewrite` row has one.
14
+
15
+ type Remedy = 'rewrite' | 'judgment'
16
+
17
+ interface GuardRemedy {
18
+ remedy: Remedy
19
+ // Why the hook cannot make the step itself — printed by `rule:list` as `not automated`.
20
+ why: string
21
+ }
22
+
23
+ const REWRITTEN =
24
+ 'the hook makes the same call right itself; it refuses only a spelling it cannot rewrite'
25
+ const ANOTHER_TOOL = 'the right form is another tool, which `updatedInput` cannot switch to'
26
+ const ANOTHER_COMMAND = 'the right act is another command, whose arguments the call does not carry'
27
+ const NO_RUN = "the call must not run at all, or only on a person's word — refusing is the remedy"
28
+ // The `Stop` rows that already run their step: each blocks only on the case the hook cannot finish.
29
+ const STOP_NOTIFY = 'the hook sends the notify itself; it blocks only when the send fails'
30
+ const HOLD_RELEASE = 'the hook releases the hold itself; it blocks only when that release fails'
31
+ const ISSUE_CITATION =
32
+ 'the hook prints the citation lines itself; it blocks only on a number it cannot cite'
33
+ const FILING_OFFER = 'the filing needs a title and a body only the model can write'
34
+
35
+ function rewrite(): GuardRemedy {
36
+ return { remedy: 'rewrite', why: REWRITTEN }
37
+ }
38
+
39
+ function judgment(why: string): GuardRemedy {
40
+ return { remedy: 'judgment', why }
41
+ }
42
+
43
+ const GUARD_REMEDIES: Readonly<Record<string, GuardRemedy>> = {
44
+ // The rewrites: each fix is the same call made right.
45
+ 'piped-verification': rewrite(), // wrapped in `(set -o pipefail; …)`
46
+ 'issue-comments': rewrite(), // the same Issue's comments read on the same line
47
+ 'lane-background': rewrite(), // a lane child's `ship` run in the foreground (`pretool-guard.ts`)
48
+ 'file-body': judgment(ANOTHER_TOOL), // Edit or Write
49
+ 'shell-body': judgment(
50
+ "which part of the body the shell was meant to expand is the author's, not the hook's",
51
+ ),
52
+ 'raw-field-body': judgment('the body moves to a file the call does not name'),
53
+ 'rule-body': judgment('a trigger-plus-pointer line is written, not derived'),
54
+ 'direct-filing': judgment(ANOTHER_COMMAND),
55
+ 'direct-pr-create': judgment(ANOTHER_COMMAND),
56
+ 'josh-git-bare': judgment(ANOTHER_COMMAND),
57
+ 'run-tail': judgment(ANOTHER_COMMAND),
58
+ 'pre-gate-cut': judgment(ANOTHER_COMMAND),
59
+ 'implementation-cut': judgment(ANOTHER_COMMAND),
60
+ 'early-heartbeat': judgment(ANOTHER_COMMAND),
61
+ 'poll-loop': judgment(ANOTHER_COMMAND),
62
+ 'lane-park': judgment(ANOTHER_COMMAND),
63
+ 'lane-split-park': judgment(ANOTHER_COMMAND),
64
+ 'lane-interactive-ask': judgment(ANOTHER_COMMAND),
65
+ 'agent-model': judgment("which model fits the unit is the author's call"),
66
+ 'lane-carry-conflict': judgment(ANOTHER_COMMAND),
67
+ 'lane-switch-main': judgment(ANOTHER_COMMAND),
68
+ 'oracle-consulted': judgment(ANOTHER_COMMAND),
69
+ 'test-declared': judgment(ANOTHER_COMMAND),
70
+ batching: judgment(ANOTHER_COMMAND),
71
+ investigation: judgment(ANOTHER_COMMAND),
72
+ 'duplicate-read': judgment(ANOTHER_COMMAND),
73
+ 'run-watcher': judgment(ANOTHER_COMMAND),
74
+ 'parent-cut': judgment(ANOTHER_COMMAND),
75
+ 'issue-watch': judgment(ANOTHER_COMMAND),
76
+ 'third-party-write': judgment(NO_RUN),
77
+ 'filing-cap': judgment(NO_RUN),
78
+ 'git-force': judgment(NO_RUN),
79
+ 'worktree-mutation': judgment(NO_RUN),
80
+ 'index-mutation': judgment(NO_RUN),
81
+ 'destructive-command': judgment(NO_RUN),
82
+ 'protected-file': judgment(NO_RUN),
83
+ // The `Stop` rows: the hook judges a finished reply, so it has no call to rewrite.
84
+ 'stop-notify': judgment(STOP_NOTIFY),
85
+ 'issue-citation': judgment(ISSUE_CITATION),
86
+ 'filing-offer': judgment(FILING_OFFER),
87
+ 'hold-release': judgment(HOLD_RELEASE),
88
+ }
89
+
90
+ // A row family shares one class: `oracle-consulted:pkg:scout` is an `oracle-consulted` row.
91
+ const FAMILY_SEPARATOR = ':'
92
+
93
+ function entry_of(id: string): GuardRemedy | undefined {
94
+ return GUARD_REMEDIES[id] ?? GUARD_REMEDIES[id.split(FAMILY_SEPARATOR)[0] ?? '']
95
+ }
96
+
97
+ function remedy_of(id: string): Remedy | undefined {
98
+ return entry_of(id)?.remedy
99
+ }
100
+
101
+ function why_of(id: string): string | undefined {
102
+ return entry_of(id)?.why
103
+ }
104
+
105
+ function is_rewrite(id: string): boolean {
106
+ return remedy_of(id) === 'rewrite'
107
+ }
108
+
109
+ const guard_remedy = {
110
+ GUARD_REMEDIES,
111
+ is_rewrite,
112
+ remedy_of,
113
+ why_of,
114
+ }
115
+
116
+ export type { GuardRemedy, Remedy }
117
+ export { guard_remedy }
@@ -7,7 +7,9 @@ import type { GuardRun } from '#scripts/josh/hook-decision'
7
7
  import { lane_child_marker, type MarkerSource } from '#scripts/lane/lane-child-marker'
8
8
  import { lane_paths } from '#scripts/lane/lane-paths'
9
9
  import { run_cut, type RunCut } from '#scripts/run/cut/run-cut'
10
+ import { run_cut_decline } from '#scripts/run/cut/run-cut-decline'
10
11
  import { run_cut_handoff } from '#scripts/run/cut/run-cut-handoff'
12
+ import { run_cut_ready } from '#scripts/run/cut/run-cut-ready'
11
13
  import type { GuardedCall } from '#scripts/time-runtime/time-batch-guard'
12
14
  import { bash_triggers } from './bash-triggers'
13
15
  import { implementation_cut_verdict } from './implementation-cut-verdict'
@@ -62,6 +64,16 @@ interface LaneCostState {
62
64
  // held outside a lane is told apart from a person's tree.
63
65
  held_issue: () => string | undefined
64
66
  verdict: () => CostVerdict
67
+ // Whether `run:cut --impl` could take the cut for this issue now — see `can_cut_sync`.
68
+ can_cut: (issue: string) => boolean
69
+ }
70
+
71
+ // **The guard asks for a cut only where `run:cut --impl` could take one** (joshuafolkken/kit#3881).
72
+ // A run held on the default branch, or one whose cut was already answered `not-a-lane` / `unready`
73
+ // under this hold, can only be refused again, so each refusal was a round trip that cut nothing. The
74
+ // decline record is a file read and comes first; the tree read spawns git and comes second.
75
+ function can_cut_sync(issue: string): boolean {
76
+ return !run_cut_decline.is_declined_sync(issue) && run_cut_ready.is_ready_sync()
65
77
  }
66
78
 
67
79
  function current_state(): LaneCostState {
@@ -75,6 +87,7 @@ function current_state(): LaneCostState {
75
87
  // the window collapses one turn's burst of edits to a single whole-transcript price.
76
88
  verdict: (): CostVerdict =>
77
89
  implementation_cut_verdict.reused_verdict(() => cost_cli.session_verdict()),
90
+ can_cut: can_cut_sync,
78
91
  }
79
92
  }
80
93
 
@@ -140,14 +153,19 @@ function warrants_the_cut(state: LaneCostState): boolean {
140
153
  // **The unmeasurable direction matches the pre-gate cut**. A session that
141
154
  // cannot be priced reads as warranting the cut, the same safety-net direction `pre-gate-cut.ts`'s
142
155
  // `warrants_the_cut` takes (`verdict !== UNDER_VERDICT`): the two rules read one statistic against one
143
- // threshold, so they must not disagree about whether an unmeasurable session is due a cut.
156
+ // threshold, so they must not disagree about whether an unmeasurable session is due a cut. Whether the
157
+ // cut could be taken is asked only once it is warranted, so an under-threshold edit spawns no git.
144
158
  function is_over_threshold_edit(
145
159
  call: GuardedCall,
146
160
  state: LaneCostState = current_state(),
147
161
  ): boolean {
148
- if (!is_edit_tool(call.name) || uncut_run_issue(state) === undefined) return false
162
+ if (!is_edit_tool(call.name)) return false
163
+
164
+ const issue = uncut_run_issue(state)
165
+
166
+ if (issue === undefined) return false
149
167
 
150
- return warrants_the_cut(state)
168
+ return warrants_the_cut(state) && state.can_cut(issue)
151
169
  }
152
170
 
153
171
  // **The threshold in the refusal text is assembled from the constant, never spelled**.
@@ -155,36 +173,14 @@ function is_over_threshold_edit(
155
173
  // building it from `CONTEXT_CUT_THRESHOLD` keeps the two from drifting. `cost_format.format_tokens` renders it the way every cost report does.
156
174
  const THRESHOLD_TEXT = `${cost_format.format_tokens(CONTEXT_CUT_THRESHOLD)}-token`
157
175
 
158
- // The instruction in the shape a refusal can carry: what the cut is for, what each verdict means, and
159
- // the reissue sentence every delivery needs. The verdicts are spelled out rather than pointed at,
160
- // because `cut` is the only one that ends the turn and a run told merely to "take the cut" would have to
161
- // read which of the others leave it implementing — the same reason `pre-gate-cut.ts` spells them out.
176
+ // The instruction in the shape a refusal can carry: the handoff in the format the parser reads, the
177
+ // cut, and the one verdict that ends the turn — `cut` is the only one, so a run told merely to "take
178
+ // the cut" is not left to look it up.
162
179
  const IMPLEMENTATION_CUT_REASON =
163
- '⛔ implementation-phase cut: this checkout is a lane dispatched for this issue, or a run holds it for ' +
164
- 'this issue (joshuafolkken/kit#2760), and its recent-context ' +
165
- `cost has crossed the shared ${THRESHOLD_TEXT} threshold mid-implementation, so the thinking accumulated ` +
166
- 'so far is now re-read on every later request. Take the cut before this edit. ' +
167
- `First write a handoff file at \`${run_cut_handoff.HANDOFF_PATH}\` (ignored, so it is never committed) with the Write tool — the user’s instruction verbatim, what you have ` +
168
- 'completed, what remains, and what you deliberately did not touch, as ' +
169
- `${run_cut_handoff.HANDOFF_FORMAT} (Markdown is refused) — and pass it as \`--handoff <path>\`, ` +
170
- 'so the fresh process resumes on the original instruction rather than the working tree alone ' +
171
- '(joshuafolkken/kit#2354); a resume that finds no instruction is refused `incomplete` rather than ' +
172
- 'continuing blind. ' +
173
- '`pnpm josh run:cut --impl <N> --handoff <path>` ends this process and relaunches a fresh one that resumes back into ' +
174
- 'implementation (`resume-impl`), dropping the accumulated context rather than carrying it — ' +
175
- 'joshuafolkken/kit#1933 built the boundary and joshuafolkken/kit#2310 measured it firing 0 times ' +
176
- 'because the verdict was only ever read at session entry, where the context has not yet grown. The ' +
177
- "verdict is `pnpm josh cost --cut`'s exactly — the same per-request billed-input statistic against the " +
178
- 'same threshold, never a second measurement. Issue `pnpm josh run:cut --impl <N> --handoff <path>` now and read the ' +
179
- 'verdict: on `cut`, read what the command printed. Where it relaunched a fresh process, **end the turn ' +
180
- 'immediately** — that process owns the run and continues implementing, so it must not be waited for, ' +
181
- 'and **no Telegram is owed**: a cut that resumes itself leaves a person nothing to act on. Where it printed that nothing was relaunched, do what it prints, then end the turn; on `not-a-lane`, `unready`, `busy`, `failed` or ' +
182
- '`unknown`, this process carries the run on and the edit is simply the next call. Never relaunch a ' +
183
- 'second process after `busy`. This fired because the dispatch mark names this lane or the run hold ' +
184
- 'names this issue; a person working here carries no mark and holds no run, so sees no refusal, so there is no human-or-child judgement left to make. The ' +
185
- 'procedure is `.claude/skills/workflow-commands/pre-gate-cut.md`. Reissue this edit once the cut has ' +
186
- 'answered — an edit reissued right after this refusal passes, so `busy` / `failed` cannot wedge the ' +
187
- 'run edit after edit; this fires again on the next threshold crossing rather than once per run.'
180
+ `⛔ implementation-phase cut: refused on every edit past the ${THRESHOLD_TEXT} line. ` +
181
+ `Write \`${run_cut_handoff.HANDOFF_PATH}\` as ${run_cut_handoff.HANDOFF_FORMAT}, run ` +
182
+ '`pnpm josh run:cut --impl <N> --handoff <path>`; `cut`: end the turn immediately, no Telegram ' +
183
+ 'is owed; else reissue the edit (`.claude/skills/workflow-commands/pre-gate-cut.md`).'
188
184
 
189
185
  // **A refusal younger than this window is this edit's own refusal being answered**.
190
186
  // A `decide` row that refused every over-threshold edit would wedge a run whose cut came back `busy` /
@@ -201,7 +197,8 @@ function is_reissued_refusal(now_ms: number, delivered_at_ms: number): boolean {
201
197
  // silenced the row for the rest of a process the moment its first refusal landed — and a `busy` /
202
198
  // `failed` / `unready` verdict, or an edit reissued unchanged, left the context to grow unwatched to
203
199
  // 282,747 tokens with the cut never taken. `decide` re-asks on every over-threshold edit; the reissue
204
- // window is what keeps that from wedging a run that genuinely cannot cut. `delivered_at_ms` is this
200
+ // window keeps that from wedging a run whose cut came back `busy` / `failed`, and a run that cannot
201
+ // cut at all never reaches here — `can_cut_sync` keeps the trigger silent for it. `delivered_at_ms` is this
205
202
  // row's own last refusal, written by `fire_once` only when a refusal actually fired, so a passed
206
203
  // reissue does not move it.
207
204
  function decide(
@@ -213,8 +210,8 @@ function decide(
213
210
  return !is_reissued_refusal(run.now_ms, delivered_at_ms)
214
211
  }
215
212
 
216
- // The row itself, so `delivered-rules.ts` spreads one entry. `decide` fires it per threshold crossing;
217
- // `keeps` is the implementation cut command, read from a `Bash` call the same
213
+ // The row itself, so `delivered-rules.ts` spreads one entry. `decide` fires it per threshold crossing
214
+ // in a run that can cut; `keeps` is the implementation cut command, read from a `Bash` call the same
218
215
  // way `pre-gate-cut`'s is.
219
216
  const ROW = {
220
217
  id: 'implementation-cut',
@@ -222,6 +219,11 @@ const ROW = {
222
219
  reason: IMPLEMENTATION_CUT_REASON,
223
220
  decide,
224
221
  keeps: bash_triggers.on_bash_command(takes_the_impl_cut),
222
+ // The opening the reason carried before it was cut to the action and the next command.
223
+ former_reasons: [
224
+ '⛔ implementation-phase cut: this checkout is a lane dispatched',
225
+ "⛔ implementation-phase cut: this run's context crossed the",
226
+ ],
225
227
  }
226
228
 
227
229
  const implementation_cut = {
@@ -51,13 +51,10 @@ function is_index_mutation(command: string): boolean {
51
51
 
52
52
  const INDEX_MUTATION_REASON =
53
53
  '⛔ git index mutation: `git add` / `stage` / `commit` / `reset` / `rm` / `mv` and `git restore ' +
54
- "--staged` overwrite the user's staged snapshot, which `CLAUDE.md` → Git Rules forbids on your own " +
55
- 'judgement (`prompts/collaboration-workflow/operating-rules.md` → "no-self-staging"). The ' +
56
- '`.claude/settings.json` deny list matches prefix globs, so it misses `git -C . commit`, `git -c ' +
57
- 'k=v commit` and `env git commit`; this row reads the argv instead, so the spelling does not matter. ' +
58
- 'A commit goes through `pnpm josh git -y`; any other staging needs explicit current-turn user ' +
59
- 'instruction, run by the user in their own terminal. **This rule fires on every occurrence, not once ' +
60
- 'per run.**'
54
+ "--staged` overwrite the user's staged snapshot. Commit with `pnpm josh git -y`; any other staging " +
55
+ "is the user's, on explicit instruction. It fires on every occurrence " +
56
+ '(`prompts/collaboration-workflow/operating-rules.md` → ' +
57
+ '"no-self-staging").'
61
58
 
62
59
  const ROW = {
63
60
  id: 'index-mutation',