@joshuafolkken/kit 1.1178.0 → 1.1229.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/settings.json +2 -2
  3. package/.claude/skills/dependency-update/SKILL.md +4 -8
  4. package/.claude/skills/epic-commands/SKILL.md +2 -2
  5. package/.claude/skills/epic-commands/epic-bundle.md +1 -1
  6. package/.claude/skills/workflow-commands/SKILL.md +2 -4
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +6 -2
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +23 -2
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +7 -2
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +21 -13
  13. package/.claude/skills/workflow-commands/chain-rule.md +1 -1
  14. package/.claude/skills/workflow-commands/delegation.md +2 -2
  15. package/.claude/skills/workflow-commands/followup.md +5 -0
  16. package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
  17. package/.claude/skills/workflow-commands/halfrun.md +1 -1
  18. package/.claude/skills/workflow-commands/issue-scout.md +1 -1
  19. package/.claude/skills/workflow-commands/kickoff.md +1 -1
  20. package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
  21. package/.claude/skills/workflow-commands/pre-gate-cut.md +6 -1
  22. package/.claude/skills/workflow-commands/prerequisite.md +2 -2
  23. package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
  24. package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
  25. package/.codex/hooks.json +2 -2
  26. package/.cursorrules +6 -41
  27. package/.github/actions/setup-node/action.yml +1 -1
  28. package/.github/workflows/auto-tag.yml +3 -4
  29. package/.github/workflows/ci.yml +32 -31
  30. package/.github/workflows/dependabot-auto-merge.yml +13 -6
  31. package/.github/workflows/publish.yml +48 -4
  32. package/.github/workflows/sonar-qube.yml +3 -0
  33. package/.pnpmfile.mjs +4 -3
  34. package/.prettierignore +8 -2
  35. package/CLAUDE.md +18 -20
  36. package/README.md +26 -19
  37. package/SECURITY.md +15 -1
  38. package/dist/CLAUDE.md +18 -20
  39. package/dist/commands/document-section-cli.js +734 -0
  40. package/dist/hooks/audit-provision.js +346 -0
  41. package/dist/hooks/batch-guard.js +5 -4
  42. package/dist/hooks/{chunk-S6X6WYKR.js → chunk-55HHG5PX.js} +1210 -711
  43. package/dist/hooks/{chunk-ZAL3NRGL.js → chunk-5VMKCCC4.js} +25 -10
  44. package/dist/hooks/chunk-5VNDSNG3.js +12 -0
  45. package/dist/hooks/{chunk-32GQTL4F.js → chunk-6HJJXRBX.js} +4 -2
  46. package/dist/hooks/chunk-CDY4NFQG.js +15 -0
  47. package/dist/hooks/chunk-ELCN5LCH.js +135 -0
  48. package/dist/hooks/{chunk-VESKRQOF.js → chunk-F56MFS4B.js} +3 -3
  49. package/dist/hooks/{chunk-UAGLLIGZ.js → chunk-FIVDHQGT.js} +7 -6
  50. package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
  51. package/dist/hooks/{chunk-G6KRCKEC.js → chunk-JUDKDCDP.js} +496 -378
  52. package/dist/hooks/{chunk-GDS5N2KB.js → chunk-NV2JRZZO.js} +718 -681
  53. package/dist/hooks/{chunk-VQLLNFNT.js → chunk-RNWHN5BP.js} +3 -3
  54. package/dist/hooks/{chunk-DO6NWWHU.js → chunk-TQNPDZNH.js} +4 -4
  55. package/dist/hooks/{chunk-I4XSL4TT.js → chunk-VC3PZPZO.js} +1 -1
  56. package/dist/hooks/codex-hook-adapter.js +14 -12
  57. package/dist/hooks/duplicate-read-guard.js +6 -5
  58. package/dist/hooks/format-edited.js +4 -3
  59. package/dist/hooks/inputs.json +217 -195
  60. package/dist/hooks/investigation-guard.js +6 -5
  61. package/dist/hooks/pretool-guard.js +12 -10
  62. package/dist/hooks/session-lang.js +2 -4
  63. package/dist/hooks/stop-guard.js +66 -58
  64. package/dist/josh.js +868 -74
  65. package/dist/version/index.d.ts +7 -2
  66. package/dist/version/index.js +152 -75
  67. package/eslint/base.js +23 -1
  68. package/eslint/rules/code-quality.js +3 -0
  69. package/eslint/rules/early-return-one-liner.js +98 -0
  70. package/eslint/rules/unicorn.js +16 -1
  71. package/package.json +7 -6
  72. package/pnpm-workspace.yaml +9 -4
  73. package/prettier/basic.d.ts +3 -1
  74. package/prettier/index.d.ts +3 -1
  75. package/prettier/static.d.ts +3 -1
  76. package/prompts/coding-standards.md +1 -1
  77. package/prompts/collaboration-workflow/file-edits.md +4 -4
  78. package/prompts/collaboration-workflow/glossary.md +1 -1
  79. package/prompts/collaboration-workflow/operating-rules.md +17 -9
  80. package/prompts/collaboration-workflow/overview.md +2 -2
  81. package/prompts/collaboration-workflow/principles.md +14 -12
  82. package/prompts/collaboration-workflow/report-format.md +1 -1
  83. package/prompts/collaboration-workflow/residency.md +1 -1
  84. package/prompts/collaboration-workflow/rule-delivery.md +6 -6
  85. package/prompts/collaboration-workflow/shell-body.md +6 -6
  86. package/prompts/collaboration-workflow/turn-batching.md +1 -1
  87. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
  88. package/prompts/collaboration-workflow/wip-cap.md +10 -10
  89. package/prompts/review-rubric.md +1 -2
  90. package/prompts/review.md +1 -1
  91. package/scripts/agent/agent-headless.ts +29 -0
  92. package/scripts/agent/agent-launch-environment.ts +6 -2
  93. package/scripts/agent/codex-agent-argv.ts +2 -1
  94. package/scripts/auto-ok/auto-ok-cli.ts +1 -1
  95. package/scripts/backlog/backlog-arrival.ts +3 -3
  96. package/scripts/backlog/backlog-budget.ts +1 -1
  97. package/scripts/backlog/backlog-drive-args.ts +135 -0
  98. package/scripts/backlog/backlog-drive-cli.ts +11 -137
  99. package/scripts/backlog/backlog-drive-launch.ts +39 -0
  100. package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
  101. package/scripts/backlog/backlog-drive-named.ts +3 -3
  102. package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
  103. package/scripts/backlog/backlog-drive-owner.ts +1 -1
  104. package/scripts/backlog/backlog-drive-restore.ts +1 -1
  105. package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
  106. package/scripts/backlog/backlog-drive.ts +1 -1
  107. package/scripts/backlog/backlog-next.ts +16 -5
  108. package/scripts/backlog/backlog-offer-cli.ts +6 -6
  109. package/scripts/backlog/backlog-offer.ts +1 -1
  110. package/scripts/backlog/backlog-plan-cli.ts +1 -1
  111. package/scripts/backlog/backlog-pool.ts +57 -11
  112. package/scripts/backlog/backlog-ready.ts +1 -1
  113. package/scripts/backlog/backlog-scope.ts +1 -1
  114. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  115. package/scripts/backlog/backlog-stalled.ts +1 -1
  116. package/scripts/build/build-codex-hooks.ts +3 -1
  117. package/scripts/build/build-commands.ts +66 -0
  118. package/scripts/build/build-hooks.ts +2 -1
  119. package/scripts/build/build-library.ts +12 -3
  120. package/scripts/build/build.ts +70 -0
  121. package/scripts/build/local-bin.ts +3 -13
  122. package/scripts/delegation/investigation-reads.ts +3 -1
  123. package/scripts/discovery/repo-origin.ts +5 -5
  124. package/scripts/doctor/doctor-consumer.ts +2 -2
  125. package/scripts/doctor/doctor-io.ts +2 -3
  126. package/scripts/document/capped-print-part.ts +30 -0
  127. package/scripts/document/capped-print.ts +3 -4
  128. package/scripts/document/document-byte-budget.ts +11 -2
  129. package/scripts/document/document-scan.ts +117 -0
  130. package/scripts/document/document-section.ts +143 -16
  131. package/scripts/document/entry-read-budget.ts +5 -3
  132. package/scripts/document/entry-read-set.ts +10 -89
  133. package/scripts/document/read-set-cost.ts +98 -0
  134. package/scripts/epic/epic-add-body.ts +1 -0
  135. package/scripts/epic/epic-audit-checks.ts +1 -1
  136. package/scripts/epic/epic-audit-rationale.ts +4 -3
  137. package/scripts/epic/epic-bundle-cli.ts +14 -3
  138. package/scripts/epic/epic-bundle-poll.ts +41 -0
  139. package/scripts/epic/epic-busy.ts +1 -1
  140. package/scripts/epic/epic-candidate-confirm.ts +3 -2
  141. package/scripts/epic/epic-classify.ts +1 -1
  142. package/scripts/epic/epic-cli-argv.ts +78 -46
  143. package/scripts/epic/epic-cli-remove.ts +11 -19
  144. package/scripts/epic/epic-cli.ts +74 -73
  145. package/scripts/epic/epic-close-comment.ts +1 -1
  146. package/scripts/epic/epic-close.ts +1 -1
  147. package/scripts/epic/epic-cross-repo.ts +5 -5
  148. package/scripts/epic/epic-graph.ts +1 -1
  149. package/scripts/epic/epic-index.ts +1 -1
  150. package/scripts/epic/epic-issue.ts +2 -6
  151. package/scripts/epic/epic-lane-offer.ts +2 -2
  152. package/scripts/epic/epic-next.ts +29 -42
  153. package/scripts/epic/epic-solo-stale.ts +1 -1
  154. package/scripts/epic/epic-triage.ts +8 -4
  155. package/scripts/epic/epic-validate.ts +1 -1
  156. package/scripts/followup/git-followup-finish.ts +2 -2
  157. package/scripts/followup/git-followup-label.ts +2 -2
  158. package/scripts/followup/git-followup-merged.ts +2 -2
  159. package/scripts/followup/git-pr-followup-checks.ts +142 -0
  160. package/scripts/followup/git-pr-followup.ts +4 -129
  161. package/scripts/gate/core-budget.ts +69 -1
  162. package/scripts/gate/gate-plan.ts +47 -7
  163. package/scripts/gate/gate-skip.ts +38 -7
  164. package/scripts/gate/hook-gate-reuse.ts +12 -10
  165. package/scripts/gate/pre-commit-type-check.ts +4 -1
  166. package/scripts/gate/project-checks.ts +6 -9
  167. package/scripts/gate/type-check-step.ts +2 -3
  168. package/scripts/gate/verification-gate.ts +27 -8
  169. package/scripts/gh/git-gh-api-path.ts +2 -2
  170. package/scripts/gh/git-gh-issue-rest.ts +3 -3
  171. package/scripts/gh/git-gh-issue-write.ts +53 -7
  172. package/scripts/gh/git-gh-pr-read.ts +64 -19
  173. package/scripts/gh/git-gh-pr-rest.ts +1 -1
  174. package/scripts/gh/git-gh-pr.ts +5 -1
  175. package/scripts/gh/git-pr-ai-review.ts +1 -1
  176. package/scripts/gh/git-pr-checks-eval.ts +1 -3
  177. package/scripts/gh/git-pr-checks-parse.ts +1 -1
  178. package/scripts/gh/git-pr-checks-watch.ts +6 -5
  179. package/scripts/gh/git-pr-checks.ts +7 -5
  180. package/scripts/gh/git-pr-coderabbit.ts +1 -1
  181. package/scripts/gh/git-pr-error.ts +1 -3
  182. package/scripts/gh/git-pr.ts +9 -13
  183. package/scripts/gh/github-issue-url.ts +2 -2
  184. package/scripts/git/constants.ts +2 -6
  185. package/scripts/git/git-command.ts +20 -218
  186. package/scripts/git/git-diff-reads.ts +225 -0
  187. package/scripts/git/git-error.ts +4 -12
  188. package/scripts/git/git-pre-push-hook.ts +139 -0
  189. package/scripts/git/git-prompt.ts +2 -6
  190. package/scripts/git/git-spawn-sync.ts +3 -1
  191. package/scripts/git/git-spawn.ts +1 -1
  192. package/scripts/git/git-staging.ts +4 -6
  193. package/scripts/git/git-status.ts +3 -9
  194. package/scripts/hooks/codex-hook-adapter.ts +1 -1
  195. package/scripts/hooks/edited-cspell.ts +2 -4
  196. package/scripts/hooks/format-edited-file.ts +3 -12
  197. package/scripts/hooks/pre-push-unit.ts +31 -9
  198. package/scripts/hooks/reserved-run.ts +2 -2
  199. package/scripts/hooks/run-hook.sh +5 -4
  200. package/scripts/hooks/stop-guard.ts +5 -3
  201. package/scripts/init/init-actions.ts +0 -5
  202. package/scripts/init/init-ai-copy.ts +13 -2
  203. package/scripts/init/init-logic-json-merge.ts +19 -0
  204. package/scripts/init/init-logic-secretlint.ts +7 -7
  205. package/scripts/init/init-logic-workspace.ts +41 -2
  206. package/scripts/init/init-logic.ts +16 -26
  207. package/scripts/init/init.ts +5 -15
  208. package/scripts/init/initial-commit.ts +4 -4
  209. package/scripts/init/kit-development-versions.ts +40 -0
  210. package/scripts/init/kit-setup-state.ts +2 -2
  211. package/scripts/init/start-exec.ts +68 -19
  212. package/scripts/init/start-setup-pr.ts +9 -9
  213. package/scripts/init/start-steps.ts +6 -10
  214. package/scripts/issue/defect-rate-cli.ts +1 -1
  215. package/scripts/issue/git-next-issues.ts +1 -1
  216. package/scripts/issue/issue-auto-ok.ts +1 -1
  217. package/scripts/issue/issue-backlinks-cli.ts +2 -2
  218. package/scripts/issue/issue-backlinks.ts +9 -9
  219. package/scripts/issue/issue-comment-cli.ts +19 -13
  220. package/scripts/issue/issue-file-cli.ts +45 -18
  221. package/scripts/issue/issue-file.ts +24 -1
  222. package/scripts/issue/issue-scout-closed.ts +1 -1
  223. package/scripts/issue/issue-state-cli.ts +30 -74
  224. package/scripts/josh/josh-command-map.ts +2 -0
  225. package/scripts/josh/josh-command-types.ts +11 -0
  226. package/scripts/josh/josh-commands-ai.ts +4 -4
  227. package/scripts/josh/josh-commands-backlog.ts +4 -1
  228. package/scripts/josh/josh-commands-development.ts +10 -0
  229. package/scripts/josh/josh-commands-document.ts +1 -0
  230. package/scripts/josh/josh-commands-lane.ts +22 -0
  231. package/scripts/josh/josh-commands-maintenance.ts +4 -4
  232. package/scripts/josh/josh-commands-run.ts +12 -7
  233. package/scripts/josh/josh-in-process.ts +28 -9
  234. package/scripts/josh/josh-logic.ts +33 -15
  235. package/scripts/josh/process-identity.ts +1 -3
  236. package/scripts/josh/session-language-cli.ts +3 -4
  237. package/scripts/lane/lane-await.ts +5 -1
  238. package/scripts/lane/lane-child-invocation.ts +2 -2
  239. package/scripts/lane/lane-cli.ts +3 -3
  240. package/scripts/lane/lane-dispatch.ts +14 -10
  241. package/scripts/lane/lane-environment.ts +5 -1
  242. package/scripts/lane/lane-handoff.ts +1 -1
  243. package/scripts/lane/lane-install.ts +2 -13
  244. package/scripts/lane/lane-launch-cli.ts +15 -1
  245. package/scripts/lane/lane-ledger.ts +145 -0
  246. package/scripts/lane/lane-leftover.ts +1 -1
  247. package/scripts/lane/lane-load.ts +108 -0
  248. package/scripts/lane/lane-measure-cli.ts +149 -0
  249. package/scripts/lane/lane-occupancy.ts +3 -0
  250. package/scripts/lane/lane-reap.ts +1 -3
  251. package/scripts/lane/lane-relaunch.ts +1 -1
  252. package/scripts/lane/lane-stats.ts +224 -0
  253. package/scripts/lane/lane-vacant.ts +36 -0
  254. package/scripts/lane/openai-lane-ship.ts +2 -2
  255. package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
  256. package/scripts/lane/openai-lane-supervisor.ts +7 -165
  257. package/scripts/lane/openai-review-broker.ts +2 -4
  258. package/scripts/lib/ancestor-directories.ts +35 -0
  259. package/scripts/lib/animation-helpers.ts +2 -2
  260. package/scripts/lib/buffered-process.ts +3 -7
  261. package/scripts/lib/cli-flags.ts +71 -1
  262. package/scripts/lib/spawn-exit.ts +2 -2
  263. package/scripts/lib/timeouts.ts +35 -2
  264. package/scripts/lines/line-budget.ts +4 -6
  265. package/scripts/notify/telegram-test.ts +6 -0
  266. package/scripts/observations/observation-ledger-line.ts +6 -6
  267. package/scripts/observations/observation-ledger-migrate.ts +55 -4
  268. package/scripts/observations/observation-ledger.ts +26 -7
  269. package/scripts/overrides/lockfile-overrides.ts +1 -1
  270. package/scripts/overrides/overrides-logic.ts +1 -1
  271. package/scripts/propagate/propagate-git.ts +1 -1
  272. package/scripts/propagate/propagate-publish.ts +2 -2
  273. package/scripts/propagate/propagate-steps.ts +20 -15
  274. package/scripts/propagate/propagate.ts +36 -27
  275. package/scripts/refactor/import-graph.ts +18 -1
  276. package/scripts/refactor/refactor-lint.ts +4 -4
  277. package/scripts/repo/repository-labels.ts +2 -2
  278. package/scripts/repo/required-checks-logic.ts +1 -3
  279. package/scripts/review/live-evidence.ts +2 -4
  280. package/scripts/review/review-brief-cli.ts +1 -1
  281. package/scripts/review/review-finding-ledger.ts +1 -1
  282. package/scripts/review/review-findings-cli.ts +1 -3
  283. package/scripts/rules/decision-oracle-batch.ts +128 -0
  284. package/scripts/rules/decision-oracle-tokens.ts +21 -0
  285. package/scripts/rules/decision-oracle.ts +8 -120
  286. package/scripts/rules/delivered-rules.ts +26 -182
  287. package/scripts/rules/direct-filing.ts +1 -1
  288. package/scripts/rules/early-heartbeat.ts +3 -3
  289. package/scripts/rules/git-argv.ts +1 -1
  290. package/scripts/rules/git-force.ts +1 -1
  291. package/scripts/rules/implementation-cut.ts +2 -2
  292. package/scripts/rules/index-guard.ts +1 -1
  293. package/scripts/rules/issue-comments.ts +118 -0
  294. package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
  295. package/scripts/rules/lane-background.ts +2 -2
  296. package/scripts/rules/lane-park.ts +18 -0
  297. package/scripts/rules/lane-split-park.ts +128 -0
  298. package/scripts/rules/oracle-firing.ts +1 -1
  299. package/scripts/rules/poll-loop.ts +1 -1
  300. package/scripts/rules/pre-gate-cut.ts +1 -1
  301. package/scripts/rules/protected-files.ts +59 -4
  302. package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
  303. package/scripts/rules/shell-body-trigger.ts +1 -1
  304. package/scripts/rules/stop-rules.ts +18 -7
  305. package/scripts/rules/test-declared-commit.ts +4 -4
  306. package/scripts/rules/wip-cap.ts +63 -0
  307. package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
  308. package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
  309. package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
  310. package/scripts/run/{run-carry.ts → carry/run-carry.ts} +1 -1
  311. package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
  312. package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +2 -2
  313. package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
  314. package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
  315. package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
  316. package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
  317. package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
  318. package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +16 -2
  319. package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
  320. package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
  321. package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
  322. package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
  323. package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
  324. package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
  325. package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
  326. package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
  327. package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
  328. package/scripts/run/run-ending.ts +2 -2
  329. package/scripts/run/run-halfrun-resume.ts +6 -1
  330. package/scripts/run/run-headless.ts +1 -1
  331. package/scripts/run/run-invocation.ts +85 -84
  332. package/scripts/run/run-liveness.ts +1 -1
  333. package/scripts/run/run-parent-cut-hook.ts +1 -1
  334. package/scripts/run/run-preflight.ts +6 -6
  335. package/scripts/run/run-prep-cli.ts +1 -1
  336. package/scripts/run/run-prrun-resume.ts +1 -1
  337. package/scripts/run/run-report-cli.ts +4 -4
  338. package/scripts/run/run-report.ts +2 -2
  339. package/scripts/run/run-status-cli.ts +1 -1
  340. package/scripts/run/run-step-cli.ts +3 -3
  341. package/scripts/run/run-step.ts +2 -2
  342. package/scripts/run/run-stop-notify.ts +1 -1
  343. package/scripts/run/run-stranded-detect.ts +4 -4
  344. package/scripts/run/run-stranded.ts +2 -2
  345. package/scripts/run/run-watcher-guard-cli.ts +1 -1
  346. package/scripts/run/run-watcher-guard.ts +5 -3
  347. package/scripts/run/run-watcher-hook.ts +1 -1
  348. package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +2 -2
  349. package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
  350. package/scripts/run/ship/run-ship-lock.ts +50 -0
  351. package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
  352. package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
  353. package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +4 -3
  354. package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
  355. package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
  356. package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +11 -12
  357. package/scripts/run/ship/run-ship-stop-text.ts +13 -0
  358. package/scripts/run/{run-ship-sync.ts → ship/run-ship-sync.ts} +25 -11
  359. package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
  360. package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +23 -8
  361. package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +3 -3
  362. package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +59 -8
  363. package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
  364. package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +8 -2
  365. package/scripts/run/wake/run-wake-restart.ts +58 -0
  366. package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
  367. package/scripts/run/{run-wake.ts → wake/run-wake.ts} +1 -1
  368. package/scripts/safe-chain/preinstall-command.ts +20 -21
  369. package/scripts/safe-chain/project-config.ts +19 -15
  370. package/scripts/sync/sync-ai-files.ts +1 -1
  371. package/scripts/sync/sync-configs.ts +2 -1
  372. package/scripts/sync/sync.ts +15 -2
  373. package/scripts/test/unit-worker-share.ts +1 -0
  374. package/scripts/time-runtime/time-background.ts +5 -1
  375. package/scripts/time-runtime/time-instant.ts +3 -1
  376. package/scripts/time-runtime/time-spans.ts +6 -139
  377. package/scripts/time-runtime/time-tool-call.ts +160 -0
  378. package/scripts/version/effective-upstream.ts +2 -20
  379. package/scripts/version/latest-corepack.ts +17 -17
  380. package/scripts/version/latest-regression.ts +1 -1
  381. package/scripts/version/latest-update.ts +4 -4
  382. package/scripts/version/npm-registry.ts +20 -5
  383. package/scripts/version/publishable-range-check.ts +2 -2
  384. package/scripts/version/publishable-range.ts +1 -1
  385. package/scripts/version/release-age.ts +48 -38
  386. package/scripts/version/release-hold.ts +1 -1
  387. package/scripts/version/safe-chain-version-update.ts +40 -0
  388. package/scripts/version/upgrade-shell-command.ts +48 -8
  389. package/scripts/version/version-check-logic.ts +23 -14
  390. package/scripts/version/version-commands.ts +23 -11
  391. package/scripts/version/version-remote.ts +28 -13
  392. package/templates/pnpm-workspace.basic.yaml +4 -0
  393. package/templates/workflows/dependabot-auto-merge.yml +5 -5
  394. package/templates/workflows/github-release.yml +2 -0
  395. package/scripts/build/build-config-merge.ts +0 -27
  396. package/scripts/build/build-managed-marker.ts +0 -27
  397. package/scripts/build/build-self-sync-guard.ts +0 -27
  398. package/scripts/build/build-version.ts +0 -26
  399. package/scripts/version/preinstall-version-update.ts +0 -86
  400. /package/scripts/git/{schemas.ts → git-schemas.ts} +0 -0
  401. /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
  402. /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
  403. /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
  404. /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
  405. /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
  406. /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
  407. /package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +0 -0
  408. /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
  409. /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
  410. /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
  411. /package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +0 -0
  412. /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
  413. /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
  414. /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
  415. /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
  416. /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
  417. /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
  418. /package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +0 -0
  419. /package/scripts/run/{run-ship-pre-detach.ts → ship/run-ship-pre-detach.ts} +0 -0
  420. /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
  421. /package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +0 -0
  422. /package/scripts/run/{run-ship.ts → ship/run-ship.ts} +0 -0
  423. /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
  424. /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
  425. /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
@@ -15,7 +15,7 @@ import { shell_segments } from './shell-segments'
15
15
  //
16
16
  // **It fires on every occurrence, not once per run.** A force push is destructive every time it is
17
17
  // issued, so refused-once-and-free-after would put the second one back on the run's self-restraint —
18
- // the disposition `run-tail.ts` and `filing-cap.ts` take for the same reason.
18
+ // the disposition `run-tail-rule.ts` and `filing-cap.ts` take for the same reason.
19
19
 
20
20
  // A force push. The long forms all open with `--force` (`--force`, `--force-with-lease[=ref]`,
21
21
  // `--force-if-includes`); `-f` and any cluster carrying `f` are the short forms; a bare `--delete` and
@@ -5,8 +5,8 @@ import { cost_verdict } from '#scripts/cost-runtime/cost-verdict'
5
5
  import type { GuardRun } from '#scripts/josh/hook-decision'
6
6
  import { lane_child_marker, type MarkerSource } from '#scripts/lane/lane-child-marker'
7
7
  import { lane_paths } from '#scripts/lane/lane-paths'
8
- import { run_cut, type RunCut } from '#scripts/run/run-cut'
9
- import { run_cut_handoff } from '#scripts/run/run-cut-handoff'
8
+ import { run_cut, type RunCut } from '#scripts/run/cut/run-cut'
9
+ import { run_cut_handoff } from '#scripts/run/cut/run-cut-handoff'
10
10
  import type { GuardedCall } from '#scripts/time-runtime/time-batch-guard'
11
11
  import { bash_triggers } from './bash-triggers'
12
12
  import { implementation_cut_verdict } from './implementation-cut-verdict'
@@ -52,7 +52,7 @@ function is_index_mutation(command: string): boolean {
52
52
  const INDEX_MUTATION_REASON =
53
53
  '⛔ git index mutation: `git add` / `stage` / `commit` / `reset` / `rm` / `mv` and `git restore ' +
54
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` → "git index を勝手に変更しない"). The ' +
55
+ 'judgement (`prompts/collaboration-workflow/operating-rules.md` → "no-self-staging"). The ' +
56
56
  '`.claude/settings.json` deny list matches prefix globs, so it misses `git -C . commit`, `git -c ' +
57
57
  'k=v commit` and `env git commit`; this row reads the argv instead, so the spelling does not matter. ' +
58
58
  'A commit goes through `pnpm josh git -y`; any other staging needs explicit current-turn user ' +
@@ -0,0 +1,118 @@
1
+ import { bash_triggers } from './bash-triggers'
2
+ import { gh_api } from './gh-api'
3
+ import { prior_comment_read } from './prior-comment-read'
4
+ import { shell_segments } from './shell-segments'
5
+
6
+ // The trigger and the delivered text behind the `issue-comments` row of `delivered-rules.ts`, moved
7
+ // out of the enumeration so the list stays a list (joshuafolkken/kit#3264).
8
+
9
+ // **A shell line carries several commands, and the subcommand has to be the one being invoked.**
10
+ // Each segment is judged on its own, anchored at its start, so `gh issue comment <N> -b "… gh issue
11
+ // view <N> …"` is read as the write it is rather than as the read it quotes. The cut itself is
12
+ // `shell-segments.ts`, shared with the triggers that need the same one.
13
+ // Global flags may precede the subcommand (`gh --repo o/r issue view 1`), so they are skipped —
14
+ // the same optional-flag prefix `gh-api.ts` skips in front of `api`, shared from there.
15
+ const ISSUE_VIEW_COMMAND = new RegExp(String.raw`^gh\s+${gh_api.GH_FLAGS}issue\s+view\s`, 'u')
16
+ // `…/issues/<N>` — **one** Issue's body. The number has to end the path, so the listing
17
+ // (`…/issues`) and every sub-resource under it (`…/issues/1319/comments`) are left alone.
18
+ const ISSUE_BODY_PATH = /repos\/[^\s'"]*\/issues\/\d+(?=$|["'\s])/u
19
+ // **A write to that path is not a read of it**, which is why the body read below asks `gh_api.is_read`:
20
+ // `gh api` sends POST as soon as any field flag appears, and `kickoff` PATCHes `…/issues/<N>` to
21
+ // normalize a title and to fill a blank body — so without the read check the delivery would be spent
22
+ // refusing a write, and the genuine body read later in the same run would never be guarded.
23
+ // A segment that fetches comments: the flag, a `comments` field in a `--json` projection, or the
24
+ // comments endpoint. The short `-c` is deliberately absent — it belongs to `wc`, `grep` and `sort`
25
+ // far more often than to `gh`, and reading it as "comments included" silenced the rule on any line
26
+ // that ended in a pipe. A run that types it pays one round trip instead.
27
+ const FETCHES_COMMENTS = /--comments\b|--json\s[\w,]*\bcomments\b|\/comments\b/u
28
+ const ISSUES_PATH = /repos\/[^\s'"]*\/issues\//u
29
+
30
+ // A field projection — `--jq` for `gh api`, `--json` for `gh issue view` — whose value never names the
31
+ // body. `gh api …/issues/<N> --jq '{state, labels}'` fetches the Issue only to read its state or its
32
+ // labels, which is a state check and not the body read this rule guards (joshuafolkken/kit#1905); a
33
+ // `--jq '.body'` still names the body and stays a body read. The value is taken quoted or bare, so the
34
+ // comma list `--json state,labels` and the expression `'{state, labels}'` are read the same way.
35
+ const FIELD_PROJECTION = /(?:--jq|--json)[= ]\s*('[^']*'|"[^"]*"|\S+)/u
36
+ const NAMES_THE_BODY = /\bbody\b/u
37
+
38
+ function projects_away_body(segment: string): boolean {
39
+ const projection = FIELD_PROJECTION.exec(segment)
40
+
41
+ return projection !== null && !NAMES_THE_BODY.test(projection[1] ?? '')
42
+ }
43
+
44
+ function is_body_read_segment(segment: string): boolean {
45
+ if (projects_away_body(segment)) return false
46
+
47
+ if (ISSUE_VIEW_COMMAND.test(segment)) return true
48
+
49
+ return gh_api.is_gh_api(segment) && ISSUE_BODY_PATH.test(segment) && gh_api.is_read(segment)
50
+ }
51
+
52
+ // **An Issue's comments, not just any comments.** Batching pushes a run to fetch the body and the
53
+ // comments on one line, so the allowance has to reach across segments — but `gh pr view 42 --json
54
+ // comments` says nothing about whether *this* Issue was read whole, so the segment doing the
55
+ // fetching has to be an Issue read itself.
56
+ function fetches_issue_comments(segment: string): boolean {
57
+ if (!FETCHES_COMMENTS.test(segment)) return false
58
+
59
+ return (
60
+ ISSUE_VIEW_COMMAND.test(segment) || (gh_api.is_gh_api(segment) && ISSUES_PATH.test(segment))
61
+ )
62
+ }
63
+
64
+ // **The trigger is the body read, not the start of implementation.** The moment an Issue's body
65
+ // reaches a run is the moment the rule binds, and it is one shell call — the test
66
+ // `prompts/collaboration-workflow/rule-delivery.md` sets for leaving residency.
67
+ function is_body_only_issue_read(command: string): boolean {
68
+ const segments = shell_segments.segments_of(command)
69
+
70
+ if (segments.some((segment) => fetches_issue_comments(segment))) return false
71
+
72
+ return segments.some((segment) => is_body_read_segment(segment))
73
+ }
74
+
75
+ // **Keeping the comments rule is fetching them**, in any of the spellings the refusal hands back.
76
+ function reads_issue_comments(command: string): boolean {
77
+ return shell_segments.segments_of(command).some((segment) => fetches_issue_comments(segment))
78
+ }
79
+
80
+ // **The refusal hands over the command that fixes it**, because reading is not the same as
81
+ // obeying: a sentence saying "also read the comments" is prose of exactly the kind
82
+ // joshuafolkken/kit#1344 measured as moving nothing, while a refused body read leaves the run
83
+ // holding the reissue that makes the comments *present*. The conflict rule ships with it — a
84
+ // delivery that said only "read them" would hand back the deciding at the moment nothing else is
85
+ // open to read, the mistake joshuafolkken/kit#1518 corrected for the WIP cap.
86
+ const ISSUE_COMMENTS_REASON =
87
+ "⛔ an Issue's comments are part of the Issue: read them before implementing, not only the body. " +
88
+ 'A decision recorded after the body was written lives only in a comment — a corrected diagnosis ' +
89
+ '(joshuafolkken/kit#1537), a changed default and an added acceptance criterion ' +
90
+ '(joshuafolkken/kit#1520), a scope handed to another Issue (joshuafolkken/kit#1304) — and ' +
91
+ 'nothing in the body says it was superseded, so a body-only reader builds the wrong thing and ' +
92
+ 'sees no contradiction. Reissue this read with the comments included: ' +
93
+ "`gh api repos/{owner}/{repo}/issues/<N>/comments --jq '.[] | {user: .user.login, created_at, " +
94
+ "body}'` beside the body read, or `gh issue view <N> --comments` where GraphQL is reachable. " +
95
+ 'Then the later text is the agreement in force — a comment supersedes the body it contradicts — ' +
96
+ "except for two answers that are not the run's to make: work a comment reassigns to another " +
97
+ 'Issue is out of scope and is not implemented, and a comment saying the Issue no longer has a ' +
98
+ 'reason to exist stops the run with a `confirmation` Telegram. The procedure is ' +
99
+ '`.claude/skills/workflow-commands/issue-comments.md`. ' +
100
+ 'Every body-only read is refused until the comments are read.'
101
+
102
+ // The enumeration row itself, so `delivered-rules.ts` spreads one entry. `already_satisfied` stands it
103
+ // down once the comments are on the transcript tail, and until then it refuses every body-only read.
104
+ const ROW = {
105
+ id: 'issue-comments',
106
+ is_trigger: bash_triggers.on_bash_command(is_body_only_issue_read),
107
+ reason: ISSUE_COMMENTS_REASON,
108
+ already_satisfied: prior_comment_read.already_read_for_call,
109
+ keeps: bash_triggers.on_bash_command(reads_issue_comments),
110
+ }
111
+
112
+ const issue_comments = {
113
+ ISSUE_COMMENTS_REASON,
114
+ ROW,
115
+ is_body_only_issue_read,
116
+ }
117
+
118
+ export { issue_comments }
@@ -67,6 +67,6 @@ const ROW = {
67
67
  keeps: bash_triggers.on_bash_command(runs_the_fold),
68
68
  }
69
69
 
70
- const issue_fold = { ISSUE_FOLD_REASON, ROW, already_folded, runs_the_fold }
70
+ const issue_fold_rule = { ISSUE_FOLD_REASON, ROW, already_folded, runs_the_fold }
71
71
 
72
- export { issue_fold }
72
+ export { issue_fold_rule }
@@ -7,7 +7,7 @@ import {
7
7
  time_transcript_line,
8
8
  type Block as TranscriptBlock,
9
9
  } from '#scripts/time-runtime/time-transcript-line'
10
- import { run_tail } from './run-tail'
10
+ import { run_tail_rule } from './run-tail-rule'
11
11
 
12
12
  // A dispatched lane child is stopped from backgrounding a long-running josh command (joshuafolkken/kit#2704).
13
13
  //
@@ -85,7 +85,7 @@ function is_backgrounded_ship(call: GuardedCall): boolean {
85
85
  // detached route on its first refusal rather than two contradictory ones. A backgrounded `ship` alone is
86
86
  // not refused: `foreground_input` turns it into the foreground call the refusal used to ask for.
87
87
  function is_candidate(call: GuardedCall): boolean {
88
- if (run_tail.is_push_step_call(call)) return true
88
+ if (run_tail_rule.is_push_step_call(call)) return true
89
89
 
90
90
  const command = backgrounded_bash_command(call)
91
91
 
@@ -1,6 +1,7 @@
1
1
  import { NEEDS_DECISION_LABEL } from '#scripts/issue/issue-labels'
2
2
  import { lane_child_marker } from '#scripts/lane/lane-child-marker'
3
3
  import { lane_handoff } from '#scripts/lane/lane-handoff'
4
+ import { bash_triggers } from './bash-triggers'
4
5
  import { shell_segments } from './shell-segments'
5
6
 
6
7
  // A dispatched lane child records its park before it stops, delivered at the call it binds on
@@ -124,9 +125,26 @@ const LANE_PARK_REASON =
124
125
  'procedure is `.claude/skills/workflow-commands/pre-gate-cut.md` → "A lane child records its park ' +
125
126
  'before it stops", and the park itself is `backlogrun-park.md` → "park and continue".'
126
127
 
128
+ // The row itself, so `delivered-rules.ts` spreads one entry. **Once per run rather than `decide`, and
129
+ // no `already_satisfied`.** The stop ends the turn, so it is not the recurring act joshuafolkken/kit#1570
130
+ // wrote `decide` for. It carries no `already_satisfied` because the park it asks for is a GitHub label a
131
+ // synchronous guard cannot read, and the transcript-tail alternative would read a child that merely
132
+ // *read* `backlogrun-park.md` — whose prose carries the `labels[]=needs-decision` command — as
133
+ // compliant and fall silent on a real violation (the header above). `keeps` reads an actual
134
+ // label-application `Bash` command, never the tail, so it is not fooled the same way; `reaches` stays
135
+ // `is_trigger`, since a compliant run issues the same confirmation notify and belongs in the
136
+ // denominator.
137
+ const ROW = {
138
+ id: 'lane-park',
139
+ is_trigger: bash_triggers.on_bash_command(is_unparked_stop),
140
+ reason: LANE_PARK_REASON,
141
+ keeps: bash_triggers.on_bash_command(records_the_park),
142
+ }
143
+
127
144
  const lane_park = {
128
145
  COMMAND_NOTIFY_MARKER,
129
146
  LANE_PARK_REASON,
147
+ ROW,
130
148
  is_confirmation_notify,
131
149
  is_parking_child,
132
150
  is_unparked_stop,
@@ -0,0 +1,128 @@
1
+ import type { GuardRun } from '#scripts/josh/hook-decision'
2
+ import { lane_child_marker } from '#scripts/lane/lane-child-marker'
3
+ import { time_density_hook } from '#scripts/time-runtime/time-density-hook'
4
+ import { bash_triggers } from './bash-triggers'
5
+ import { lane_park } from './lane-park'
6
+ import { shell_segments } from './shell-segments'
7
+ import { tail_commands } from './tail-commands'
8
+
9
+ // A dispatched lane child that split its own Issue is stopped from parking the epic it promoted
10
+ // (joshuafolkken/kit#3296).
11
+ //
12
+ // **A split is not a park.** On 2026-10-06, inside a `backlogrun`, the lane child for #3255 promoted
13
+ // its Issue to an epic with `pnpm josh epic --promote 3255`, filed #3291–#3294, then applied
14
+ // `needs-decision` to #3255 and sent a `confirmation` Telegram asking whether to run the epic. The
15
+ // children were already opted in, so the question had no answer to wait for, and the same run's child
16
+ // for #3247 had split without parking. `backlogrun-park.md` → "Splitting a child mid-run" says
17
+ // splitting alone never adds `needs-decision` to the promoted epic; the parent reads an open Issue that
18
+ // carries `epic` as a split (`run-merge.ts`), so the label is never what tells it.
19
+ //
20
+ // **The material is the run's own calls.** The promotion is a `josh epic --promote <N>` this run issued,
21
+ // read off the transcript tail as `filing-cap.ts` reads its filings — a tool call, never prose, so a
22
+ // child that merely read a document quoting the command is not mistaken for one that ran it. Only a
23
+ // promotion of the dispatched issue counts: the epic a mid-run split promotes is the child's own Issue.
24
+
25
+ // `josh epic`, named canonically — `shell_segments.is_josh_command` expands an alias before the match.
26
+ const EPIC_COMMANDS: ReadonlySet<string> = new Set(['epic'])
27
+ const PROMOTED_ISSUE = /--promote[=\s]+["']?(?<issue>\d+)\b/u
28
+ // The issue a label write targets, in the two spellings `lane_park.records_the_park` accepts: the REST
29
+ // path `…/issues/<N>/labels` and `gh issue edit <N>`.
30
+ const LABEL_TARGETS: ReadonlyArray<RegExp> = [
31
+ /\/issues\/(?<issue>\d+)\/labels\b/u,
32
+ /\bissue\s+edit\s+["']?(?<issue>\d+)\b/u,
33
+ ]
34
+ // The issue a confirmation notify is about, read off its `--issue-url` the way `stop-rules.ts` spells it.
35
+ const ISSUE_URL = /--issue-url[=\s]+["']?([^\s"']+)/u
36
+ const URL_ISSUE = /\/issues\/(\d+)\b/u
37
+
38
+ function promoted_in(segment: string): string | undefined {
39
+ if (!shell_segments.is_josh_command(segment, EPIC_COMMANDS)) return undefined
40
+
41
+ return PROMOTED_ISSUE.exec(segment)?.groups?.['issue']
42
+ }
43
+
44
+ // Every issue a `josh epic --promote` among these commands promoted.
45
+ function promoted_issues(commands: ReadonlyArray<string>): ReadonlySet<string> {
46
+ const segments = commands.flatMap((command) => shell_segments.segments_of(command))
47
+ const issues = segments.map((segment) => promoted_in(segment))
48
+
49
+ return new Set(issues.filter((issue) => issue !== undefined))
50
+ }
51
+
52
+ function label_target(command: string): string | undefined {
53
+ for (const pattern of LABEL_TARGETS) {
54
+ const issue = pattern.exec(command)?.groups?.['issue']
55
+
56
+ if (issue !== undefined) return issue
57
+ }
58
+
59
+ return undefined
60
+ }
61
+
62
+ // **Both acts of the park are refused: the label write and the stop Telegram.** Refusing the label
63
+ // alone left the notify to `lane-park`, whose once-per-run refusal asks for this very label and lets the
64
+ // reissue through, so the Telegram still went out. This row is listed before `lane-park` and shares the
65
+ // notify with it — an overlap that is safe because a run that promoted nothing is left to `lane-park`
66
+ // (`decide` answers false and the call falls through). Either act onto another issue — the leftover
67
+ // child the procedure does park — is not the epic's park, so it is left alone.
68
+ function is_park_of(command: string, issue: string): boolean {
69
+ return lane_park.records_the_park(command) && label_target(command) === issue
70
+ }
71
+
72
+ function notified_issue(command: string): string | undefined {
73
+ const [, url = ''] = ISSUE_URL.exec(command) ?? []
74
+ const [, issue] = URL_ISSUE.exec(url) ?? []
75
+
76
+ return issue
77
+ }
78
+
79
+ function is_notify_of(command: string, issue: string): boolean {
80
+ return lane_park.is_confirmation_notify(command) && notified_issue(command) === issue
81
+ }
82
+
83
+ // **The command test comes first and the world is consulted second**, exactly as `lane-park.ts` gates
84
+ // its own read: a person working in a lane carries no mark and sees no refusal.
85
+ function is_candidate(command: string): boolean {
86
+ const issue = lane_child_marker.marked_issue()
87
+
88
+ if (issue === undefined) return false
89
+ if (!is_park_of(command, issue) && !is_notify_of(command, issue)) return false
90
+
91
+ return lane_child_marker.is_child_of(process.cwd())
92
+ }
93
+
94
+ // Refused only once the run has promoted the dispatched issue, and on every occurrence: a split child
95
+ // must never park its epic, so the route is always "return the split", never a reissue.
96
+ function decide(_call: unknown, run: GuardRun): boolean {
97
+ const issue = lane_child_marker.marked_issue()
98
+ const commands = tail_commands.prior_bash_commands(time_density_hook.read_tail(run.transcript))
99
+
100
+ return issue !== undefined && promoted_issues(commands).has(issue)
101
+ }
102
+
103
+ const LANE_SPLIT_PARK_REASON =
104
+ '⛔ lane split park: this session is a dispatched lane child that promoted its own Issue to an epic ' +
105
+ 'this run, and a split is not a park — do not apply `needs-decision` to the promoted epic and do not ' +
106
+ 'send a `confirmation` Telegram. The parent reads an open Issue carrying `epic` as a split and ' +
107
+ 'continues through its children; a question here has nobody to answer it (joshuafolkken/kit#3296, ' +
108
+ 'measured 2026-10-06 when the child for #3255 parked its promoted epic). If what remains of the ' +
109
+ 'original work needs a person, park that child issue instead, then end the turn. The procedure is ' +
110
+ '`.claude/skills/workflow-commands/backlogrun-park.md` → "Splitting a child mid-run". This rule fires ' +
111
+ 'on every occurrence, not once per run.'
112
+
113
+ // It declares no `keeps`: not parking is the absence of a call, so the row is reported unmeasured
114
+ // rather than scored on an act that does not exist (`lane-carry-conflict.ts`).
115
+ const ROW = {
116
+ id: 'lane-split-park',
117
+ is_trigger: bash_triggers.on_bash_command(is_candidate),
118
+ reason: LANE_SPLIT_PARK_REASON,
119
+ decide,
120
+ }
121
+
122
+ const lane_split_park = {
123
+ LANE_SPLIT_PARK_REASON,
124
+ ROW,
125
+ promoted_issues,
126
+ }
127
+
128
+ export { lane_split_park }
@@ -133,7 +133,7 @@ function firing_oracles(): ReadonlyArray<{
133
133
  return decision_oracle.DECISION_ORACLES.flatMap((oracle) => {
134
134
  const firing_point = FIRING.get(oracle.name)
135
135
 
136
- return firing_point === undefined ? [] : [{ oracle, firing_point }]
136
+ return firing_point === undefined ? [] : { oracle, firing_point }
137
137
  })
138
138
  }
139
139
 
@@ -17,7 +17,7 @@ import { bash_triggers } from './bash-triggers'
17
17
  // way the command's *exit* is not what ends the wait, and each pass spends a full-context round trip.
18
18
  // A command backgrounded with `run_in_background` re-invokes the run when it exits — the completion
19
19
  // notification is what ends the wait on the task itself, which is the mechanism this row hands back,
20
- // the same reading joshuafolkken/kit#1510 took for the foreground push (`run-tail.ts`).
20
+ // the same reading joshuafolkken/kit#1510 took for the foreground push (`run-tail-rule.ts`).
21
21
  //
22
22
  // **It fires on every occurrence**, for the reason `git-force.ts` does: a poll loop wastes the same
23
23
  // round trips each time it is armed, so refused-once-and-free-after would put every loop but the first
@@ -2,7 +2,7 @@ import { cost_cli, type CostVerdict } from '#scripts/cost-runtime/cost-cli'
2
2
  import { cost_verdict } from '#scripts/cost-runtime/cost-verdict'
3
3
  import { lane_child_marker } from '#scripts/lane/lane-child-marker'
4
4
  import { lane_paths } from '#scripts/lane/lane-paths'
5
- import { run_cut, type RunCut } from '#scripts/run/run-cut'
5
+ import { run_cut, type RunCut } from '#scripts/run/cut/run-cut'
6
6
  import type { GuardedCall } from '#scripts/time-runtime/time-batch-guard'
7
7
  import { bash_triggers } from './bash-triggers'
8
8
  import { shell_segments } from './shell-segments'
@@ -2,12 +2,15 @@ import path from 'node:path'
2
2
  import { doctor_consumer } from '#scripts/doctor/doctor-consumer'
3
3
  import { json_value } from '#scripts/lib/json-value'
4
4
  import type { GuardedCall } from '#scripts/time-runtime/time-batch-guard'
5
+ import { bash_triggers } from './bash-triggers'
6
+ import { shell_segments } from './shell-segments'
5
7
 
6
8
  // The trigger and the delivered text behind the `protected-file` row of `delivered-rules.ts`
7
9
  // (joshuafolkken/kit#2983): the two files a run's own file tools must not touch.
8
10
  //
9
- // - **`.env` is never read.** It carries the Telegram bot token, and a Read lands it in the transcript.
10
- // The scripts that need it load it in their own process, so refusing the tool costs them nothing.
11
+ // - **`.env` is never read** — by the `Read` tool or by a shell reader in a `Bash` call. It carries the
12
+ // Telegram bot token, and either read lands it in the transcript. The scripts that need it load it
13
+ // in their own process, so refusing the call costs them nothing.
11
14
  // - **`.claude/settings.json` is never edited in a consumer repository.** It is the deny list and the
12
15
  // hooks that enforce every other rule, so a run that rewrites it can switch the guards off; `josh
13
16
  // sync` owns it there. Only a kit consumer's file is refused — decided per file from the repository
@@ -20,6 +23,29 @@ const READ_TOOL = 'Read'
20
23
  const EDIT_TOOLS: ReadonlySet<string> = new Set(['Edit', 'Write'])
21
24
  const ENVIRONMENT_FILE = '.env'
22
25
  const SETTINGS_FILE = path.join('.claude', 'settings.json')
26
+ const WHITESPACE = /\s+/u
27
+ const TOKEN_QUOTES = /["']/gu
28
+ const INPUT_REDIRECT = '<'
29
+ const SHELL_READERS: ReadonlySet<string> = new Set([
30
+ '.',
31
+ 'awk',
32
+ 'bat',
33
+ 'cat',
34
+ 'cut',
35
+ 'grep',
36
+ 'head',
37
+ 'less',
38
+ 'more',
39
+ 'nl',
40
+ 'od',
41
+ 'rg',
42
+ 'sed',
43
+ 'sort',
44
+ 'source',
45
+ 'strings',
46
+ 'tail',
47
+ 'xxd',
48
+ ])
23
49
 
24
50
  function file_path_of(call: GuardedCall): string {
25
51
  if (!json_value.is_record(call.input)) return ''
@@ -29,8 +55,36 @@ function file_path_of(call: GuardedCall): string {
29
55
  return typeof value === 'string' ? value : ''
30
56
  }
31
57
 
58
+ function is_environment_path(token: string): boolean {
59
+ return path.basename(token.replaceAll(TOKEN_QUOTES, '')) === ENVIRONMENT_FILE
60
+ }
61
+
62
+ // A shell read reaches the same bytes a `Read` does, so a segment that runs a reader (`cat .env`,
63
+ // `source .env`) or redirects the file into a command (`< .env`) is refused too. `.env.example` and
64
+ // `.env.local` keep passing: only a token whose basename is exactly `.env` counts.
65
+ function is_environment_read_segment(segment: string): boolean {
66
+ const words = segment
67
+ .replaceAll(INPUT_REDIRECT, ' < ')
68
+ .split(WHITESPACE)
69
+ .filter((word) => word !== '')
70
+ const is_redirected = words.some(
71
+ (word, index) => word === INPUT_REDIRECT && is_environment_path(words[index + 1] ?? ''),
72
+ )
73
+ const [command = '', ...rest] = words
74
+
75
+ return (
76
+ is_redirected || (SHELL_READERS.has(command) && rest.some((word) => is_environment_path(word)))
77
+ )
78
+ }
79
+
80
+ function is_environment_shell_read(command: string): boolean {
81
+ return shell_segments.segments_of(command).some((segment) => is_environment_read_segment(segment))
82
+ }
83
+
32
84
  function is_environment_read(call: GuardedCall): boolean {
33
- return call.name === READ_TOOL && path.basename(file_path_of(call)) === ENVIRONMENT_FILE
85
+ if (call.name === READ_TOOL) return is_environment_path(file_path_of(call))
86
+
87
+ return bash_triggers.on_bash_command(is_environment_shell_read)(call)
34
88
  }
35
89
 
36
90
  // The repository the settings file belongs to is two levels above it (`<root>/.claude/settings.json`).
@@ -55,7 +109,8 @@ function is_protected_file_call(
55
109
  }
56
110
 
57
111
  const PROTECTED_FILE_REASON =
58
- '⛔ protected file: `.env` holds the Telegram bot token, so reading it puts a secret in the ' +
112
+ '⛔ protected file: `.env` holds the Telegram bot token, so reading it — with the Read tool or a ' +
113
+ 'shell reader such as `cat` / `grep` / `source` — puts a secret in the ' +
59
114
  'transcript — the scripts that need it load it in their own process. `.claude/settings.json` in a ' +
60
115
  'consumer repository is the deny list and the hooks every other rule rests on, so a run must not ' +
61
116
  'rewrite it; `josh sync` owns it, and a change belongs upstream in kit (`CLAUDE.md` → "Route ' +
@@ -166,7 +166,7 @@ const RUN_TAIL_REASON =
166
166
  'run**, so ' +
167
167
  'reissuing the same call in the foreground will be refused again.'
168
168
 
169
- const run_tail = {
169
+ const run_tail_rule = {
170
170
  RUN_TAIL_REASON,
171
171
  decide,
172
172
  is_backgrounded_push_step,
@@ -175,4 +175,4 @@ const run_tail = {
175
175
  is_push_step_call,
176
176
  }
177
177
 
178
- export { run_tail }
178
+ export { run_tail_rule }
@@ -140,7 +140,7 @@ const BODY_FLAG = new RegExp(
140
140
  'u',
141
141
  )
142
142
 
143
- // **A flag is only a flag outside the quotes**, the same reading `run-tail.ts` takes of a pull-request
143
+ // **A flag is only a flag outside the quotes**, the same reading `run-tail-rule.ts` takes of a pull-request
144
144
  // title. This repository's own refusal text names `--body-file <path>`, so a `grep` or an `echo`
145
145
  // quoting it would otherwise be scored as a run that passed a body by path. The quoted spellings of
146
146
  // the same thing are not lost by blanking: `--field "body=@<path>"` is a whole-value file reference,
@@ -83,6 +83,9 @@ interface StopContext {
83
83
  handed_off: boolean
84
84
  // The resolved `JOSH_SESSION_LANG` — `session_language.resolve_session_lang` (joshuafolkken/kit#2470).
85
85
  session_lang: string
86
+ // This session is an agent kit launched under `claude -p` — `agent_headless.is_headless`
87
+ // (joshuafolkken/kit#3245). No person reads its reply, so the rules on how a reply is written stand down.
88
+ headless_agent: boolean
86
89
  }
87
90
 
88
91
  interface StopOutcome {
@@ -170,12 +173,10 @@ const HEADLESS_WAIT_CALLS = ['"command":"pnpm josh lane:await']
170
173
  const HEADLESS_WAIT_BODY =
171
174
  ' this is a `claude -p` `backlogrun` parent (`JOSH_RUN_HEADLESS`) with lanes still ' +
172
175
  'in flight, and ending this turn ends the process — its background waits are killed with it and the ' +
173
- 'lanes are left without a parent. Wait in the foreground instead: run `pnpm josh lane:await <N...>` ' +
174
- 'for the in-flight lanes as a foreground command within the tool timeout (with ' +
175
- '`pnpm josh run:progress --wait` in the background for the heartbeat, which streams its reports ' +
176
- 'itself and lives while this turn does), act on what it reports, and continue the loop. End the turn only ' +
177
- 'after `pnpm josh run:carry --cut` or `--end` (`backlogrun-progress.md` → "The parent keeps no clock ' +
178
- 'of its own").'
176
+ "lanes are left without a parent. Do not watch them yourself: the `run:wake` supervisor's driver " +
177
+ 'watches the lanes and wakes a session only for a branch that needs judging. Hand the loop back with ' +
178
+ '`pnpm josh run:carry --cut --owner "$PPID"` (or `--end` when the run is over), then end the turn ' +
179
+ '(`backlogrun-progress.md` → "Picking the lanes up in the fresh session").'
179
180
 
180
181
  const HEADLESS_WAIT_REASON = `${HEADLESS_WAIT_MARKER}${HEADLESS_WAIT_BODY}`
181
182
 
@@ -263,13 +264,23 @@ function citation_reason(context: StopContext): string | undefined {
263
264
  return build_citation_reason(references)
264
265
  }
265
266
 
267
+ // The two rules on how a reply is written. **A headless agent's reply has no reader**
268
+ // (joshuafolkken/kit#3245): its text lands in a stream-json log, so correcting its citations or its
269
+ // language costs a turn and reaches nobody. The artifacts a person does read — a Telegram, an Issue, a
270
+ // pull request — are held to the same rules by their own guards, not here.
271
+ function wording_reason(context: StopContext): string | undefined {
272
+ if (context.headless_agent) return undefined
273
+
274
+ return citation_reason(context) ?? language_reason(context)
275
+ }
276
+
266
277
  // The three rules that read the reply itself, filing offer first and the language last. A drifted reply
267
278
  // refused for another reason is not rewritten, but the drift still ends: `block_envelope` appends the
268
279
  // session-language note to every refusal, so the turn it continues is written in the session language.
269
280
  function reply_reason(context: StopContext): string | undefined {
270
281
  if (needs_filing(context)) return FILING_OFFER_REASON
271
282
 
272
- return citation_reason(context) ?? language_reason(context)
283
+ return wording_reason(context)
273
284
  }
274
285
 
275
286
  // The headless wait holds past the loop-breaker until the spin bound is reached.
@@ -1,9 +1,9 @@
1
1
  import { test_declared_changed } from '#scripts/test/test-declared-changed'
2
2
  import type { GuardedCall } from '#scripts/time-runtime/time-batch-guard'
3
- import { run_tail } from './run-tail'
3
+ import { run_tail_rule } from './run-tail-rule'
4
4
 
5
5
  // The trigger and the delivered text behind the `test-declared` row of `delivered-rules.ts`
6
- // (joshuafolkken/kit#2118). It lives beside `run-tail.ts` and the other per-rule modules rather than
6
+ // (joshuafolkken/kit#2118). It lives beside `run-tail-rule.ts` and the other per-rule modules rather than
7
7
  // inline in the enumeration, so the enumeration stays a list of rows.
8
8
  //
9
9
  // **A commit whose code change carries no test, refused at the commit stage `run-tail` already
@@ -11,7 +11,7 @@ import { run_tail } from './run-tail'
11
11
  // the input is mechanical — the changed paths, the test-file spelling, the exempt-path enumeration —
12
12
  // so it is a delivered rule rather than resident prose.
13
13
  //
14
- // **No new delivery path.** The command half is `run_tail.is_push_step_call`, reused verbatim, and the
14
+ // **No new delivery path.** The command half is `run_tail_rule.is_push_step_call`, reused verbatim, and the
15
15
  // only thing added is the world-read: the working-tree verdict `pnpm josh test:declared` prints. Like
16
16
  // `pre-gate-cut`, the command test comes first and the git read second, so an ordinary `Bash` call
17
17
  // pays nothing and only a `pnpm josh git -y` spawns `git status`.
@@ -23,7 +23,7 @@ import { run_tail } from './run-tail'
23
23
  // commit; refusing once and passing the reissue is the same safe direction `issue-comments` takes, and
24
24
  // a compliant reissue that adds a test flips the verdict to `satisfied` and stops matching anyway.
25
25
  function is_untested_commit(call: GuardedCall): boolean {
26
- if (!run_tail.is_push_step_call(call)) return false
26
+ if (!run_tail_rule.is_push_step_call(call)) return false
27
27
 
28
28
  return test_declared_changed.current_verdict() === 'required'
29
29
  }