@joshuafolkken/kit 1.1455.0 → 1.1514.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (374) hide show
  1. package/.claude/settings.json +6 -6
  2. package/.claude/skills/dependency-update/SKILL.md +26 -55
  3. package/.claude/skills/epic-commands/SKILL.md +83 -156
  4. package/.claude/skills/epic-commands/epic-bundle.md +51 -79
  5. package/.claude/skills/epic-commands/execution-waves.md +43 -81
  6. package/.claude/skills/verify-ui/SKILL.md +24 -50
  7. package/.claude/skills/workflow-commands/SKILL.md +7 -9
  8. package/.claude/skills/workflow-commands/background-commands.md +31 -46
  9. package/.claude/skills/workflow-commands/backlogrun-child.md +65 -85
  10. package/.claude/skills/workflow-commands/backlogrun-lanes.md +86 -125
  11. package/.claude/skills/workflow-commands/backlogrun-park.md +68 -118
  12. package/.claude/skills/workflow-commands/backlogrun-progress.md +17 -28
  13. package/.claude/skills/workflow-commands/backlogrun-recovery.md +17 -26
  14. package/.claude/skills/workflow-commands/backlogrun-steps.md +35 -52
  15. package/.claude/skills/workflow-commands/backlogrun.md +31 -50
  16. package/.claude/skills/workflow-commands/chain-rule.md +31 -40
  17. package/.claude/skills/workflow-commands/delegation.md +12 -8
  18. package/.claude/skills/workflow-commands/entry-sequence.md +1 -3
  19. package/.claude/skills/workflow-commands/followup.md +55 -77
  20. package/.claude/skills/workflow-commands/fullrun-steps.md +3 -5
  21. package/.claude/skills/workflow-commands/fullrun.md +4 -6
  22. package/.claude/skills/workflow-commands/halfrun.md +3 -7
  23. package/.claude/skills/workflow-commands/into-target.md +1 -2
  24. package/.claude/skills/workflow-commands/issue-comments.md +43 -60
  25. package/.claude/skills/workflow-commands/issue-scout.md +3 -2
  26. package/.claude/skills/workflow-commands/kickoff.md +65 -74
  27. package/.claude/skills/workflow-commands/latest-gate.md +2 -6
  28. package/.claude/skills/workflow-commands/needs-human-review.md +2 -5
  29. package/.claude/skills/workflow-commands/observation-filing.md +59 -118
  30. package/.claude/skills/workflow-commands/observation-ledger.md +52 -112
  31. package/.claude/skills/workflow-commands/pre-gate-cut.md +88 -142
  32. package/.claude/skills/workflow-commands/prerequisite.md +11 -17
  33. package/.claude/skills/workflow-commands/progress-watcher.md +23 -33
  34. package/.claude/skills/workflow-commands/prrun.md +3 -5
  35. package/.claude/skills/workflow-commands/retrospective.md +35 -54
  36. package/.claude/skills/workflow-commands/split-assessment.md +32 -58
  37. package/.claude/skills/workflow-commands/target-repository.md +1 -2
  38. package/.claude/skills/workflow-commands/working-tree-hold.md +54 -73
  39. package/.codex/hooks.json +6 -6
  40. package/.github/actions/setup-node/action.yml +1 -1
  41. package/.github/workflows/ci.yml +1 -1
  42. package/.github/workflows/publish.yml +2 -2
  43. package/CLAUDE.md +7 -6
  44. package/cspell/index.yaml +7 -0
  45. package/dist/CLAUDE.md +7 -6
  46. package/dist/commands/document-section-cli.js +67 -34
  47. package/dist/hooks/batch-guard.js +6 -6
  48. package/dist/hooks/chunk-4KKD2UTK.js +66 -0
  49. package/dist/hooks/{chunk-GPLGZ6IC.js → chunk-6TS2JD6D.js} +14 -3
  50. package/dist/hooks/chunk-7DSBJVFH.js +50 -0
  51. package/dist/hooks/{chunk-5H4SSDSD.js → chunk-A65Y4YOR.js} +3 -3
  52. package/dist/hooks/{chunk-3C4XRYG6.js → chunk-APUO6U4J.js} +102 -19
  53. package/dist/hooks/{chunk-IX6TTTI6.js → chunk-EFXE6C37.js} +8 -22
  54. package/dist/hooks/chunk-GP4IEC4O.js +20 -0
  55. package/dist/hooks/chunk-JP45SOCJ.js +56 -0
  56. package/dist/hooks/{chunk-OLMO72ON.js → chunk-LAOV7NLM.js} +1 -1
  57. package/dist/hooks/chunk-M4HGZV7U.js +83 -0
  58. package/dist/hooks/{chunk-TBJFTSUY.js → chunk-O6GDRNJB.js} +9 -7
  59. package/dist/hooks/{chunk-HFGWYGUV.js → chunk-PF2NZL2A.js} +5 -5
  60. package/dist/hooks/{chunk-WNLVGT7E.js → chunk-PTICX4OL.js} +4 -5
  61. package/dist/hooks/{chunk-HHI6FO3R.js → chunk-RT27QRJW.js} +763 -1515
  62. package/dist/hooks/{chunk-7WQBPLUN.js → chunk-SYOTDYCI.js} +1149 -1095
  63. package/dist/hooks/{chunk-LIAFCXL7.js → chunk-TNUMY6NL.js} +1519 -1332
  64. package/dist/hooks/chunk-UPUZF3AN.js +279 -0
  65. package/dist/hooks/{chunk-Z4XI7CZS.js → chunk-WK6SMBIC.js} +1932 -784
  66. package/dist/hooks/codex-hook-adapter.js +17 -14
  67. package/dist/hooks/duplicate-read-guard.js +7 -7
  68. package/dist/hooks/format-edited.js +5 -4
  69. package/dist/hooks/inputs.json +302 -247
  70. package/dist/hooks/investigation-guard.js +8 -11
  71. package/dist/hooks/pretool-guard.js +16 -13
  72. package/dist/hooks/session-lang.js +372 -26
  73. package/dist/hooks/stop-guard.js +2593 -189
  74. package/dist/josh.js +2128 -282
  75. package/dist/managed-marker/index.js +1 -2
  76. package/dist/version/index.js +15 -12
  77. package/eslint/base.js +2 -1
  78. package/eslint/rules/unicorn.js +16 -0
  79. package/lefthook/base.yml +5 -3
  80. package/package.json +14 -12
  81. package/prompts/coding-standards.md +12 -22
  82. package/prompts/collaboration-workflow/file-edits.md +25 -45
  83. package/prompts/collaboration-workflow/gh-rest.md +19 -36
  84. package/prompts/collaboration-workflow/glossary.md +5 -7
  85. package/prompts/collaboration-workflow/issue-citation.md +6 -14
  86. package/prompts/collaboration-workflow/issue-template.md +40 -32
  87. package/prompts/collaboration-workflow/lane-instructions.md +35 -0
  88. package/prompts/collaboration-workflow/operating-rules.md +33 -46
  89. package/prompts/collaboration-workflow/output-bounds.md +15 -15
  90. package/prompts/collaboration-workflow/overview.md +14 -19
  91. package/prompts/collaboration-workflow/plan-comment.md +13 -13
  92. package/prompts/collaboration-workflow/principles.md +57 -54
  93. package/prompts/collaboration-workflow/proposal-request.md +1 -9
  94. package/prompts/collaboration-workflow/report-format.md +41 -49
  95. package/prompts/collaboration-workflow/residency.md +15 -11
  96. package/prompts/collaboration-workflow/rule-delivery.md +7 -15
  97. package/prompts/collaboration-workflow/shell-body.md +10 -20
  98. package/prompts/collaboration-workflow/turn-batching.md +8 -14
  99. package/prompts/collaboration-workflow/upstream-interrupt.md +26 -34
  100. package/prompts/collaboration-workflow/wip-cap.md +26 -32
  101. package/prompts/collaboration-workflow.md +25 -25
  102. package/prompts/refactoring.md +33 -43
  103. package/prompts/review-rubric.md +84 -129
  104. package/prompts/review.md +110 -141
  105. package/prompts/sonar-hotspot-handling.md +22 -50
  106. package/prompts/testing-guide.md +48 -99
  107. package/scripts/adopt/adopt-toolkits.ts +2 -4
  108. package/scripts/agent/agent-argv.ts +20 -4
  109. package/scripts/agent/agent-exit-record.ts +23 -1
  110. package/scripts/agent/agent-role-profile.ts +101 -45
  111. package/scripts/agent/agent-safe-text.ts +60 -0
  112. package/scripts/agent/claude-agent-argv.ts +14 -0
  113. package/scripts/agent/codex-agent-argv.ts +9 -2
  114. package/scripts/agent/lane-instructions.ts +144 -0
  115. package/scripts/backlog/backlog-budget-cli.ts +1 -2
  116. package/scripts/backlog/backlog-budget.ts +4 -4
  117. package/scripts/backlog/backlog-drive-carried.ts +26 -0
  118. package/scripts/backlog/backlog-drive-cli.ts +10 -5
  119. package/scripts/backlog/backlog-drive-named.ts +3 -5
  120. package/scripts/backlog/backlog-drive-restore.ts +28 -2
  121. package/scripts/backlog/backlog-drive-seats.ts +27 -0
  122. package/scripts/backlog/backlog-drive.ts +11 -6
  123. package/scripts/backlog/backlog-occupied.ts +56 -0
  124. package/scripts/backlog/backlog-ready.ts +59 -15
  125. package/scripts/behavior/behavior-rules.ts +3 -2
  126. package/scripts/cost-runtime/codex-usage.ts +4 -2
  127. package/scripts/cost-runtime/cost-cli.ts +1 -2
  128. package/scripts/delegation/duplicate-read-guard.ts +2 -2
  129. package/scripts/delegation/investigation-guard.ts +15 -65
  130. package/scripts/delegation/investigation-reads.ts +1 -2
  131. package/scripts/discovery/repo-discovery.ts +4 -2
  132. package/scripts/discovery/repo-origin.ts +2 -4
  133. package/scripts/doctor/doctor-logic.ts +6 -3
  134. package/scripts/document/document-byte-budget.ts +28 -27
  135. package/scripts/document/document-byte-check.ts +1 -2
  136. package/scripts/document/document-section-cli.ts +17 -5
  137. package/scripts/document/entry-read-budget.ts +3 -3
  138. package/scripts/document/read-set-cli.ts +4 -1
  139. package/scripts/epic/epic-audit-orphans.ts +5 -8
  140. package/scripts/epic/epic-audit-rationale.ts +8 -2
  141. package/scripts/epic/epic-audit.ts +52 -1
  142. package/scripts/epic/epic-bundle-cli.ts +1 -2
  143. package/scripts/epic/epic-bundle-evidence.ts +49 -26
  144. package/scripts/epic/epic-bundle-referenced.ts +14 -3
  145. package/scripts/epic/epic-bundle.ts +17 -9
  146. package/scripts/epic/epic-chains.ts +6 -2
  147. package/scripts/epic/epic-close-comment.ts +1 -2
  148. package/scripts/followup/git-followup-workflow.ts +19 -1
  149. package/scripts/gate/check-inputs.ts +129 -0
  150. package/scripts/gate/check-reuse.ts +162 -0
  151. package/scripts/gate/core-budget.ts +93 -44
  152. package/scripts/gate/core-wait.ts +130 -0
  153. package/scripts/gate/free-cores.ts +49 -0
  154. package/scripts/gate/gate-ledger.ts +17 -3
  155. package/scripts/gate/gate-plan.ts +23 -1
  156. package/scripts/gate/gate-report.ts +15 -2
  157. package/scripts/gate/gate-run-steps.ts +150 -0
  158. package/scripts/gate/held-mark.ts +55 -0
  159. package/scripts/gate/machine-capacity.ts +70 -11
  160. package/scripts/gate/pre-commit-type-check.ts +14 -1
  161. package/scripts/gate/verification-gate.ts +65 -127
  162. package/scripts/gh/git-gh-issue-rest.ts +1 -0
  163. package/scripts/gh/git-gh-issue-write.ts +24 -2
  164. package/scripts/gh/git-pr.ts +19 -3
  165. package/scripts/git/git-common-directory.ts +3 -3
  166. package/scripts/git/git-diff-reads.ts +16 -9
  167. package/scripts/git/git-execa-error.ts +1 -2
  168. package/scripts/git/main-merge-regenerate.ts +78 -0
  169. package/scripts/git/main-merge.ts +14 -5
  170. package/scripts/hooks/batch-guard.ts +5 -8
  171. package/scripts/hooks/format-edited-file.ts +10 -4
  172. package/scripts/hooks/pre-commit-lint.ts +91 -0
  173. package/scripts/hooks/pre-push-unit.ts +15 -4
  174. package/scripts/hooks/pretool-guard.ts +48 -79
  175. package/scripts/hooks/reserved-run.ts +1 -0
  176. package/scripts/hooks/run-hook.sh +10 -12
  177. package/scripts/hooks/step-zero-notice.ts +3 -2
  178. package/scripts/hooks/stop-actions.ts +204 -0
  179. package/scripts/hooks/stop-guard.ts +62 -21
  180. package/scripts/init/hook-command-rewrite.ts +6 -2
  181. package/scripts/init/hook-launch.ts +12 -7
  182. package/scripts/init/init-logic-deploy-vps.ts +4 -2
  183. package/scripts/issue/behavior-change-lint.ts +12 -6
  184. package/scripts/issue/expected-effect-lint.ts +59 -0
  185. package/scripts/issue/issue-cite-cli.ts +35 -15
  186. package/scripts/issue/issue-classification.ts +1 -1
  187. package/scripts/issue/issue-lint-cli.ts +5 -3
  188. package/scripts/issue/issue-scout-cli.ts +22 -16
  189. package/scripts/issue/markdown-section.ts +19 -5
  190. package/scripts/josh/command-usage.ts +110 -0
  191. package/scripts/josh/file-map-stamp.ts +1 -3
  192. package/scripts/josh/hook-decision.ts +17 -2
  193. package/scripts/josh/josh-command-map.ts +2 -0
  194. package/scripts/josh/josh-command-types.ts +55 -9
  195. package/scripts/josh/josh-commands-ai.ts +13 -0
  196. package/scripts/josh/josh-commands-development.ts +1 -1
  197. package/scripts/josh/josh-commands-hooks.ts +6 -0
  198. package/scripts/josh/josh-commands-lane.ts +10 -1
  199. package/scripts/josh/josh-commands-workflow.ts +5 -0
  200. package/scripts/josh/josh-logic.ts +6 -15
  201. package/scripts/josh/josh-usage-automation.ts +121 -0
  202. package/scripts/josh/josh-usage-backlog.ts +180 -0
  203. package/scripts/josh/josh-usage-developer.ts +230 -0
  204. package/scripts/josh/josh-usage-hooks.ts +38 -0
  205. package/scripts/josh/josh-usage-issue.ts +205 -0
  206. package/scripts/josh/josh-usage-review.ts +101 -0
  207. package/scripts/josh/josh-usage-run.ts +252 -0
  208. package/scripts/josh/josh-usage-session.ts +238 -0
  209. package/scripts/josh/josh-usage-workflow.ts +212 -0
  210. package/scripts/josh/josh.ts +14 -5
  211. package/scripts/josh/package-directory.ts +20 -0
  212. package/scripts/lane/lane-await.ts +3 -2
  213. package/scripts/lane/lane-capacity.ts +8 -2
  214. package/scripts/lane/lane-check-stats.ts +56 -0
  215. package/scripts/lane/lane-child-invocation.ts +49 -5
  216. package/scripts/lane/lane-dispatch.ts +3 -3
  217. package/scripts/lane/lane-guard-policy.ts +5 -5
  218. package/scripts/lane/lane-handoff.ts +8 -1
  219. package/scripts/lane/lane-install.ts +12 -4
  220. package/scripts/lane/lane-ledger.ts +52 -1
  221. package/scripts/lane/lane-limit-cli.ts +8 -9
  222. package/scripts/lane/lane-load.ts +9 -2
  223. package/scripts/lane/lane-measure-cli.ts +4 -0
  224. package/scripts/lane/lane-parked.ts +18 -0
  225. package/scripts/lane/lane-preserve.ts +29 -1
  226. package/scripts/lane/lane-process-load.ts +125 -0
  227. package/scripts/lane/lane-process-stats.ts +93 -0
  228. package/scripts/lane/lane-registry.ts +17 -10
  229. package/scripts/lane/lane-restart.ts +5 -26
  230. package/scripts/lane/lane-session-action.ts +146 -0
  231. package/scripts/lane/lane-session-broker.ts +68 -125
  232. package/scripts/lane/lane-spent.ts +14 -3
  233. package/scripts/lane/lane-stats.ts +1 -0
  234. package/scripts/lane/lane-withdraw-cli.ts +71 -0
  235. package/scripts/lane/lane-withdraw.ts +182 -0
  236. package/scripts/lint/format.ts +4 -4
  237. package/scripts/lint/lint-parallel.ts +10 -3
  238. package/scripts/lint/lint-related-scope.ts +4 -1
  239. package/scripts/managed-marker/managed-marker-logic.ts +1 -2
  240. package/scripts/notify/telegram-test.ts +10 -5
  241. package/scripts/overrides/lockfile-overrides.ts +1 -3
  242. package/scripts/propagate/propagate.ts +1 -2
  243. package/scripts/release/release-progress.ts +3 -2
  244. package/scripts/report/report-format-reference.ts +2 -0
  245. package/scripts/report/report-lint.ts +7 -4
  246. package/scripts/review/acceptance-map-check.ts +61 -0
  247. package/scripts/review/acceptance-map.ts +205 -0
  248. package/scripts/review/review-brief-cli.ts +1 -1
  249. package/scripts/review/review-record-cli.ts +6 -2
  250. package/scripts/rules/agent-model.ts +59 -0
  251. package/scripts/rules/delivered-rules.ts +11 -2
  252. package/scripts/rules/destructive-command.ts +6 -9
  253. package/scripts/rules/direct-filing.ts +3 -7
  254. package/scripts/rules/early-heartbeat.ts +7 -18
  255. package/scripts/rules/file-body.ts +6 -13
  256. package/scripts/rules/filing-cap.ts +4 -6
  257. package/scripts/rules/firing-point.ts +6 -5
  258. package/scripts/rules/git-force.ts +5 -9
  259. package/scripts/rules/guard-remedy.ts +117 -0
  260. package/scripts/rules/implementation-cut.ts +37 -35
  261. package/scripts/rules/index-guard.ts +4 -7
  262. package/scripts/rules/issue-comments.ts +67 -16
  263. package/scripts/rules/josh-git-bare.ts +3 -6
  264. package/scripts/rules/lane-background.ts +10 -21
  265. package/scripts/rules/lane-carry-conflict.ts +7 -17
  266. package/scripts/rules/lane-interactive-ask.ts +6 -11
  267. package/scripts/rules/lane-park.ts +5 -13
  268. package/scripts/rules/lane-split-park.ts +2 -7
  269. package/scripts/rules/lane-switch-main.ts +9 -12
  270. package/scripts/rules/last-prompt.ts +50 -5
  271. package/scripts/rules/oracle-consulted.ts +4 -5
  272. package/scripts/rules/oracle-list-cli.ts +7 -0
  273. package/scripts/rules/piped-verification.ts +84 -32
  274. package/scripts/rules/poll-loop.ts +7 -17
  275. package/scripts/rules/pre-gate-cut.ts +7 -21
  276. package/scripts/rules/prior-comment-read.ts +7 -5
  277. package/scripts/rules/protected-files.ts +5 -9
  278. package/scripts/rules/raw-field-body.ts +3 -7
  279. package/scripts/rules/rule-body-guard.ts +4 -11
  280. package/scripts/rules/rule-list.ts +4 -1
  281. package/scripts/rules/rule-registry-rows.ts +10 -0
  282. package/scripts/rules/rule-registry.ts +7 -4
  283. package/scripts/rules/rule-rewrite.ts +114 -0
  284. package/scripts/rules/rule-value.ts +7 -1
  285. package/scripts/rules/run-tail-rule.ts +6 -22
  286. package/scripts/rules/shell-body-trigger.ts +4 -6
  287. package/scripts/rules/shell-segments.ts +22 -0
  288. package/scripts/rules/stop-decision.ts +77 -0
  289. package/scripts/rules/stop-rules.ts +164 -77
  290. package/scripts/rules/tail-commands.ts +15 -1
  291. package/scripts/rules/test-declared-commit.ts +4 -10
  292. package/scripts/rules/third-party-write.ts +5 -10
  293. package/scripts/rules/worktree-guard.ts +5 -12
  294. package/scripts/run/board/run-board-cli.ts +2 -0
  295. package/scripts/run/board/run-board-closed.ts +8 -2
  296. package/scripts/run/board/run-board-github.ts +19 -3
  297. package/scripts/run/board/run-board-header.ts +4 -1
  298. package/scripts/run/board/run-board-idle.ts +31 -0
  299. package/scripts/run/board/run-board-labels.ts +2 -0
  300. package/scripts/run/board/run-board-layout.ts +1 -1
  301. package/scripts/run/board/run-board-read.ts +25 -12
  302. package/scripts/run/board/run-board-reason.ts +17 -4
  303. package/scripts/run/board/run-board-render-legend.ts +1 -0
  304. package/scripts/run/board/run-board-render-slot.ts +1 -1
  305. package/scripts/run/board/run-board-render-waits.ts +41 -0
  306. package/scripts/run/board/run-board-render.ts +6 -1
  307. package/scripts/run/board/run-board-row-yield.ts +1 -0
  308. package/scripts/run/board/run-board-state.ts +9 -0
  309. package/scripts/run/board/run-board-status.ts +89 -23
  310. package/scripts/run/board/run-board-thanks.ts +7 -2
  311. package/scripts/run/board/run-board-tick.ts +37 -8
  312. package/scripts/run/carry/run-carry-args.ts +1 -3
  313. package/scripts/run/carry/run-carry-conversation.ts +7 -2
  314. package/scripts/run/cut/run-cut-cli.ts +18 -22
  315. package/scripts/run/cut/run-cut-decline.ts +72 -0
  316. package/scripts/run/cut/run-cut-ready.ts +74 -0
  317. package/scripts/run/cut/run-cut-report.ts +29 -0
  318. package/scripts/run/cut/run-cut.ts +10 -1
  319. package/scripts/run/detached-launch.ts +23 -4
  320. package/scripts/run/entry/run-entry-cli.ts +8 -1
  321. package/scripts/run/event/run-event-render.ts +1 -0
  322. package/scripts/run/event/run-event-scope.ts +19 -1
  323. package/scripts/run/event/run-event-stream-emit.ts +13 -6
  324. package/scripts/run/event/run-event-stream.ts +4 -0
  325. package/scripts/run/hold/run-hold-cli.ts +14 -3
  326. package/scripts/run/hold/run-hold-session.ts +46 -0
  327. package/scripts/run/hold/run-hold.ts +13 -4
  328. package/scripts/run/launch-priority.ts +32 -0
  329. package/scripts/run/merge/run-merge-cli.ts +16 -42
  330. package/scripts/run/merge/run-merge-ship.ts +59 -1
  331. package/scripts/run/merge/run-merge-signals.ts +99 -0
  332. package/scripts/run/merge/run-merge-steps.ts +11 -1
  333. package/scripts/run/merge/run-merge.ts +22 -9
  334. package/scripts/run/run-ending.ts +31 -3
  335. package/scripts/run/run-liveness.ts +1 -1
  336. package/scripts/run/run-parent-cut-hook.ts +5 -10
  337. package/scripts/run/run-poll-hook.ts +157 -0
  338. package/scripts/run/run-preflight.ts +1 -2
  339. package/scripts/run/run-step-cli.ts +22 -15
  340. package/scripts/run/run-step-section.ts +85 -0
  341. package/scripts/run/run-stop-notify.ts +1 -2
  342. package/scripts/run/run-watcher-guard.ts +6 -3
  343. package/scripts/run/ship/run-ship-cli.ts +5 -12
  344. package/scripts/run/ship/run-ship-detach.ts +9 -0
  345. package/scripts/run/ship/run-ship-next.ts +10 -2
  346. package/scripts/run/ship/run-ship-overlap.ts +195 -0
  347. package/scripts/run/ship/run-ship-pre-detach.ts +33 -15
  348. package/scripts/run/ship/run-ship-preflight.ts +28 -2
  349. package/scripts/run/ship/run-ship-return.ts +31 -7
  350. package/scripts/run/ship/run-ship-review-steps.ts +25 -14
  351. package/scripts/run/ship/run-ship-review.ts +15 -2
  352. package/scripts/run/ship/run-ship-steps.ts +1 -1
  353. package/scripts/run/ship/run-ship-stop-context.ts +206 -0
  354. package/scripts/run/ship/run-ship-sync.ts +14 -6
  355. package/scripts/run/tidy/run-tidy-cli.ts +3 -3
  356. package/scripts/run/tidy/run-tidy-lanes.ts +3 -2
  357. package/scripts/safe-chain/project-config.ts +7 -2
  358. package/scripts/sync/sync-hook-safety.ts +8 -2
  359. package/scripts/test/test-e2e-guard.ts +30 -5
  360. package/scripts/test/test-unit-guard.ts +16 -1
  361. package/scripts/test/unit-worker-share.ts +26 -3
  362. package/scripts/time-runtime/time-batch-guard.ts +3 -3
  363. package/scripts/time-runtime/time-hook-transcript.ts +3 -2
  364. package/scripts/time-runtime/time-shell.ts +39 -4
  365. package/scripts/time-runtime/time-spans.ts +1 -0
  366. package/scripts/time-runtime/time-tool-call.ts +7 -0
  367. package/scripts/version/effective-upstream.ts +3 -4
  368. package/scripts/version/kit-version-config.ts +8 -2
  369. package/scripts/version/latest-corepack.ts +1 -2
  370. package/scripts/version/release-hold.ts +11 -4
  371. package/scripts/version/version-commands.ts +12 -4
  372. package/templates/gitignore +6 -2
  373. package/dist/hooks/chunk-D5QBTOCZ.js +0 -68
  374. package/dist/hooks/chunk-UT7YC3N2.js +0 -56
@@ -1,3 +1,7 @@
1
+ import { cost_blocks } from '#scripts/cost-runtime/cost-blocks'
2
+ import { json_value } from '#scripts/lib/json-value'
3
+ import type { GuardedCall } from '#scripts/time-runtime/time-batch-guard'
4
+ import { time_shell } from '#scripts/time-runtime/time-shell'
1
5
  import { bash_triggers } from './bash-triggers'
2
6
  import { gh_api } from './gh-api'
3
7
  import { prior_comment_read } from './prior-comment-read'
@@ -77,6 +81,9 @@ function reads_issue_comments(command: string): boolean {
77
81
  return shell_segments.segments_of(command).some((segment) => fetches_issue_comments(segment))
78
82
  }
79
83
 
84
+ const RULE_DOC = '`.claude/skills/workflow-commands/issue-comments.md`'
85
+ const COMMENTS_FALLBACK = 'gh api repos/{owner}/{repo}/issues/<N>/comments'
86
+
80
87
  // **The refusal hands over the command that fixes it**, because reading is not the same as
81
88
  // obeying: a sentence saying "also read the comments" is prose that moves nothing, while a refused
82
89
  // body read leaves the run holding the reissue that makes the comments *present*. The conflict rule
@@ -88,22 +95,62 @@ function reads_issue_comments(command: string): boolean {
88
95
  // being the Issue the refused call named — so a run that reads only the opening still leaves holding
89
96
  // the fix. The label keeps `time_transcript_line.guard_from_refusal` naming this guard, not the command.
90
97
  const ISSUE_COMMENTS_REASON =
91
- '⛔ issue comments: pnpm josh issue:read <N>\n' +
92
- "Run that in place of this read: <N> is the Issue it names, and an Issue's comments are part of " +
93
- 'the Issue, so the body and every comment come back together. Without josh, add ' +
94
- "`gh api repos/{owner}/{repo}/issues/<N>/comments --jq '.[] | {user: .user.login, created_at, " +
95
- "body}'` beside the body read, or `gh issue view <N> --comments` where GraphQL is reachable.\n" +
96
- 'Why: a decision recorded after the body was written lives only in a comment — a corrected ' +
97
- 'diagnosis (joshuafolkken/kit#1537), a changed default and an added acceptance criterion ' +
98
- '(joshuafolkken/kit#1520), a scope handed to another Issue (joshuafolkken/kit#1304) — and ' +
99
- 'nothing in the body says it was superseded, so a body-only reader builds the wrong thing and ' +
100
- 'sees no contradiction. ' +
101
- 'Then the later text is the agreement in force — a comment supersedes the body it contradicts — ' +
102
- "except for two answers that are not the run's to make: work a comment reassigns to another " +
103
- 'Issue is out of scope and is not implemented, and a comment saying the Issue no longer has a ' +
104
- 'reason to exist stops the run with a `confirmation` Telegram. The procedure is ' +
105
- '`.claude/skills/workflow-commands/issue-comments.md`. ' +
106
- 'Every body-only read is refused until the comments are read.'
98
+ `⛔ issue comments: pnpm josh issue:read <N>\n` +
99
+ `Run that in place of this read — an Issue's comments are part of it, and a later comment ` +
100
+ `supersedes the body it contradicts; refused until that command is on the transcript. Without ` +
101
+ `josh, read \`${COMMENTS_FALLBACK}\` beside the body (${RULE_DOC}).`
102
+
103
+ // **A `gh api` body read is rewritten rather than refused** (joshuafolkken/kit#3920): the fix is the
104
+ // same Issue's comments endpoint, read on the same line, and the path the call already names is all
105
+ // it needs. Prepended on lines of their own, so a trailing comment or a `&&` chain on the typed line
106
+ // cannot swallow or skip them, and the typed line stays last — its exit status is still the call's.
107
+ // A `gh issue view` read is left to the refusal — its global flags (`--repo`) and its `--json`
108
+ // projection have no one comments spelling to add.
109
+ const COMMENTS_JQ = String.raw`'.[] | "— \(.user.login) \(.created_at)\n\(.body)\n"'`
110
+
111
+ const COMMENTS_NOTE =
112
+ "↻ issue comments: this read returned only the Issue's body, so the Issue's comments were fetched " +
113
+ `on the same line — a later comment supersedes the body it contradicts (${RULE_DOC}).`
114
+
115
+ // **Only a line that opens with the body read is rewritten.** Prepended ahead of a `cd` or a
116
+ // subshell, the comments read would resolve `{owner}/{repo}` in another directory and print another
117
+ // repository's comments; and the line is cut on its quote-blanked reading, so a body-read spelling
118
+ // quoted in a string or a heredoc body is never prepended as a real call. Every other line keeps the
119
+ // refusal.
120
+ function rewritable_path(command: string): string | undefined {
121
+ const read = shell_segments.first_of(command)
122
+
123
+ if (read === undefined || ISSUE_VIEW_COMMAND.test(read)) return undefined
124
+
125
+ return is_body_read_segment(read) ? ISSUE_BODY_PATH.exec(read)?.[0] : undefined
126
+ }
127
+
128
+ function comments_command(command: string): string | undefined {
129
+ if (!is_body_only_issue_read(command)) return undefined
130
+
131
+ const path = rewritable_path(command)
132
+
133
+ if (path === undefined) return undefined
134
+
135
+ return `gh api --paginate ${path}/comments --jq ${COMMENTS_JQ}\n${command}`
136
+ }
137
+
138
+ // The call's input with the comments read prepended, or `undefined` where the comments are already on
139
+ // the tail or the read has no rewritable spelling.
140
+ function comments_input(
141
+ call: GuardedCall,
142
+ tail: () => string,
143
+ ): Record<string, unknown> | undefined {
144
+ if (call.name !== cost_blocks.BASH_TOOL || !json_value.is_record(call.input)) return undefined
145
+
146
+ const command = comments_command(time_shell.bash_command(call.input))
147
+
148
+ if (command === undefined || prior_comment_read.already_read_for_call(tail(), call)) {
149
+ return undefined
150
+ }
151
+
152
+ return { ...call.input, command }
153
+ }
107
154
 
108
155
  // The enumeration row itself, so `delivered-rules.ts` spreads one entry. `already_satisfied` stands it
109
156
  // down once the comments are on the transcript tail, and until then it refuses every body-only read.
@@ -113,13 +160,17 @@ const ROW = {
113
160
  reason: ISSUE_COMMENTS_REASON,
114
161
  already_satisfied: prior_comment_read.already_read_for_call,
115
162
  keeps: bash_triggers.on_bash_command(reads_issue_comments),
163
+ rewrite: comments_input,
164
+ rewrite_note: COMMENTS_NOTE,
116
165
  // The opening the reason carried before it led with the command.
117
166
  former_reasons: ["⛔ an Issue's comments are part of the Issue: read them before implementing"],
118
167
  }
119
168
 
120
169
  const issue_comments = {
170
+ COMMENTS_NOTE,
121
171
  ISSUE_COMMENTS_REASON,
122
172
  ROW,
173
+ comments_input,
123
174
  is_body_only_issue_read,
124
175
  }
125
176
 
@@ -52,12 +52,9 @@ function is_bare_josh_git(command: string): boolean {
52
52
  // sibling reasons do.
53
53
  const JOSH_GIT_BARE_REASON =
54
54
  '⛔ bare `pnpm josh git`: this prompts to confirm the staging, and with no TTY the prompt falls to ' +
55
- 'its cancel branch — `Checking unstaged files... Found` then `Operation cancelled.` — so the call ' +
56
- 'throws away the time it takes to fail (about 31 seconds in one measured lane) and you reissue it ' +
57
- 'with `-y` anyway (joshuafolkken/kit#2297). Run it unattended from the start: ' +
58
- '`pnpm josh git -y "<title> #<N>"`. The recovery form that opens the PR for a push that already ' +
59
- 'landed is `pnpm josh git -y --skip-commit --skip-push`. This rule fires on every occurrence, not ' +
60
- 'once per run.'
55
+ 'its cancel branch. Run `pnpm josh git -y "<title> #<N>"`, or ' +
56
+ '`pnpm josh git -y --skip-commit --skip-push` to open the PR for a push that already landed. It ' +
57
+ 'fires on every occurrence.'
61
58
 
62
59
  // The row itself, so `delivered-rules.ts` spreads one entry. `is_trigger` reads the input through
63
60
  // `on_bash_command` — the `Bash` tool-name gate every command row shares — and `decide` returns true so
@@ -109,9 +109,9 @@ function foreground_input(
109
109
  call: GuardedCall,
110
110
  is_lane_child: () => boolean = () => lane_child_marker.is_child_of(process.cwd()),
111
111
  ): Record<string, unknown> | undefined {
112
- if (!is_backgrounded_ship(call) || !json_value.is_record(call.input)) return undefined
113
-
114
- if (!is_lane_child()) return undefined
112
+ if (!is_backgrounded_ship(call) || !json_value.is_record(call.input) || !is_lane_child()) {
113
+ return undefined
114
+ }
115
115
 
116
116
  return { ...call.input, [BACKGROUND_KEY]: false, [TIMEOUT_KEY]: FOREGROUND_TIMEOUT_MS }
117
117
  }
@@ -119,7 +119,7 @@ function foreground_input(
119
119
  const FOREGROUND_NOTE =
120
120
  'lane background: this lane child backgrounded `josh ship`, which would die with the headless turn ' +
121
121
  'before its supervisor exists — so it was run in the foreground instead, where it detaches itself ' +
122
- 'after the preflight. End the turn on `launched` / `busy` (joshuafolkken/kit#3154).'
122
+ 'after the preflight. End the turn on `launched` / `busy`.'
123
123
 
124
124
  // The launches `launched_ids` reads off the transcript tail that never saw their finish. A launch is
125
125
  // read off its own tool result and a finish off the harness notice, both by `time_transcript_line`.
@@ -149,26 +149,15 @@ function pending_agent_ids(tail: string): ReadonlyArray<string> {
149
149
 
150
150
  const LANE_BACKGROUND_REASON =
151
151
  '⛔ lane background: this session is a dispatched lane child (`JOSH_LANE_CHILD`), a headless ' +
152
- '`claude -p` process whose background Bash tasks are killed when the turn ends — so a backgrounded ' +
153
- '`josh gate` / `josh git` / `josh followup` / `josh ship` never re-invokes you; it dies at `exit code ' +
154
- '143` (joshuafolkken/kit#2704, measured on the #2606 child: implementation, review and live ' +
155
- 'verification done, the run parked instead of merged). Hand the gate-to-merge region to the detached ' +
156
- 'supervisor instead: `pnpm josh ship --detach --review "<title> #<N>"` (add `--cite <N>`) issued in ' +
157
- 'the foreground — its preflight runs in this process before the supervisor exists, so a backgrounded ' +
158
- '`ship` dies with the turn (joshuafolkken/kit#3027; a backgrounded `ship` issued alone is moved to ' +
159
- 'the foreground for you, joshuafolkken/kit#3154) — then end the turn on `launched` / `busy`. ' +
160
- '`.claude/skills/workflow-commands/chain-rule.md` step 0 is the single source. A ' +
161
- 'single check you must read before continuing runs in the foreground within the tool timeout; the ' +
162
- 'commit-push step does not — in a child it too belongs to the detached ship, in either spelling. This ' +
163
- 'rule fires on every occurrence, not once per run.'
152
+ 'process whose background tasks die when the turn ends. Hand over to ' +
153
+ '`pnpm josh ship --detach --review "<title> #<N>"`, issued in the foreground, then end the turn on ' +
154
+ '`launched` / `busy`. It fires on every occurrence ' +
155
+ '(`.claude/skills/workflow-commands/chain-rule.md` step 0).'
164
156
 
165
157
  const LANE_BACKGROUND_STOP_REASON =
166
158
  '⛔ lane background stop: this is a dispatched lane child (`JOSH_LANE_CHILD`) and a background task ' +
167
- 'it launched has not finished — ending this headless turn kills it, and nothing re-invokes you ' +
168
- 'afterwards (joshuafolkken/kit#2704). Do not send a notification and do not stop: wait for the task ' +
169
- 'in the foreground and act on its result, or re-issue the work through `pnpm josh ship --detach`, ' +
170
- 'whose supervisor outlives the turn (`.claude/skills/workflow-commands/chain-rule.md` step 0). Do ' +
171
- 'not repeat your previous reply.'
159
+ 'it launched has not finished, and ending this turn kills it. Wait for it in the foreground, or ' +
160
+ 're-issue the work through `pnpm josh ship --detach` — do not notify, stop or repeat your reply.'
172
161
 
173
162
  // The row itself, so `delivered-rules.ts` spreads one entry. It fires on every occurrence (`decide`
174
163
  // returns true): a child must never background these, so the route is always the detached ship, never
@@ -89,25 +89,15 @@ function is_carry_conflict(
89
89
  }
90
90
 
91
91
  // The instruction in the shape a refusal can carry: what the child cannot do, why the record is not a
92
- // conflict, and the mechanical branch to take instead — continue, never park or ask. **The procedure
93
- // is named, not restated** — its single sources are `pre-gate-cut.md` and `backlogrun-progress.md` →
94
- // "The hand-off", and a second copy here would be the clone `CLAUDE.md` prohibits. Apostrophes are
95
- // avoided so the single-quoted literal needs no escaping, as the sibling reasons do.
92
+ // conflict, and the mechanical branch to take instead — continue, never park or ask. Its history is
93
+ // `docs/maintainers/rule-delivery-rationale.md` → "Why a refusal is the action and the next command".
94
+ // Apostrophes are avoided so the single-quoted literal needs no escaping, as the sibling reasons do.
96
95
  const LANE_CARRY_CONFLICT_REASON =
97
96
  '⛔ lane child carry conflict: this session is a dispatched lane child, and ' +
98
- '`pnpm josh run:merge` / `pnpm josh run:carry` are the parent backlogrun budget commands — a child ' +
99
- 'must never run them. The live carry record here belongs to the parent that launched this child, ' +
100
- 'not to a competing run: the parent holds the one run budget for the repository, and every lane ' +
101
- 'shares the common git directory it is keyed on, so a live owner a lane child reads is always its ' +
102
- 'parent (joshuafolkken/kit#2267, measured 2026-09-21 in backlogrun #2252 — a child read run:merge ' +
103
- '`busy` as a competitor and parked #2258 unimplemented). Do not run this command, and do not treat ' +
104
- 'it as a conflict: no `needs-decision`, no `confirmation` Telegram, no question to a person — a ' +
105
- 'dispatched child cannot ask, and there is nothing to decide. Continue implementing this Issue; the ' +
106
- 'parent owns the budget and counts the outcome at the return. The procedure is ' +
107
- '`.claude/skills/workflow-commands/pre-gate-cut.md` and `backlogrun-progress.md` → "The hand-off". ' +
108
- 'Live evidence of `run:carry` is taken with a fresh `JOSH_TEMP_ROOT` prefixed to the call, which this ' +
109
- 'rule lets through (joshuafolkken/kit#3458); `run:merge` is refused under any root, because it also ' +
110
- 'syncs main, closes lanes and writes to GitHub. This rule fires on every occurrence, not once per run.'
97
+ '`run:merge` / `run:carry` are the parent budget commands; the live record belongs to the parent, ' +
98
+ 'not to a competing run. Continue implementing this Issue: no `needs-decision`, no Telegram. ' +
99
+ 'Live `run:carry` evidence takes a fresh `JOSH_TEMP_ROOT`; `run:merge` is refused under any root, ' +
100
+ 'on every occurrence.'
111
101
 
112
102
  // The row itself, so `delivered-rules.ts` spreads one entry. `is_trigger` reads the input through
113
103
  // `on_bash_command` — the `Bash` tool-name gate every command row shares — and `decide` returns true
@@ -41,17 +41,12 @@ function is_interactive_ask(
41
41
  // `backlogrun-park.md` → "park and continue", and a second copy here would be the clone `CLAUDE.md`
42
42
  // prohibits. The one command is spelled so the child is not left to reconstruct it from a doc read.
43
43
  const LANE_INTERACTIVE_ASK_REASON =
44
- '⛔ lane child interactive ask: this session is a dispatched lane child and cannot ask a person, so ' +
45
- 'this call is refused. Left to run it would end the turn with nothing on the Issue — the parent then ' +
46
- 'finds an OPEN Issue with `in-progress` and no question (joshuafolkken/kit#2201; joshuafolkken/kit#2034 ' +
47
- 'placed the same rule one tool-call too late). Do not ask: park the decision instead. Apply ' +
48
- '`needs-decision` and post a comment carrying the question, the options and whether work was stashed — ' +
49
- "`gh api repos/{owner}/{repo}/issues/<N>/labels -f 'labels[]=needs-decision'` then " +
50
- '`pnpm josh issue:comment <N> --body-file <path>` — then send the ' +
51
- '`confirmation` Telegram and stop. The procedure is ' +
52
- '`.claude/skills/workflow-commands/pre-gate-cut.md` → "A lane child records its park before it stops", ' +
53
- 'and the park itself is `backlogrun-park.md` → "park and continue". This rule fires on every ' +
54
- 'occurrence, not once per run.'
44
+ '⛔ lane child interactive ask: this session is a dispatched lane child and cannot ask a ' +
45
+ 'person, so park instead: ' +
46
+ "`gh api repos/{owner}/{repo}/issues/<N>/labels -f 'labels[]=needs-decision'`, " +
47
+ '`pnpm josh issue:comment <N> --body-file <path>`, ' +
48
+ 'the `confirmation` Telegram, stop. It fires on every occurrence ' +
49
+ '(`.claude/skills/workflow-commands/pre-gate-cut.md`, `backlogrun-park.md`).'
55
50
 
56
51
  // The row itself, so `delivered-rules.ts` spreads one entry. `decide` returns true so it refuses every
57
52
  // occurrence; it declares no `keeps` — not asking is the absence of a call, not a call, so the row is
@@ -108,19 +108,11 @@ function records_the_park(command: string): boolean {
108
108
  // single source is `backlogrun-park.md` → "park and continue", and a second copy here would be the
109
109
  // clone `CLAUDE.md` prohibits.
110
110
  const LANE_PARK_REASON =
111
- '⛔ lane child park: this session is a dispatched lane child, and a stop that waits on a person must ' +
112
- 'leave the question on the Issue before it sends this Telegram — otherwise the parent finds an OPEN ' +
113
- 'Issue with `in-progress` and no question, and a person cannot tell what to answer ' +
114
- '(joshuafolkken/kit#2034 measured this twice in one run). The child is the only process that knows ' +
115
- 'the question, the options and whether it stashed, so it records the park itself rather than leaving ' +
116
- 'the parent to guess from a log. Before you notify and stop: apply `needs-decision` and post a ' +
117
- 'comment carrying the question, the options, and whether work was stashed — ' +
118
- "`gh api repos/{owner}/{repo}/issues/<N>/labels -f 'labels[]=needs-decision'` then " +
119
- '`pnpm josh issue:comment <N> --body-file <path>`. If this stop is a ' +
120
- '`needs-human-review` or `already-done` one, that label is already on the Issue and there is nothing ' +
121
- 'to add. Then reissue this notify — it fires once per run and cannot repeat on the call in hand. The ' +
122
- 'procedure is `.claude/skills/workflow-commands/pre-gate-cut.md` → "A lane child records its park ' +
123
- 'before it stops", and the park itself is `backlogrun-park.md` → "park and continue".'
111
+ '⛔ lane child park: this session is a dispatched lane child, so a stop for a person parks first: ' +
112
+ "`gh api repos/{owner}/{repo}/issues/<N>/labels -f 'labels[]=needs-decision'`; " +
113
+ '`pnpm josh issue:comment <N> --body-file <path>` (question, options, stash); a ' +
114
+ '`needs-human-review` / `already-done` stop already is. Reissue this notify, once per run ' +
115
+ '(`.claude/skills/workflow-commands/pre-gate-cut.md`).'
124
116
 
125
117
  // The row itself, so `delivered-rules.ts` spreads one entry. **Once per run rather than `decide`, and
126
118
  // no `already_satisfied`.** The stop ends the turn, so it is not the recurring act `decide` is for. It carries no `already_satisfied` because the park it asks for is a GitHub label a
@@ -101,13 +101,8 @@ function decide(_call: unknown, run: GuardRun): boolean {
101
101
 
102
102
  const LANE_SPLIT_PARK_REASON =
103
103
  '⛔ lane split park: this session is a dispatched lane child that promoted its own Issue to an epic ' +
104
- 'this run, and a split is not a park — do not apply `needs-decision` to the promoted epic and do not ' +
105
- 'send a `confirmation` Telegram. The parent reads an open Issue carrying `epic` as a split and ' +
106
- 'continues through its children; a question here has nobody to answer it (joshuafolkken/kit#3296, ' +
107
- 'measured 2026-10-06 when the child for #3255 parked its promoted epic). If what remains of the ' +
108
- 'original work needs a person, park that child issue instead, then end the turn. The procedure is ' +
109
- '`.claude/skills/workflow-commands/backlogrun-park.md` → "Splitting a child mid-run". This rule fires ' +
110
- 'on every occurrence, not once per run.'
104
+ 'this run, and a split is not a park: no `needs-decision` on the epic, no `confirmation` Telegram, ' +
105
+ 'on every occurrence. Park a child issue if the rest needs a person, else end the turn (`.claude/skills/workflow-commands/backlogrun-park.md` → "Splitting a child mid-run").'
111
106
 
112
107
  // It declares no `keeps`: not parking is the absence of a call, so the row is reported unmeasured
113
108
  // rather than scored on an act that does not exist (`lane-carry-conflict.ts`).
@@ -61,8 +61,12 @@ const FLAG_PREFIX = '-'
61
61
  function switch_target(segment: string): string | undefined {
62
62
  const call = git_argv.parse(segment)
63
63
 
64
- if (call?.subcommand !== SWITCH_SUBCOMMAND) return undefined
65
- if (call.args.some((argument) => NON_COLLIDING_FLAGS.has(argument))) return undefined
64
+ if (
65
+ call?.subcommand !== SWITCH_SUBCOMMAND ||
66
+ call.args.some((argument) => NON_COLLIDING_FLAGS.has(argument))
67
+ ) {
68
+ return undefined
69
+ }
66
70
 
67
71
  return call.args.find((argument) => !argument.startsWith(FLAG_PREFIX))
68
72
  }
@@ -103,16 +107,9 @@ function is_switch_away_from_lane(
103
107
  // prohibits. Apostrophes are avoided so the single-quoted literal needs no escaping, as the siblings do.
104
108
  const LANE_SWITCH_MAIN_REASON =
105
109
  '⛔ lane child on `git switch main`: this session is a dispatched lane child, and switching to the ' +
106
- 'default branch here always fails — a lane is a linked work tree, the primary checkout holds the ' +
107
- 'default branch, and git allows one branch in one work tree at a time (`fatal: main is already used ' +
108
- 'by worktree at <the primary checkout>`, measured 11 times in the 2026-09-21 backlogrun). Do not ' +
109
- 'switch: the lane is already on its own issue branch, branched from a fresh default before it opened, ' +
110
- 'and the latest default is brought in during the gate by `pnpm josh main:merge`. So skip this step ' +
111
- 'and keep implementing on the current branch — no `git switch`, no `git pull`. `pnpm josh main:sync` ' +
112
- '(`josh ms`) is refused in a lane for the same reason, and a lane is finished with `pnpm josh ' +
113
- 'lane:close <issue-number>`. The parent runs `pnpm josh ms` in the primary checkout ' +
114
- 'before each lane opens, never the child — `backlogrun-lanes.md` and `backlogrun-child.md` are the ' +
115
- 'single sources. This rule fires on every occurrence, not once per run.'
110
+ 'default branch here always fails (`already used by worktree`: the primary checkout holds it). Skip ' +
111
+ 'this step and keep implementing on the current branch; `pnpm josh main:merge` brings the default ' +
112
+ 'in during the gate. Refused on every occurrence (`backlogrun-lanes.md`, `backlogrun-child.md`).'
116
113
 
117
114
  // The row itself, so `delivered-rules.ts` spreads one entry. `is_trigger` reads the input through
118
115
  // `on_bash_command` — the `Bash` tool-name gate every command row shares — and `decide` returns true so
@@ -30,10 +30,13 @@ const KEYWORD_BEFORE_ARGUMENT = -2
30
30
 
31
31
  const PROMPT_LINE_SCHEMA = z.object({
32
32
  isMeta: z.boolean().nullish(),
33
+ timestamp: z.string().nullish(),
33
34
  origin: z.object({ kind: z.string().nullish() }).nullish(),
34
35
  message: z.object({ content: z.unknown() }),
35
36
  })
36
37
 
38
+ const TIMED_LINE_SCHEMA = z.object({ timestamp: z.string() })
39
+
37
40
  type PromptLine = z.infer<typeof PROMPT_LINE_SCHEMA>
38
41
 
39
42
  function is_person(line: PromptLine): boolean {
@@ -42,13 +45,20 @@ function is_person(line: PromptLine): boolean {
42
45
  return line.origin === null || line.origin === undefined || line.origin.kind === HUMAN_ORIGIN
43
46
  }
44
47
 
45
- // The prompt line's text, or `undefined` when the harness wrote it or it does not read as one.
46
- function person_text(line: string): string | undefined {
48
+ // The prompt line as the person wrote it, or `undefined` when the harness wrote it or it does not
49
+ // read as one.
50
+ function person_line(line: string): PromptLine | undefined {
47
51
  const parsed = PROMPT_LINE_SCHEMA.safeParse(json_value.parse_or_undefined(line))
48
52
 
49
- if (!parsed.success || !is_person(parsed.data)) return undefined
53
+ return parsed.success && is_person(parsed.data) ? parsed.data : undefined
54
+ }
50
55
 
51
- return time_reported_failure.result_text(parsed.data.message.content)
56
+ // The prompt line's text, or `undefined` when the harness wrote it or it does not read as one.
57
+ function person_text(line: string): string | undefined {
58
+ const person = person_line(line)
59
+ if (person === undefined) return undefined
60
+
61
+ return time_reported_failure.result_text(person.message.content)
52
62
  }
53
63
 
54
64
  // A keyword opening the prompt is what a person types; a keyword before its one trailing argument is a
@@ -75,6 +85,41 @@ function prompt_text(tail: string): string {
75
85
  return text === undefined || is_invocation(text) ? NO_PROMPT : text
76
86
  }
77
87
 
78
- const last_prompt = { prompt_text }
88
+ // **The prompt that opened this turn, when a person typed it** — '' when the harness opened it (a task
89
+ // notification, a subagent's hand-back), so a question asked turns ago does not answer for a stop the
90
+ // run reached on its own since (`stop-decision.ts` → `is_question`).
91
+ function turn_prompt(tail: string): string {
92
+ const opener = tail.split('\n').findLast((line) => filing_cap.is_prompt_line(line))
93
+ if (opener === undefined) return NO_PROMPT
94
+
95
+ return person_text(opener) ?? NO_PROMPT
96
+ }
97
+
98
+ // When the person last prompted on the tail, or `undefined` when no prompt of theirs is on it.
99
+ function prompt_time(tail: string): string | undefined {
100
+ return tail
101
+ .split('\n')
102
+ .filter((line) => filing_cap.is_prompt_line(line))
103
+ .map((line) => person_line(line)?.timestamp ?? undefined)
104
+ .findLast((time) => time !== undefined)
105
+ }
106
+
107
+ // The first dated line on the tail, or `undefined` when none is.
108
+ function tail_time(tail: string): string | undefined {
109
+ return tail
110
+ .split('\n')
111
+ .map((line) => TIMED_LINE_SCHEMA.safeParse(json_value.parse_or_undefined(line)).data?.timestamp)
112
+ .find((time) => time !== undefined)
113
+ }
114
+
115
+ // **When the current pause began, as far as the tail shows** — what a recorded notify is compared
116
+ // against. A person's prompt ends the pause it reported, so the last one opens the next; with none on
117
+ // the tail, only a send dated inside the tail is provably after every prompt, and an older send is
118
+ // announced again (joshuafolkken/kit#3961).
119
+ function pause_start(tail: string): string | undefined {
120
+ return prompt_time(tail) ?? tail_time(tail)
121
+ }
122
+
123
+ const last_prompt = { pause_start, prompt_text, turn_prompt }
79
124
 
80
125
  export { last_prompt }
@@ -48,11 +48,10 @@ function command_line(oracle: Oracle): string {
48
48
  // the single source of the procedure.
49
49
  function reason_for(oracle: Oracle, firing_point: FiringPoint): string {
50
50
  return (
51
- `⛔ decision oracle not consulted: this is ${firing_point.describes}, but the run has not run ` +
52
- `\`${command_line(oracle)}\` first — the oracle that decides ${oracle.decision.toLowerCase()}. ` +
53
- `It answers ${oracle.vocabulary.join(' | ')}; single source ${oracle.single_source}. Reissue ` +
54
- `after running it and reading its verdict. Every such call is refused until that command is on ` +
55
- `the transcript.`
51
+ `⛔ decision oracle not consulted: this is ${firing_point.describes}. Run ` +
52
+ `\`${command_line(oracle)}\` first — it decides ${oracle.decision.toLowerCase()} and answers ` +
53
+ `${oracle.vocabulary.join(' | ')} (${oracle.single_source}). Every such call is refused until ` +
54
+ `that command is on the transcript.`
56
55
  )
57
56
  }
58
57
 
@@ -12,6 +12,10 @@ import { oracle_firing } from './oracle-firing'
12
12
  const ARGV_OFFSET = 2
13
13
  const INDENT = ' '
14
14
  const FIELD_WIDTH = 10
15
+ // The rule-prose guard sends a rule author here first, so the list closes on the placement order a new
16
+ // rule is weighed against before it becomes a guard or a document line.
17
+ const AUTOMATION_ORDER =
18
+ 'automate first: a command > a hook that runs it > a refusal guard > a document — a lower rung states why the higher cannot hold (`principles.md` → "automate-first"; a guard\'s `why` is `scripts/rules/guard-remedy.ts`)'
15
19
 
16
20
  function pad_field(label: string): string {
17
21
  return label.padEnd(FIELD_WIDTH)
@@ -51,6 +55,8 @@ function print_list(): void {
51
55
  for (const oracle of decision_oracle.DECISION_ORACLES) {
52
56
  console.info(format_oracle(oracle))
53
57
  }
58
+
59
+ console.info(AUTOMATION_ORDER)
54
60
  }
55
61
 
56
62
  function run(argv: ReadonlyArray<string>): number {
@@ -70,6 +76,7 @@ function main(argv: ReadonlyArray<string>): void {
70
76
  }
71
77
 
72
78
  const oracle_list_cli = {
79
+ AUTOMATION_ORDER,
73
80
  print_list,
74
81
  }
75
82