@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
@@ -2,10 +2,13 @@ import { git_gh_command } from '#scripts/gh/git-gh-command'
2
2
  import { issue_cite } from '#scripts/issue/issue-cite'
3
3
  import { josh_command, type JoshResult } from '#scripts/josh/josh-run'
4
4
  import { lane_capacity } from '#scripts/lane/lane-capacity'
5
- import { lane_registry } from '#scripts/lane/lane-registry'
5
+ import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
6
+ import { lane_spent } from '#scripts/lane/lane-spent'
6
7
  import type { RunCarry } from '#scripts/run/carry/run-carry'
8
+ import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
7
9
  import { run_headless } from '#scripts/run/run-headless'
8
10
  import { run_invocation } from '#scripts/run/run-invocation'
11
+ import { backlog_occupied, type OccupiedRead } from './backlog-occupied'
9
12
  import { backlog_stalled } from './backlog-stalled'
10
13
 
11
14
  // The ready line reports runnable work to the driving backlogrun parent on a watcher wake.
@@ -18,18 +21,60 @@ interface ReadyReading {
18
21
  free_lanes: number
19
22
  }
20
23
 
24
+ // How many open lanes hold a place under the limit, and how many hold a port seat.
25
+ interface LaneUse {
26
+ occupied: number
27
+ seated: number
28
+ }
29
+
21
30
  interface ReadyPorts {
22
31
  free_lane_count: () => Promise<number>
23
32
  ready_issues: () => Promise<ReadonlyArray<string>>
24
33
  }
25
34
 
26
- // The lanes in use: the open worktrees an interruption has not stranded. `josh lane:limit` prints it.
27
- async function live_lane_count(): Promise<number> {
28
- return lane_registry.live_count(await lane_registry.list_lanes())
35
+ // What decides which open lanes hold a place, read from this checkout's live run.
36
+ async function occupied_read(): Promise<OccupiedRead> {
37
+ const carry = await run_headless.current_carry()
38
+
39
+ return {
40
+ invocation: carry?.invocation,
41
+ events: carry === undefined ? [] : await run_event_stream_emit.scoped_current_events(),
42
+ idle: lane_spent.idle_lanes,
43
+ }
44
+ }
45
+
46
+ // The lanes that hold a place under the limit (`backlog-occupied.ts`).
47
+ async function occupied_lanes(lanes?: ReadonlyArray<LaneInfo>): Promise<Array<LaneInfo>> {
48
+ const open = lanes ?? (await lane_registry.list_lanes())
49
+
50
+ return backlog_occupied.occupied_lanes(open, await occupied_read())
51
+ }
52
+
53
+ // The open lanes as the free count reads them: those that hold a place under the limit, and every
54
+ // open one, since an uncounted lane still holds its port seat.
55
+ async function lane_use(lanes?: ReadonlyArray<LaneInfo>): Promise<LaneUse> {
56
+ const open = lanes ?? (await lane_registry.list_lanes())
57
+ const occupied = await occupied_lanes(open)
58
+
59
+ return { occupied: occupied.length, seated: open.filter((lane) => !lane.is_stranded).length }
60
+ }
61
+
62
+ function free_of(limit: number, use: LaneUse): number {
63
+ return lane_capacity.free_lanes(limit, use.occupied, use.seated)
64
+ }
65
+
66
+ // Free lanes beside `lanes`, `undefined` where the limit could not be read: the count `lane:limit`
67
+ // prints, and the one `run:board` draws, so the two never answer apart.
68
+ async function free_lanes_beside(lanes: ReadonlyArray<LaneInfo>): Promise<number | undefined> {
69
+ const limit = await lane_capacity.lane_limit()
70
+
71
+ if (limit.kind !== 'limit') return undefined
72
+
73
+ return free_of(limit.limit, await lane_use(lanes))
29
74
  }
30
75
 
31
76
  async function free_under(limit: number): Promise<number> {
32
- return lane_capacity.free_lanes(limit, await live_lane_count())
77
+ return free_of(limit, await lane_use())
33
78
  }
34
79
 
35
80
  // Free lanes right now: the limit less the live lanes. An unreadable limit reads as no free lane — the
@@ -38,15 +83,12 @@ async function free_under(limit: number): Promise<number> {
38
83
  //
39
84
  // **Occupancy is the local worktree count, not the dispatch budget's busy read.** `epic-lane-offer`
40
85
  // measures occupancy with `epic_busy.occupied_lanes`, a network read of in-progress issue labels; this
41
- // counts live worktrees instead, deliberately — it runs on the cheap-first path where the costly read
42
- // (`backlog:next`) is gated behind a free lane, so a network read here would defeat that design. The
43
- // two can diverge, but only toward over-counting live lanes → fewer free lanes, the safe direction.
86
+ // counts the worktrees that hold a place instead (`backlog-occupied.ts`), deliberately — it runs on the
87
+ // cheap-first path where the costly read (`backlog:next`) is gated behind a free lane, so a network
88
+ // read here would defeat that design. The two can diverge, but only toward over-counting live lanes →
89
+ // fewer free lanes, the safe direction.
44
90
  async function free_lane_count(): Promise<number> {
45
- const limit = await lane_capacity.lane_limit()
46
-
47
- if (limit.kind !== 'limit') return NO_FREE
48
-
49
- return await free_under(limit.limit)
91
+ return (await free_lanes_beside(await lane_registry.list_lanes())) ?? NO_FREE
50
92
  }
51
93
 
52
94
  // **The drive's count throws on an unreadable limit**. The drive launches no
@@ -165,7 +207,9 @@ const backlog_ready = {
165
207
  read_backlog_next,
166
208
  print_offer_hint,
167
209
  free_lane_count,
168
- live_lane_count,
210
+ free_lanes_beside,
211
+ lane_use,
212
+ occupied_lanes,
169
213
  drive_free_lane_count,
170
214
  drains_pool,
171
215
  print_ready_line,
@@ -175,4 +219,4 @@ const backlog_ready = {
175
219
  }
176
220
 
177
221
  export { backlog_ready }
178
- export type { ReadyPorts, ReadyReading }
222
+ export type { LaneUse, ReadyPorts, ReadyReading }
@@ -53,8 +53,9 @@ const INDEX_ASSERTION_NAME = 'no-direct-git-index-mutation'
53
53
  // a tool result, or a call to any tool but `Bash`. Read through `json_value.is_record` so a malformed
54
54
  // input is treated as absent rather than throwing mid-scan.
55
55
  function command_of(block: Block): string | undefined {
56
- if (block.type !== TOOL_USE || block.name !== BASH_TOOL) return undefined
57
- if (!json_value.is_record(block.input)) return undefined
56
+ if (block.type !== TOOL_USE || block.name !== BASH_TOOL || !json_value.is_record(block.input)) {
57
+ return undefined
58
+ }
58
59
 
59
60
  const command = block.input[COMMAND_KEY]
60
61
 
@@ -56,8 +56,10 @@ function measurement_from(
56
56
  return parsed.success ? [parsed.data.payload.info] : []
57
57
  })
58
58
  const session = metadata.find((entry) => entry.payload.id === thread_id)
59
- if (session === undefined || usages.length === 0) return undefined
60
- if (!same_project(session.payload.cwd, target)) return undefined
59
+
60
+ if (session === undefined || usages.length === 0 || !same_project(session.payload.cwd, target)) {
61
+ return undefined
62
+ }
61
63
 
62
64
  return { billed_input_per_request: billed_per_request(usages) }
63
65
  }
@@ -56,8 +56,7 @@ interface RunContext {
56
56
  // `--over=` does not arrive as "hand off after any request" and stop an unattended run after its
57
57
  // first child.
58
58
  function to_threshold(raw: string | undefined): number | undefined {
59
- if (raw === undefined) return undefined
60
- if (raw.trim() === '') return undefined
59
+ if (raw === undefined || raw.trim() === '') return undefined
61
60
 
62
61
  const parsed = Number(raw)
63
62
 
@@ -176,8 +176,8 @@ function is_candidate(call: GuardedCall): boolean {
176
176
  }
177
177
 
178
178
  // A refusable call is refused only where the mode is `refuse`. A lane child is `notice`, so it reaches
179
- // here and is withheld the refusal — the notice branch carries the guidance instead, because a refusal
180
- // ends a headless child's turn.
179
+ // here and is withheld the refusal — the notice branch carries the guidance instead, for the reason its
180
+ // `because` in `lane-guard-policy.ts` records.
181
181
  function should_block_here(tail: string, call: GuardedCall, refused_at_ms: number): boolean {
182
182
  if (duplicate_mode() !== 'refuse') return false
183
183
 
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { text } from 'node:stream/consumers'
3
3
  import { fileURLToPath } from 'node:url'
4
+ import { agent_session_role } from '#scripts/agent/agent-session-role'
4
5
  import { hook_decision, type GuardRun } from '#scripts/josh/hook-decision'
5
- import { lane_guard_policy, type LaneGuardMode } from '#scripts/lane/lane-guard-policy'
6
+ import { lane_guard_policy } from '#scripts/lane/lane-guard-policy'
6
7
  import type { GuardedCall } from '#scripts/time-runtime/time-batch-guard'
7
8
  import { investigation_reads } from './investigation-reads'
8
9
 
@@ -21,91 +22,42 @@ import { investigation_reads } from './investigation-reads'
21
22
  // file — debugging the guard itself, or a session whose reading really is all edit targets.
22
23
  const SWITCH_ENV_KEY = 'JOSH_INVESTIGATION_GUARD'
23
24
 
24
- // This guard's refusal record, and the notice's own record — distinct so a lane-child notice never
25
- // spends the refusal's stamp, the same split the batching guard keeps.
26
25
  const STAMP_PREFIX = 'josh-investigation-guard-'
27
- const NOTICE_STAMP_PREFIX = 'josh-investigation-guard-notice-'
28
26
 
29
- // **How the guard behaves for the session in hand, decided from the one-place enumeration**.
30
- // Outside a lane child the mode is `refuse` and the
31
- // guard refuses; in a dispatched lane child it is `notice`, because a lane child does read unedited
32
- // files and can dispatch sub-agents, so the reading is there to send out. The enumeration reads `JOSH_LANE_CHILD` against this checkout's own
33
- // issue only for a guard whose mode it would change, so a person working in a lane sees the guard
34
- // unchanged. It wraps `investigation_reads.should_block` here rather than inside it so the pure counting
35
- // rule stays free of process state and its own suite keeps testing it directly.
36
- function investigation_mode(): LaneGuardMode {
37
- return lane_guard_policy.mode_here('investigation')
38
- }
39
-
40
- // A refusable call is refused only where the mode is `refuse` — the main line. A lane child is
41
- // `notice`, so it reaches the notice branch and is withheld the refusal that would end its headless
42
- // turn; an `off` some future enumeration sets stays silent here too.
27
+ // **How the guard behaves for the session in hand, decided from the one-place enumeration**. It is
28
+ // `refuse` in a dispatched lane child too (joshuafolkken/kit#3927): a hook denial returns to the model
29
+ // as guidance, and the notice it replaced was measured not to move the reading. It wraps
30
+ // `investigation_reads.should_block` here rather than inside it so the pure counting rule stays free of
31
+ // process state and its own suite keeps testing it directly. A reviewer session stands down: reading
32
+ // the diff's files is its whole task, and it carries the lane's marker, so it would otherwise be refused
33
+ // like the child it reviews — the stand-down `implementation-cut.ts` takes for the same session.
43
34
  function should_block(
44
35
  tail: string,
45
36
  call: GuardedCall,
46
37
  refused_at_ms: number,
47
38
  run?: GuardRun,
48
39
  ): boolean {
49
- if (investigation_mode() !== 'refuse') return false
40
+ if (
41
+ agent_session_role.is_reviewer() ||
42
+ lane_guard_policy.mode_here('investigation') !== 'refuse'
43
+ ) {
44
+ return false
45
+ }
50
46
 
51
47
  return investigation_reads.should_block(tail, call, refused_at_ms, run)
52
48
  }
53
49
 
54
- // The lane-child notice: the same threshold, delivered without a `permissionDecision`. It
55
- // reads the notify stamp's own record, so a notice never spends the refusal's stamp and re-arms on the
56
- // next accumulation exactly as the refusal does.
57
- function should_notify(
58
- tail: string,
59
- call: GuardedCall,
60
- notified_at_ms: number,
61
- run?: GuardRun,
62
- ): boolean {
63
- if (investigation_mode() !== 'notice') return false
64
-
65
- return investigation_reads.should_block(tail, call, notified_at_ms, run)
66
- }
67
-
68
- // **The lane-child counterpart of REASON, delivered as a notice rather than a refusal**.
69
- // A dispatched lane child ends its turn on a denial, so
70
- // the same guidance is carried without a `permissionDecision`: the read proceeds and the child is nudged
71
- // to delegate rather than killed. It carries no ⛔, so a person watching reads it as advice.
72
- const NOTICE = `💡 investigation: files read and not edited, or search turns, since the last delegated unit reached the threshold, so the reading from here should go to a unit of its own. This is a notice, not a refusal — the read proceeds, because a dispatched lane child ends its turn on a denial. Ask \`pnpm josh delegate investigation\`, then dispatch the \`${investigation_reads.INVESTIGATOR_AGENT}\` agent (\`kit:${investigation_reads.INVESTIGATOR_AGENT}\` in a consumer) with a brief of what the main line has already concluded and what is left to find out; it returns the conclusion plus its \`file:line\` citations, never the file text. Keep a read in the main line only where this run will edit that file. This recurs on the next accumulation.`
73
-
74
- // The notice as it reaches the model: the base guidance with the concrete unedited reads named, so a
75
- // lane child is shown *which* reads to send to a unit rather than only told to delegate. An empty
76
- // set names nothing and the notice falls back to its guidance alone. The call is part of the notify
77
- // contract but this guard's wording depends on the tail alone.
78
- function notice_text(_call: GuardedCall, tail: string): string {
79
- const pending = investigation_reads
80
- .tally_of(tail)
81
- .pending.map((absolute) => investigation_reads.repository_relative(absolute))
82
-
83
- return pending.length === 0
84
- ? NOTICE
85
- : `${NOTICE} Read and not edited so far: ${pending.join(' ')}.`
86
- }
87
-
88
50
  const GUARD = hook_decision.create_transcript_guard({
89
51
  prefix: STAMP_PREFIX,
90
52
  switch_key: SWITCH_ENV_KEY,
91
53
  is_candidate: investigation_reads.is_refusable_call,
92
54
  should_block,
93
55
  reason: investigation_reads.REASON,
94
- // The lane-child notice fires where the refusal is withheld, on a record of its own so it
95
- // never spends the refusal's stamp.
96
- notify: {
97
- prefix: NOTICE_STAMP_PREFIX,
98
- should_notify,
99
- text: notice_text,
100
- },
101
56
  })
102
57
 
103
58
  const investigation_refusal = GUARD.refusal
104
59
  const investigation_outcome = GUARD.outcome
105
60
  const { is_enabled, refusal_path } = GUARD
106
- // The notice's stamp path, exposed the way `refusal_path` is so a test can clean it apart from the
107
- // refusal's record.
108
- const investigation_notice_path = hook_decision.create_refusal_stamp(NOTICE_STAMP_PREFIX).path
109
61
  const { deny_envelope, load_environment_file, DISABLED_VALUES } = hook_decision
110
62
 
111
63
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
@@ -115,13 +67,11 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) {
115
67
 
116
68
  export {
117
69
  deny_envelope,
118
- investigation_notice_path,
119
70
  investigation_outcome,
120
71
  investigation_refusal,
121
72
  is_enabled,
122
73
  load_environment_file,
123
74
  refusal_path,
124
75
  DISABLED_VALUES,
125
- NOTICE,
126
76
  SWITCH_ENV_KEY,
127
77
  }
@@ -42,7 +42,7 @@ const SEARCH_LABELS: ReadonlySet<string> = new Set(
42
42
  const INVESTIGATOR_AGENT = 'investigator'
43
43
 
44
44
  // The deny reason is the only text that reaches the model, so it names the count, command and return shape.
45
- const REASON = `⛔ investigation: ${String(delegation_policy.INVESTIGATION_FILE_THRESHOLD)} files read and not edited, or ${String(delegation_policy.INVESTIGATION_SEARCH_TURN_THRESHOLD)} search turns, since the last delegated unit, so the reading from here goes to a unit of its own (a successful write also starts the search count over). Ask \`pnpm josh delegate investigation\`, then dispatch the \`${INVESTIGATOR_AGENT}\` agent (\`kit:${INVESTIGATOR_AGENT}\` in a consumer) with a brief of what the main line has already concluded and what is left to find out; it returns the conclusion plus its \`file:line\` citations, never the file text. Keep a read in the main line only where this run will edit that file — an \`Edit\` cannot be issued against text you do not hold. This run's own instructions and the harness's own session files — a backgrounded call's output, a persisted tool result, this session's scratchpad — never count toward the files, so they are not what took the file count here; every read-only search turn counts, whatever it names. The rule is \`.claude/skills/workflow-commands/delegation.md\` → "The pre-implementation reading".`
45
+ const REASON = `⛔ investigation: ${String(delegation_policy.INVESTIGATION_FILE_THRESHOLD)} files read and not edited, or ${String(delegation_policy.INVESTIGATION_SEARCH_TURN_THRESHOLD)} search turns, since the last delegated unit. Run \`pnpm josh delegate investigation\`, then dispatch the \`${INVESTIGATOR_AGENT}\` agent (\`kit:${INVESTIGATOR_AGENT}\` in a consumer); it returns \`file:line\` citations, never the file text. Keep main-line reads to files you will edit; every read-only search turn counts. \`.claude/skills/workflow-commands/delegation.md\``
46
46
 
47
47
  interface ReadTally {
48
48
  // The files read and not since edited, resolved.
@@ -362,7 +362,6 @@ const investigation_reads = {
362
362
  is_session_artifact,
363
363
  is_subject_file,
364
364
  projected_count,
365
- repository_relative,
366
365
  resolved,
367
366
  should_block,
368
367
  tally_of,
@@ -138,8 +138,10 @@ function resolve_current_owner(repository_path: string): string | undefined {
138
138
  // all — the very failure this function exists to close, moved somewhere else.
139
139
  function main_worktree(repository_path: string): string {
140
140
  const git_directory = resolve_git_directory(repository_path)
141
- if (git_directory === undefined) return repository_path
142
- if (path.basename(git_directory) !== GIT_DIRECTORY) return repository_path
141
+
142
+ if (git_directory === undefined || path.basename(git_directory) !== GIT_DIRECTORY) {
143
+ return repository_path
144
+ }
143
145
 
144
146
  return path.dirname(git_directory)
145
147
  }
@@ -68,8 +68,7 @@ function parse_scp_like(url: string): RepoIdentity | undefined {
68
68
  const user_end = url.indexOf(USER_SEPARATOR)
69
69
  const authority = user_end === NOT_FOUND ? url : url.slice(user_end + 1)
70
70
  const host_end = authority.indexOf(HOST_SEPARATOR)
71
- if (host_end === NOT_FOUND) return undefined
72
- if (!is_github_ssh_host(authority.slice(0, host_end))) return undefined
71
+ if (host_end === NOT_FOUND || !is_github_ssh_host(authority.slice(0, host_end))) return undefined
73
72
 
74
73
  return to_identity(authority.slice(host_end + 1))
75
74
  }
@@ -115,8 +114,7 @@ function origin_section_lines(lines: ReadonlyArray<string>): Array<string> {
115
114
  // the first separator rather than matched, for the reason `parse_scp_like` is split by hand.
116
115
  function url_value(line: string): string | undefined {
117
116
  const separator = line.indexOf('=')
118
- if (separator === NOT_FOUND) return undefined
119
- if (line.slice(0, separator).trim() !== URL_KEY) return undefined
117
+ if (separator === NOT_FOUND || line.slice(0, separator).trim() !== URL_KEY) return undefined
120
118
 
121
119
  return line.slice(separator + 1).trim()
122
120
  }
@@ -69,9 +69,12 @@ function decide_reclaim(
69
69
  global_josh: string | undefined,
70
70
  shim_content: string,
71
71
  ): ReclaimDecision {
72
- if (!is_shadowed(path_josh, global_josh)) return { action: 'none', reason: NO_SHADOW_REASON }
73
- // is_shadowed already guarantees path_josh is defined; this narrows the type for the return.
74
- if (path_josh === undefined) return { action: 'none', reason: NO_SHADOW_REASON }
72
+ // is_shadowed already implies path_josh is defined; the undefined check narrows the type for the
73
+ // return.
74
+ if (path_josh === undefined || !is_shadowed(path_josh, global_josh)) {
75
+ return { action: 'none', reason: NO_SHADOW_REASON }
76
+ }
77
+
75
78
  if (!is_kit_shim(shim_content)) return { action: 'none', reason: NOT_KIT_SHIM_REASON }
76
79
 
77
80
  return { action: 'remove', target: path_josh, reason: RECLAIM_REASON }
@@ -57,52 +57,53 @@ function block_ceiling(size: number): number {
57
57
  // a stale entry (a file that no longer exists or that an entry now reads) and on an un-budgeted
58
58
  // unreached file, so the definition cannot rot as documents move on or off the execution path.
59
59
  const DOCUMENT_BYTE_BUDGET: ReadonlyArray<DocumentBudget> = [
60
- { path: '.claude/skills/dependency-update/SKILL.md', bytes: 8192 },
61
- { path: '.claude/skills/epic-commands/SKILL.md', bytes: 16_384 },
62
- { path: '.claude/skills/epic-commands/epic-bundle.md', bytes: 8192 },
63
- { path: '.claude/skills/epic-commands/execution-waves.md', bytes: 8192 },
64
- { path: '.claude/skills/verify-ui/SKILL.md', bytes: 8192 },
60
+ { path: '.claude/skills/dependency-update/SKILL.md', bytes: 4096 },
61
+ { path: '.claude/skills/epic-commands/SKILL.md', bytes: 12_288 },
62
+ { path: '.claude/skills/epic-commands/epic-bundle.md', bytes: 4096 },
63
+ { path: '.claude/skills/epic-commands/execution-waves.md', bytes: 4096 },
64
+ { path: '.claude/skills/verify-ui/SKILL.md', bytes: 4096 },
65
65
  { path: '.claude/skills/workflow-commands/backlogrun-handoff-report.md', bytes: 4096 },
66
66
  { path: '.claude/skills/workflow-commands/fullrun-steps.md', bytes: 8192 },
67
67
  { path: '.claude/skills/workflow-commands/into-target.md', bytes: 4096 },
68
68
  { path: '.claude/skills/workflow-commands/issue-comments.md', bytes: 8192 },
69
69
  { path: '.claude/skills/workflow-commands/issue-fold-existing.md', bytes: 4096 },
70
70
  { path: '.claude/skills/workflow-commands/needs-human-review.md', bytes: 4096 },
71
- { path: '.claude/skills/workflow-commands/observation-filing.md', bytes: 12_288 },
72
- { path: '.claude/skills/workflow-commands/observation-ledger.md', bytes: 12_288 },
71
+ { path: '.claude/skills/workflow-commands/observation-filing.md', bytes: 8192 },
72
+ { path: '.claude/skills/workflow-commands/observation-ledger.md', bytes: 8192 },
73
73
  { path: '.claude/skills/workflow-commands/prerequisite.md', bytes: 8192 },
74
74
  { path: '.claude/skills/workflow-commands/target-repository.md', bytes: 4096 },
75
75
  { path: '.claude/skills/workflow-commands/working-tree-hold.md', bytes: 8192 },
76
- { path: 'docs/josh-commands-automation.md', bytes: 53_248 },
77
- { path: 'docs/josh-commands-run.md', bytes: 77_824 },
78
- { path: 'docs/josh-commands-backlog.md', bytes: 61_440 },
79
- { path: 'docs/josh-commands.md', bytes: 57_344 },
76
+ { path: 'docs/josh-commands-automation.md', bytes: 49_152 },
77
+ { path: 'docs/josh-commands-run.md', bytes: 65_536 },
78
+ { path: 'docs/josh-commands-backlog.md', bytes: 53_248 },
79
+ { path: 'docs/josh-commands.md', bytes: 49_152 },
80
80
  { path: 'docs/labels-and-run-states.md', bytes: 8192 },
81
- { path: 'prompts/coding-standards.md', bytes: 8192 },
82
- { path: 'prompts/collaboration-workflow.md', bytes: 8192 },
83
- { path: 'prompts/collaboration-workflow/file-edits.md', bytes: 12_288 },
84
- { path: 'prompts/collaboration-workflow/gh-rest.md', bytes: 8192 },
81
+ { path: 'prompts/coding-standards.md', bytes: 4096 },
82
+ { path: 'prompts/collaboration-workflow.md', bytes: 4096 },
83
+ { path: 'prompts/collaboration-workflow/file-edits.md', bytes: 4096 },
84
+ { path: 'prompts/collaboration-workflow/gh-rest.md', bytes: 4096 },
85
85
  { path: 'prompts/collaboration-workflow/glossary.md', bytes: 4096 },
86
86
  { path: 'prompts/collaboration-workflow/issue-citation.md', bytes: 4096 },
87
- { path: 'prompts/collaboration-workflow/issue-template.md', bytes: 12_288 },
88
- { path: 'prompts/collaboration-workflow/operating-rules.md', bytes: 12_288 },
87
+ { path: 'prompts/collaboration-workflow/issue-template.md', bytes: 8192 },
88
+ { path: 'prompts/collaboration-workflow/lane-instructions.md', bytes: 4096 },
89
+ { path: 'prompts/collaboration-workflow/operating-rules.md', bytes: 8192 },
89
90
  { path: 'prompts/collaboration-workflow/output-bounds.md', bytes: 4096 },
90
91
  { path: 'prompts/collaboration-workflow/overview.md', bytes: 4096 },
91
- { path: 'prompts/collaboration-workflow/plan-comment.md', bytes: 8192 },
92
- { path: 'prompts/collaboration-workflow/principles.md', bytes: 16_384 },
92
+ { path: 'prompts/collaboration-workflow/plan-comment.md', bytes: 4096 },
93
+ { path: 'prompts/collaboration-workflow/principles.md', bytes: 12_288 },
93
94
  { path: 'prompts/collaboration-workflow/proposal-request.md', bytes: 4096 },
94
- { path: 'prompts/collaboration-workflow/report-format.md', bytes: 16_384 },
95
+ { path: 'prompts/collaboration-workflow/report-format.md', bytes: 12_288 },
95
96
  { path: 'prompts/collaboration-workflow/residency.md', bytes: 8192 },
96
97
  { path: 'prompts/collaboration-workflow/rule-delivery.md', bytes: 4096 },
97
- { path: 'prompts/collaboration-workflow/shell-body.md', bytes: 8192 },
98
- { path: 'prompts/collaboration-workflow/turn-batching.md', bytes: 8192 },
99
- { path: 'prompts/collaboration-workflow/upstream-interrupt.md', bytes: 12_288 },
100
- { path: 'prompts/collaboration-workflow/wip-cap.md', bytes: 12_288 },
98
+ { path: 'prompts/collaboration-workflow/shell-body.md', bytes: 4096 },
99
+ { path: 'prompts/collaboration-workflow/turn-batching.md', bytes: 4096 },
100
+ { path: 'prompts/collaboration-workflow/upstream-interrupt.md', bytes: 8192 },
101
+ { path: 'prompts/collaboration-workflow/wip-cap.md', bytes: 8192 },
101
102
  { path: 'prompts/refactoring.md', bytes: 8192 },
102
- { path: 'prompts/review-rubric.md', bytes: 16_384 },
103
- { path: 'prompts/review.md', bytes: 16_384 },
103
+ { path: 'prompts/review-rubric.md', bytes: 12_288 },
104
+ { path: 'prompts/review.md', bytes: 12_288 },
104
105
  { path: 'prompts/sonar-hotspot-handling.md', bytes: 8192 },
105
- { path: 'prompts/testing-guide.md', bytes: 16_384 },
106
+ { path: 'prompts/testing-guide.md', bytes: 12_288 },
106
107
  ]
107
108
 
108
109
  // The recorded ceiling for one path, or undefined when it carries no budget entry.
@@ -26,8 +26,7 @@ function ceiling_message(
26
26
  current_bytes: number,
27
27
  recorded_bytes: number | undefined,
28
28
  ): string | undefined {
29
- if (recorded_bytes === undefined) return undefined
30
- if (current_bytes <= recorded_bytes) return undefined
29
+ if (recorded_bytes === undefined || current_bytes <= recorded_bytes) return undefined
31
30
 
32
31
  return document_byte_budget.over_budget_message(relative_path, current_bytes, recorded_bytes)
33
32
  }
@@ -2,6 +2,7 @@
2
2
  import { existsSync } from 'node:fs'
3
3
  import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
+ import { find_package_directory } from '#scripts/josh/package-directory'
5
6
  import { capped_output } from './capped-print'
6
7
  import { document_section } from './document-section'
7
8
  import { entry_read_set } from './entry-read-set'
@@ -35,14 +36,25 @@ const USAGE = 'Usage: josh doc:section <file.md> "<heading>"'
35
36
  // directory, a bare name that happened to exist beside the caller would beat the document under
36
37
  // `root` and print a section of the wrong file — silently, which is the failure this command exists
37
38
  // to refuse.
38
- function resolve_document(name: string, root: string = process.cwd()): string {
39
- const named = path.resolve(root, name)
39
+ //
40
+ // **The kit package is the fallback.** A consumer keeps no `prompts/` of its own — the package ships
41
+ // it under `node_modules/@joshuafolkken/kit/` — so `prompts/review.md` resolves there when `root` has
42
+ // no copy, exactly as `review:brief` resolves the rubric.
43
+ const PACKAGE_DIRECTORY = find_package_directory(path.dirname(fileURLToPath(import.meta.url)))
40
44
 
41
- if (existsSync(named)) return named
45
+ function candidates_under(directory: string, name: string): Array<string> {
46
+ return [path.resolve(directory, name), entry_read_set.document_path(directory, name)]
47
+ }
42
48
 
43
- const in_skill = entry_read_set.document_path(root, name)
49
+ function resolve_document(
50
+ name: string,
51
+ root: string = process.cwd(),
52
+ package_directory: string = PACKAGE_DIRECTORY,
53
+ ): string {
54
+ const named = path.resolve(root, name)
55
+ const candidates = [...candidates_under(root, name), ...candidates_under(package_directory, name)]
44
56
 
45
- return existsSync(in_skill) ? in_skill : named
57
+ return candidates.find((candidate) => existsSync(candidate)) ?? named
46
58
  }
47
59
 
48
60
  function report_missing(markdown: string, heading: string): number {
@@ -47,9 +47,9 @@ const ENTRY_READ_BUDGET: ReadonlyArray<EntryBudget> = [
47
47
  { entry: 'kickoff', bytes: 36_864 },
48
48
  { entry: 'fullrun', bytes: 53_248 },
49
49
  { entry: 'halfrun', bytes: 49_152 },
50
- { entry: 'prrun', bytes: 57_344 },
51
- { entry: 'backlogrun', bytes: 90_112 },
52
- { entry: LANE_CHILD, bytes: 49_152 },
50
+ { entry: 'prrun', bytes: 53_248 },
51
+ { entry: 'backlogrun', bytes: 81_920 },
52
+ { entry: LANE_CHILD, bytes: 45_056 },
53
53
  ]
54
54
 
55
55
  function byte_size(root: string, relative_path: string): number {
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { fileURLToPath } from 'node:url'
3
+ import { lane_instructions } from '#scripts/agent/lane-instructions'
3
4
  import { backlogrun_parent_read_set } from './backlogrun-parent-read-set'
4
5
  import { entry_read_set, type ReadSetCost, type SectionCost } from './entry-read-set'
5
6
  import { lane_child_read_set } from './lane-child-read-set'
@@ -192,7 +193,9 @@ function total_lines(report: ReadSetCost, width: number): Array<string> {
192
193
  }
193
194
 
194
195
  function note_lines(report: ReadSetCost): Array<string> {
195
- if (report.entry === lane_child_read_set.LANE_CHILD) return [...LANE_CHILD_NOTE, '']
196
+ if (report.entry === lane_child_read_set.LANE_CHILD) {
197
+ return [...LANE_CHILD_NOTE, lane_instructions.resident_line(), '']
198
+ }
196
199
 
197
200
  if (report.entry === BACKLOGRUN) return [...BACKLOGRUN_PARENT_NOTE, '']
198
201
 
@@ -14,7 +14,6 @@ import { epic_parse } from './epic-parse'
14
14
  // the children the epic already names, while this one searches for issues the epic does *not* name.
15
15
 
16
16
  const SEARCH_LIMIT = 50
17
- const PARENT_MARKERS: ReadonlyArray<string> = ['親:', 'Parent:', '親:']
18
17
  // Named apart from `epic_audit_checks.ORPHAN_CHILD`: that check reports an orphan it found, this one
19
18
  // reports that the search for orphans was incomplete, and a reader has to be able to tell them apart.
20
19
  const ORPHAN_SEARCH = 'orphan search'
@@ -25,18 +24,15 @@ const CAPPED_BY_MATCHES = `The open-issue scan filled its ${String(SEARCH_LIMIT)
25
24
 
26
25
  const searched_issue_schema = z.object({ number: z.number(), body: z.string().nullable() })
27
26
 
28
- function has_parent_marker(line: string): boolean {
29
- return PARENT_MARKERS.some((marker) => line.includes(marker))
30
- }
31
-
32
27
  // Whether an issue's body names this epic as its parent, in the shape the issue template uses.
33
28
  //
34
29
  // Both halves must be on the *same line*. An issue parented to a different epic routinely backlinks
35
30
  // this one elsewhere in its body, and matching the marker and the number independently reported
36
31
  // every such issue as an orphan.
37
32
  //
38
- // The number is read through the same reference parse the checks use, so a parent line (a PARENT_MARKERS
39
- // prefix) such as `owner/other#858` names that repository's epic and not this one's.
33
+ // The number is read through the same reference parse the checks use, so a parent line (one
34
+ // `epic_audit_logic.has_parent_marker` accepts) such as `owner/other#858` names that repository's
35
+ // epic and not this one's.
40
36
  // The known set is this repository alone — a parent line names this epic or it names nothing here —
41
37
  // and it is passed rather than omitted so a repository whose own name contains a dot still recognizes
42
38
  // `joshuafolkken/site.com#858`.
@@ -49,7 +45,8 @@ function names_this_epic(line: string, epic_number: number, repo: string): boole
49
45
  function claims_parent(body: string | null, epic_number: number, repo: string): boolean {
50
46
  return (body ?? '')
51
47
  .split('\n')
52
- .some((line) => has_parent_marker(line) && names_this_epic(line, epic_number, repo))
48
+ .filter((line) => epic_audit_logic.has_parent_marker(line))
49
+ .some((line) => names_this_epic(line, epic_number, repo))
53
50
  }
54
51
 
55
52
  // Why the scan stopped before the end of the open backlog, or that it did not. Both cutoffs hide the
@@ -68,9 +68,15 @@ function order_pairs(
68
68
  return links.flatMap((link) => {
69
69
  const blocker = find_child(children, { repo: epic_repo, number: link.blocker })
70
70
  const blocked = find_child(children, { repo: epic_repo, number: link.blocked })
71
- if (blocker === undefined || blocked === undefined) return []
72
71
 
73
- if (!is_open_local(blocker, current_repo) || !is_open_local(blocked, current_repo)) return []
72
+ if (
73
+ blocker === undefined ||
74
+ blocked === undefined ||
75
+ !is_open_local(blocker, current_repo) ||
76
+ !is_open_local(blocked, current_repo)
77
+ ) {
78
+ return []
79
+ }
74
80
 
75
81
  // eslint-disable-next-line unicorn/no-unnecessary-array-flat-map -- one return type (sonarjs/function-return-type)
76
82
  return [{ blocker, blocked }]