@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,12 +2,10 @@
2
2
 
3
3
  ## 0. Per-Requirement Test Coverage (MANDATORY)
4
4
 
5
- Before writing any implementation code, **list every requirement from the user's request and assign a test to each one**. This step is non-negotiable.
5
+ Before any implementation code, **list every requirement from the user's request and assign a test to each one**.
6
6
 
7
7
  ### Planning template
8
8
 
9
- For a user request with N requirements, enumerate each one before touching any source files:
10
-
11
9
  ```text
12
10
  Req 1: "Label chip shown immediately"
13
11
  → E2E: open editor, type new label, press Enter → chip visible without blurring
@@ -21,19 +19,17 @@ Req N: ...
21
19
 
22
20
  ### Rules
23
21
 
24
- - **Every user-facing behavior change** (UI interaction, keyboard shortcut, visible state change) must have a corresponding E2E test.
25
- - **Every logic/utility change** (pure functions, filters, transforms) must have a unit test. A bug fix takes a regression test; a refactor takes tests pinning the existing behavior BEFORE the change (`prompts/refactoring.md`).
22
+ - User-facing behavior change (UI interaction, keyboard shortcut, visible state) → an E2E test.
23
+ - Logic/utility change (pure functions, filters, transforms) → a unit test. Bug fix → a regression test; refactor → tests pinning the existing behavior BEFORE the change (`prompts/refactoring.md`).
26
24
  - **Non-runtime updates (pre-approved manual-only exception):** a change touching no executable runtime code path — docs, non-executable config, editor/IDE files, cosmetic asset swaps with no selector/path change — may be verified by hand; declare it in Step 0 with the reason. The mechanically exempt (`*.md`, `.editorconfig`, `.idea/**`, `.vscode/**`, `prompts/**`) need no declaration. A runtime change whose test is infeasible needs the reason and user approval.
27
- - **basic で kit がテストできない言語だけの変更:** `package.json` に `josh.profile: basic`(改名前の `static` も同じ)が記録され、変更が HTML/CSS や Lua などのソースだけなら自動テストは不要。HTML/CSS はブラウザーで対象ページを開き、想定する画面幅でレイアウト・リンク・HTML 内の操作を確認する。そのほかのソースは変更した処理を実際に動かす。どちらも結果を記録する。同じ変更に JavaScript または TypeScript のファイルが含まれれば自動テストを行う。
28
- - If a test is genuinely infeasible (e.g., native OS date-picker popup cannot be driven by Playwright), write a comment in the test file explaining why and test the closest observable behavior instead (e.g., that the editor remains open after the `change` event fires).
29
- - Do **not** report a requirement as done if its test is missing.
25
+ - **basic で kit がテストできない言語だけの変更:** `package.json` に `josh.profile: basic`(改名前の `static` も同じ)があり、変更が HTML/CSS や Lua などのソースだけなら自動テストは不要。HTML/CSS はブラウザーで対象ページを開き、想定する画面幅でレイアウト・リンク・HTML 内の操作を確認する。そのほかのソースは変更した処理を実際に動かす。どちらも結果を記録する。JavaScript または TypeScript のファイルを含む変更は自動テストを行う。
26
+ - Test infeasible (e.g., a native OS date-picker Playwright cannot drive) → comment why in the test file and test the closest observable behavior (e.g., the editor stays open after `change`).
27
+ - A requirement without its test is not done.
30
28
 
31
29
  ### Verification checklist (add to the completion gate)
32
30
 
33
- - [ ] Count requirements in the user's request.
34
- - [ ] Count tests added/updated for those requirements.
35
- - [ ] The two counts match (or each gap is documented as infeasible with a comment).
36
- - [ ] **UI changes only:** a screenshot of the affected screen was captured and visually confirmed before reporting completion (passing tests are not proof the UI looks correct — see the **UI verification (screenshot)** rule in the completion gate). If a screenshot is impossible in this environment, that is stated and the user is asked to verify visually.
31
+ - [ ] Requirement count equals the count of tests added/updated (each gap documented as infeasible).
32
+ - [ ] UI changes: a screenshot of the affected screen was checked (`CLAUDE.md` → UI verification); if impossible here, say so and ask the user to check.
37
33
 
38
34
  ---
39
35
 
@@ -44,21 +40,19 @@ Req N: ...
44
40
  | `src/routes/` pages, UI components with user interaction | E2E (Playwright) |
45
41
  | `.ts`/`.js` utilities, `src/lib/server/`, display-only components | Unit/Integration (Vitest) |
46
42
 
47
- **The type is decided from the path, not by asking.** A change under `src/routes/` takes E2E; every other runtime path takes Unit — a component with interaction is reached through the route that renders it, and that route is under `src/routes/`. `pnpm josh test:declared` prints the type each untested runtime file calls for on its `required` detail line, so the choice never becomes a stop.
43
+ **The type is decided from the path, not by asking**: under `src/routes/` → E2E; every other runtime path → Unit (an interactive component is reached through its route). `pnpm josh test:declared` prints the type per untested runtime file on its `required` line.
48
44
 
49
45
  ### Test file naming & placement (one unambiguous rule)
50
46
 
51
- There is exactly **one** convention for every test file — never choose between two. The rules below are mandatory; some are config-enforced (a violating file runs under the wrong vitest project or not at all), others are enforced by convention/review — each rule states which.
52
-
53
47
  | Test kind | Required filename | Routed to |
54
48
  | ------------------- | ------------------------ | -------------------------------- |
55
49
  | Unit / integration | `*.test.ts` | node/unit vitest project |
56
50
  | Component / browser | `*.svelte.test.ts` | browser/component vitest project |
57
51
  | E2E | `src/routes/**/*.e2e.ts` | Playwright |
58
52
 
59
- - **Use `*.test.ts` — never `*.spec.ts`.** **Lint-enforced** by `eslint/rules/test-filename.js` (wired into `eslint/base.js`) for every JS/TS extension; how the ban is wired and why: `docs/maintainers/testing-guide-history.md` → "Why the `*.spec.*` ban is a lint rule".
60
- - **The `.svelte.` infix is required for component/browser tests and must be preserved.** `*.svelte.test.ts` routes the file to the **browser/component** vitest project (`include: src/**/*.svelte.{test,spec}.{js,ts}`); plain `*.test.ts` routes to the **node/unit** project (`include: src/**/*.{test,spec}.{js,ts}`). Renaming `Foo.svelte.test.ts` → `Foo.test.ts` silently moves it to the wrong project — do not drop the `.svelte.` infix.
61
- - **Colocate every test next to the code it exercises.** A top-level `tests/` directory is **not used** — place `foo.test.ts` beside `foo.ts`, and E2E specs under the relevant `src/routes/**` path. The top-level `tests/` ban is **lint-enforced** by the same rule (`eslint/rules/test-filename.js`). Note: `playwright.config.ts` discovers E2E via `testMatch: '**/*.e2e.{ts,js}'`, so an `*.e2e.ts` placed outside `src/routes/**` would still run — that specific `src/routes/**` placement is enforced by **convention/review, not config**, and must be upheld manually.
53
+ - **Never `*.spec.ts`** — lint-enforced by `eslint/rules/test-filename.js` (wired into `eslint/base.js`) for every JS/TS extension.
54
+ - **Keep the `.svelte.` infix** on component/browser tests: `*.svelte.test.ts` matches the browser project (`include: src/**/*.svelte.{test,spec}.{js,ts}`), plain `*.test.ts` the node project (`include: src/**/*.{test,spec}.{js,ts}`); dropping it silently moves the file to the wrong project.
55
+ - **Colocate** each test beside its code (`foo.test.ts` next to `foo.ts`); no top-level `tests/` (lint-enforced by the same rule). E2E specs go under `src/routes/**` — enforced by review only, since `playwright.config.ts` `testMatch: '**/*.e2e.{ts,js}'` runs them anywhere.
62
56
 
63
57
  ---
64
58
 
@@ -72,156 +66,111 @@ There is exactly **one** convention for every test file — never choose between
72
66
  ### Test Functions
73
67
 
74
68
  - Outside `describe`: `test`; inside `describe`: `it`
75
- - Use `describe` only when grouping multiple tests is necessary
69
+ - `describe` only when grouping multiple tests
76
70
 
77
71
  ### Parameterized Tests
78
72
 
79
73
  - Playwright: `for` loop; Vitest: `test.each` / `it.each`
80
- - Test cases: define as `cases` or `*_cases` array at file top (outside test functions)
74
+ - Cases: a `cases` or `*_cases` array at file top, outside test functions
81
75
 
82
76
  ### Constants
83
77
 
84
- - All magic numbers/strings except `0`, `1`, `-1` must be `UPPER_SNAKE_CASE` constants
85
- - Place after imports at file top
78
+ - Magic numbers/strings except `0`, `1`, `-1` → `UPPER_SNAKE_CASE` constants after the imports
86
79
 
87
80
  ### Imports
88
81
 
89
- - Always include `.js` extension: `import { foo } from './bar.js'`
82
+ - Always the `.js` extension: `import { foo } from './bar.js'`
90
83
  - Type imports: `import { type Foo } from '...'`
91
84
 
92
85
  ### Playwright
93
86
 
94
- - Element selection: `page.getByTestId('id')` — add `data-testid` to implementation files
87
+ - Select with `page.getByTestId('id')`; add `data-testid` to the implementation
95
88
  - Test functions always `async`
96
- - **Project split:** `playwright.config.ts` defines **`e2e-guest`** (no auth: `src/routes/**/dash-guest.e2e.ts`, `src/routes/**/demo.e2e.ts`), then **`e2e-main`** (depends on `e2e-guest`), then **`e2e-leak-check`** (depends on `e2e-main`). Authenticated projects set `storageState` to `src/routes/.auth/user.json` when that file exists — **there is no UI login on every test**; cookies/session are restored from disk.
97
- - **Workers:** Playwright uses `workers: 1` because authenticated E2E shares one storage state; higher parallelism races on the same DB user.
98
- - **Timeouts:** Guest/main/leak projects use **per-project test timeouts** in `playwright.config.ts` so switching tabs and dash actions are not cut off by the global default.
89
+ - `playwright.config.ts` projects, each depending on the previous: `e2e-guest` (no auth: `src/routes/**/dash-guest.e2e.ts`, `src/routes/**/demo.e2e.ts`) → `e2e-main` → `e2e-leak-check`. Authenticated ones restore `storageState` from `src/routes/.auth/user.json` when it exists — no UI login per test.
90
+ - `workers: 1` — authenticated E2E shares one storage state and DB user.
91
+ - Guest/main/leak projects set per-project test timeouts so tab switches and dash actions are not cut off.
99
92
 
100
93
  ### Regression fix workflow (E2E data / cleanup bugs)
101
94
 
102
- When fixing bugs where tests leave data behind (or similar persistent state):
95
+ When tests leave persistent state behind:
103
96
 
104
- 1. **Add or extend a failing guard** — e.g. `src/routes/dash/dash-leak-check.e2e.ts` exports `dash_leak_guard.scrub_then_assert_clean`: it scrubs tasks whose titles contain the `E2E_` automation prefix, then asserts none remain. Confirm it **fails** while scrub or per-test teardown is broken (red).
105
- 2. **Fix production code or test teardown** — cleanup must be reliable (prefer `data-testid` over locale-specific `aria-label` for automation; avoid swallowing cleanup errors unless explicitly intended).
106
- 3. **Confirm the guard passes** (green) with full `pnpm test:e2e` (or targeted Playwright command above).
107
- 4. **Document** any new invariant in this guide or the relevant E2E helper module.
97
+ 1. Add or extend a guard and confirm it fails (e.g. `dash_leak_guard.scrub_then_assert_clean` in `src/routes/dash/dash-leak-check.e2e.ts` scrubs `E2E_`-prefixed tasks, then asserts none remain).
98
+ 2. Fix production code or teardown — prefer `data-testid` over locale-specific `aria-label`; do not swallow cleanup errors unless intended.
99
+ 3. Confirm green with full `pnpm test:e2e` (or a targeted Playwright run).
100
+ 4. Document any new invariant here or in the E2E helper module.
108
101
 
109
102
  ### Error Messages
110
103
 
111
- - Include expected and actual values explicitly
104
+ - State expected and actual values
112
105
  - Wrap `number` in template literals with `String()`
113
- - Don't add `?? ''` unless the value can genuinely be `undefined`
106
+ - No `?? ''` unless the value can be `undefined`
114
107
 
115
108
  ### Type Safety
116
109
 
117
- - No `any`; always use loop variables (avoids unused-variable lint errors)
118
- - Use `@ts-expect-error`, not `@ts-ignore`
110
+ - No `any`; use every loop variable
111
+ - `@ts-expect-error`, not `@ts-ignore`
119
112
 
120
113
  ### Statement Limit
121
114
 
122
- - Max 10 statements per test function; split complex tests if exceeded
115
+ - Max 10 statements per test function; split beyond that
123
116
 
124
117
  ### No live network
125
118
 
126
- - **A unit test must not reach the network.** Mock every read that leaves the process — a `gh` call, an HTTP request, a CLI subprocess that makes one
127
- - **Mock the whole read, not the first half of it.** A helper that stubs one call and leaves a second one on its default calls through, and the test still **passes** — slower, and against whatever the remote answers. That is how joshuafolkken/kit#1353 reached a 10-second timeout in CI on a change that had nothing to do with it
128
- - In kit's own checkout `vitest.config.ts` arms a guard that fails the run and lists the `gh` invocations it recorded — the commands, not the test that made them, which the command text is usually enough to find (`docs/josh-commands.md` → `josh test:unit`). Elsewhere the rule holds without one
119
+ - **A unit test must not reach the network** — mock every read leaving the process (`gh` call, HTTP request, CLI subprocess that makes one).
120
+ - **Mock the whole read**: a stub of one call leaving a second on its default still passes, slowly and against the remote (joshuafolkken/kit#1353: a 10-second CI timeout).
121
+ - kit's `vitest.config.ts` fails such a run, listing the `gh` invocations (`docs/josh-commands.md` → `josh test:unit`); elsewhere the rule holds unguarded.
129
122
 
130
123
  ### A josh command that writes state runs through the integration harness (kit only)
131
124
 
132
- - **A new or changed state-writing josh command needs a test in all three environments of `scripts/test/josh-harness.ts`** — kit, a consumer without `docs/`, a lane worktree (joshuafolkken/kit#2402). Packed suite: `vitest.harness.config.ts`
125
+ - A new or changed state-writing josh command needs a test in all three environments of `scripts/test/josh-harness.ts` — kit, a consumer without `docs/`, a lane worktree (joshuafolkken/kit#2402). Packed suite: `vitest.harness.config.ts`
133
126
 
134
127
  ---
135
128
 
136
129
  ## 3. Checklist
137
130
 
138
- - [ ] **Before “done”:** **Completion gate** in `CLAUDE.md` — `pnpm josh gate` (lint, type check, spell check and unit tests), then E2E per § 6 below: the CI E2E job where a pull request is open, **`pnpm josh test:e2e`** run by you where there is none. **Do not** stop at `pnpm josh test:unit` when E2E applies, and **never** ask the user to run it. Run it with `CI` unset so Playwright uses **DEV** (`pnpm run dev` on the port `playwright.config.ts` resolves from `PORT_SEED`), matching the VS Code Test plugin. ReadLints **0 errors** on touched files; Playwright **no failures and no flaky** runs in what you report. **If Playwright cannot run in your environment:** do **not** use `pnpm build` or `CI=true` unless the user asked — say the E2E result is missing and that the gate is therefore not closed
139
- - [ ] Magic numbers/strings → constants (except `0`, `1`, `-1`)
140
- - [ ] `.js` on all import paths
141
- - [ ] Playwright: `data-testid` selectors only
142
- - [ ] `async`/`await` used correctly
143
- - [ ] No `any`; all loop variables used
144
- - [ ] Max 10 statements per test function
145
- - [ ] `read_lints` run; 0 errors before reporting done
146
- - [ ] No unnecessary `?? ''`; `String()` for numbers in template literals
131
+ - [ ] `pnpm josh gate`, then E2E per § 6: the CI E2E job where a pull request is open; where there is none, run `pnpm josh test:e2e` with `CI` unset (DEV: `pnpm run dev` on the port `playwright.config.ts` derives from `PORT_SEED`); no `pnpm build` or `CI=true` unless asked. Playwright cannot run here → report the E2E result missing and the gate not closed
132
+ - [ ] `read_lints`: 0 errors on touched files; Playwright: no failures and no flaky runs
133
+ - [ ] § 2 holds: constants, `.js` imports, `data-testid` selectors, correct `async`/`await`, no `any`, ≤10 statements, `String()` / no needless `?? ''`
147
134
 
148
135
  ---
149
136
 
150
137
  ## 4. Reference Files
151
138
 
152
- **E2E:** `src/routes/**/page.e2e.ts`, `src/routes/**/praise.e2e.ts`
153
- **Unit:** `src/lib/data/phrases/phrases.test.ts`, `src/lib/data/praise-audio.test.ts`
154
- **ESLint:** `eslint.config.js`
139
+ E2E: `src/routes/**/page.e2e.ts`, `src/routes/**/praise.e2e.ts` · Unit: `src/lib/data/phrases/phrases.test.ts`, `src/lib/data/praise-audio.test.ts` · ESLint: `eslint.config.js`
155
140
 
156
141
  ---
157
142
 
158
143
  ## 5. Unit Test Example — Stateful Functions
159
144
 
160
- ```typescript
161
- import { expect, test } from 'vitest'
162
- import { get_praise_audio_file, reset_praise_audio_index } from './praise-audio.js'
163
-
164
- test('returns expected values in sequence', () => {
165
- reset_praise_audio_index() // reset state at the start of each test
166
-
167
- const first_result = get_praise_audio_file()
168
- expect(first_result).toBe('expected-value-1')
169
-
170
- const second_result = get_praise_audio_file()
171
- expect(second_result).toBe('expected-value-2')
172
- })
173
- ```
145
+ Reset module state at the start of each test (`reset_praise_audio_index()` in `src/lib/data/praise-audio.test.ts`), then assert each call of the sequence in order.
174
146
 
175
147
  ---
176
148
 
177
149
  ## 6. Closing the E2E gate without a human run
178
150
 
179
- No human E2E run closes the gate, and nothing a red local run stopped is let through
180
- (`docs/maintainers/testing-guide-history.md`).
151
+ No human E2E run closes the gate, and nothing a red local run stopped is let through.
181
152
 
182
153
  ### Which result closes the gate
183
154
 
184
- Decided by whether a pull request is open, never by judgement:
155
+ Decided by whether a pull request is open:
185
156
 
186
157
  | Situation | What closes the gate |
187
158
  | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
188
159
  | A pull request is open — `fullrun`, `prrun`, `backlogrun` | The CI E2E job. `pnpm josh followup` waits for the checks and refuses to merge while any of them is non-passing. |
189
160
  | No pull request — `halfrun`'s stop before commit, a completion reported outside any workflow | `pnpm josh test:e2e`, run by **you**, output read by you. |
190
161
 
191
- **Never ask the user to run it, in either row.** A completion report is not allowed to depend on
192
- somebody being at the keyboard; that dependency is what made a `fullrun` chain stall at its last
193
- step. And never report completion on an E2E result nobody read — an unread result is not a pass.
162
+ **Never ask the user to run it**, in either row, and never report completion on an unread E2E result — unread is not a pass.
194
163
 
195
- **Where the application layer ships an integrated gate, prefer it** over a bare `pnpm josh test:e2e`
196
- for the second row: `@joshuafolkken/app-kit` ships `pnpm josh-app verify`, which builds once and
197
- boots once to run the E2E suite and the DAST scan against that one server — and which delegates E2E
198
- to `josh test:e2e`, so the skip below still applies. **Decide by the command list the installed
199
- toolkit prints, not by which toolkit is installed and not by a version number written here**: the
200
- same rule the UI gate uses, and the only one that survives a release.
164
+ Second row: prefer an integrated gate when the installed toolkit's printed command list has one (not by toolkit or version) — `@joshuafolkken/app-kit`'s `pnpm josh-app verify` builds and boots once for E2E and the DAST scan, delegating E2E to `josh test:e2e` (so the skip below applies).
201
165
 
202
166
  ### A project with no E2E suite
203
167
 
204
- `pnpm josh test:e2e` runs through `scripts/test/test-e2e-guard.ts`, which **skips and exits 0**, naming
205
- the reason, when `@playwright/test` is not installed or no `*.e2e.{ts,js}` file exists. That skip is
206
- a **defined** outcome, not an accident: a project with no E2E suite has nothing for the gate to
207
- read, and the run says so out loud. **A skip you did not see printed is not one** — it is an unread
208
- result, and it falls under the previous paragraph.
168
+ `pnpm josh test:e2e` runs through `scripts/test/test-e2e-guard.ts`, which skips and exits 0, naming the reason, when `@playwright/test` is not installed or no `*.e2e.{ts,js}` file exists. That is a defined outcome; **a skip you did not see printed is an unread result**.
209
169
 
210
- **CI decides the same question by the same rule.** The `e2e-detect` job in
211
- `templates/workflows/ci.yml` enables the `e2e` job when any `*.e2e.{ts,js}` exists outside
212
- `node_modules` — the guard's own glob — whatever the config's filename;
213
- `scripts/ci/ci-yml-e2e-detect.test.ts` pins the agreement. CI enables the job on the spec alone, even
214
- where the guard skips for a missing `@playwright/test`, so a spec that cannot run ends red rather than
215
- silently green.
170
+ CI uses the same rule: the `e2e-detect` job in `templates/workflows/ci.yml` enables the `e2e` job when any `*.e2e.{ts,js}` exists outside `node_modules` (the guard's glob), whatever the config's filename — on the spec alone, so a spec that cannot run ends red, not silently green. `scripts/ci/ci-yml-e2e-detect.test.ts` pins the agreement.
216
171
 
217
172
  ### The gate is not weakened
218
173
 
219
- - **A non-passing check keeps the merge closed.** `evaluate_pr_state` returns `success` only when
220
- GitHub reports the pull request `CLEAN` **and** every required check passes; the only exception is a
221
- non-passing set made entirely of CodeRabbit checks (the temporary kit#753 policy). A failing check
222
- ends the wait on the poll that sees it, by name (`PR checks failed (failed checks: E2E).`).
223
- - **Filtering, narrowing or reinterpreting E2E output to get past it is a workaround** —
224
- `CLAUDE.md` → "Cross-package problems"; reporting the filtered result honestly does not make it
225
- compliant.
226
- - **`E2E` stays off the default required-check list**; a project opts in through
227
- `JOSH_REQUIRED_CHECKS`. `scripts/gh/git-pr-checks-e2e-gate.test.ts` pins both properties.
174
+ - **A non-passing check keeps the merge closed.** `evaluate_pr_state` returns `success` only when GitHub reports the pull request `CLEAN` and every required check passes; the only exception is a non-passing set made entirely of CodeRabbit checks (the temporary kit#753 policy). A failing check ends the wait on the poll that sees it, by name (`PR checks failed (failed checks: E2E).`).
175
+ - **Filtering, narrowing or reinterpreting E2E output to get past it is a workaround** (`CLAUDE.md` → "Cross-package problems"), however honestly reported.
176
+ - `E2E` stays off the default required-check list; a project opts in through `JOSH_REQUIRED_CHECKS`. `scripts/gh/git-pr-checks-e2e-gate.test.ts` pins both properties.
@@ -65,8 +65,7 @@ function declared_toolkits(project_root: string): ReadonlyArray<string> {
65
65
  // declares one, so a toolkit shipping several CLIs still resolves to one deterministically.
66
66
  function pick_bin_name(package_name: string, bin: string | Record<string, string>): string {
67
67
  const unscoped = path.basename(package_name)
68
- if (typeof bin === 'string') return unscoped
69
- if (bin[unscoped] !== undefined) return unscoped
68
+ if (typeof bin === 'string' || bin[unscoped] !== undefined) return unscoped
70
69
 
71
70
  return Object.keys(bin)[0] ?? unscoped
72
71
  }
@@ -99,8 +98,7 @@ function has_runnable_cli(project_root: string, bin_name: string): boolean {
99
98
  // spawning it would fail the sync step rather than say what is wrong.
100
99
  function resolve_toolkit(project_root: string, package_name: string): Release | undefined {
101
100
  const bin_name = read_bin_name(project_root, package_name)
102
- if (bin_name === undefined) return undefined
103
- if (!has_runnable_cli(project_root, bin_name)) return undefined
101
+ if (bin_name === undefined || !has_runnable_cli(project_root, bin_name)) return undefined
104
102
 
105
103
  return { package_name, version: LATEST_VERSION, bin_name }
106
104
  }
@@ -4,6 +4,7 @@ import {
4
4
  type AgentEnvironment,
5
5
  type AgentProfile,
6
6
  type AgentRole,
7
+ type ProfileResult,
7
8
  } from './agent-role-profile'
8
9
  import { claude_agent_argv } from './claude-agent-argv'
9
10
  import { codex_agent_argv } from './codex-agent-argv'
@@ -11,6 +12,8 @@ import { codex_agent_argv } from './codex-agent-argv'
11
12
  interface AgentArgv {
12
13
  command: string
13
14
  args: ReadonlyArray<string>
15
+ // The invocation is the last element, so `detached-launch.ts` lets that one break lines.
16
+ ends_with_prompt: true
14
17
  }
15
18
 
16
19
  type AgentArgvResult =
@@ -112,22 +115,35 @@ function resume_argv(
112
115
  : with_profile_in(invocation, resolved.profile, cwd)
113
116
  }
114
117
 
118
+ function profiled_in(invocation: string, resolved: ProfileResult, cwd: string): AgentArgvResult {
119
+ return resolved.kind === 'rejected'
120
+ ? resolved
121
+ : with_profile_in(invocation, resolved.profile, cwd)
122
+ }
123
+
115
124
  function resolve_in(
116
125
  invocation: string,
117
126
  role: AgentRole,
118
127
  cwd: string,
119
128
  environment: AgentEnvironment = process.env,
120
129
  ): AgentArgvResult {
121
- const resolved = agent_role_profile.resolve(role, environment)
130
+ return profiled_in(invocation, agent_role_profile.resolve(role, environment), cwd)
131
+ }
122
132
 
123
- return resolved.kind === 'rejected'
124
- ? resolved
125
- : with_profile_in(invocation, resolved.profile, cwd)
133
+ // The reviewer for one review round, so a verification pass runs on its own profile.
134
+ function review_in(
135
+ invocation: string,
136
+ round: number,
137
+ cwd: string,
138
+ environment: AgentEnvironment = process.env,
139
+ ): AgentArgvResult {
140
+ return profiled_in(invocation, agent_role_profile.resolve_review(round, environment), cwd)
126
141
  }
127
142
 
128
143
  const agent_argv = {
129
144
  resolve,
130
145
  resolve_in,
146
+ review_in,
131
147
  resume_argv,
132
148
  with_profile,
133
149
  with_profile_in,
@@ -20,6 +20,28 @@ function read_exit_record(transcript: string): ClaudeResultEvent | undefined {
20
20
  .findLast((record) => record !== undefined)
21
21
  }
22
22
 
23
- const agent_exit_record = { read_exit_record }
23
+ function event_type(line: string): unknown {
24
+ const value = json_value.parse_or_undefined(line)
25
+
26
+ return typeof value === 'object' && value !== null && 'type' in value ? value.type : undefined
27
+ }
28
+
29
+ // The Claude stream events a turn is made of — a Codex transcript's own (`turn.completed`, `error`)
30
+ // are none of these, so one never reads as killed.
31
+ const TURN_TYPES: ReadonlySet<unknown> = new Set(['system', 'assistant', 'user', 'stream_event'])
32
+
33
+ // Whether a Claude session was killed inside a turn: its last event is a turn's, not a `result`. A
34
+ // finished turn ends on a result; a background task can wake the session for another turn, and a
35
+ // process killed before that turn's result leaves other events last.
36
+ function is_mid_turn(transcript: string): boolean {
37
+ const last = transcript
38
+ .split('\n')
39
+ .map((line) => event_type(line))
40
+ .findLast((type) => type !== undefined)
41
+
42
+ return TURN_TYPES.has(last)
43
+ }
44
+
45
+ const agent_exit_record = { is_mid_turn, read_exit_record }
24
46
 
25
47
  export { agent_exit_record }
@@ -1,11 +1,8 @@
1
1
  import { agent_session_environment } from '#scripts/josh/agent-session-environment'
2
2
  import { z } from 'zod'
3
+ import { agent_safe_text } from './agent-safe-text'
3
4
 
4
- const FIRST_PRINTABLE_CODE = 0x20
5
- const DELETE_CODE = 0x7f
6
- const FIRST_CODE_POINT = 0
7
5
  const EMPTY_LENGTH = 0
8
- const MAX_VALUE_LENGTH = 4096
9
6
 
10
7
  const ROLE_SCHEMA = z.enum(['scheduler', 'worker', 'reviewer'])
11
8
  const PROVIDER_SCHEMA = z.enum(['anthropic', 'openai'])
@@ -32,6 +29,18 @@ type ProfileResult = { kind: 'profile'; profile: AgentProfile } | { kind: 'rejec
32
29
  type Rejected = Extract<ProfileResult, { kind: 'rejected' }>
33
30
  type EffortResult = Rejected | { kind: 'effort'; effort: AgentEffort }
34
31
  type ProviderResult = Rejected | { kind: 'provider'; provider: AgentProvider; is_default?: true }
32
+ type OverrideField = 'model' | 'effort'
33
+
34
+ interface Override {
35
+ key: string
36
+ value: string | undefined
37
+ }
38
+
39
+ // What a resolution is keyed on beyond the role: the run phase (effort) and the review round (profile).
40
+ interface AgentStage {
41
+ phase?: string | undefined
42
+ round?: number
43
+ }
35
44
 
36
45
  const SCHEDULER: AgentRole = 'scheduler'
37
46
  const WORKER: AgentRole = 'worker'
@@ -72,6 +81,22 @@ const OPENAI_PROFILES: Readonly<Record<AgentRole, AgentProfile>> = {
72
81
 
73
82
  const PROVIDER_PROFILES = { anthropic: DEFAULT_PROFILES, openai: OPENAI_PROFILES }
74
83
 
84
+ // **The verification pass resolves a profile of its own** (joshuafolkken/kit#3926). A round after the
85
+ // first only checks that the first round's findings were fixed, so its model and effort are set apart:
86
+ // its own keys win, then the reviewer's, then its own default.
87
+ const FIRST_REVIEW_ROUND = 1
88
+ // Sonnet holds the verification pass by the eval criterion: on every `verification-fix-*` scenario its
89
+ // pass/fail equals Opus's (100% agreement), and Opus passes all 4.
90
+ const VERIFICATION_MODEL = 'claude-sonnet-5-5'
91
+ const VERIFICATION_PROFILES: Readonly<Record<AgentProvider, AgentProfile>> = {
92
+ anthropic: { ...DEFAULT_PROFILES.reviewer, model: VERIFICATION_MODEL },
93
+ openai: OPENAI_PROFILES.reviewer,
94
+ }
95
+ const VERIFICATION_KEYS = {
96
+ model: 'JOSH_REVIEWER_VERIFICATION_MODEL',
97
+ effort: 'JOSH_REVIEWER_VERIFICATION_EFFORT',
98
+ }
99
+
75
100
  // **Effort as a function of the run phase, not the role alone**. For the first
76
101
  // merge only the mechanical ship/bookkeeping region is lowered: the pre-gate resume drives the gate,
77
102
  // commit, PR and merge, applying fixes the gate has already named — work delegation.md calls the opposite
@@ -93,19 +118,7 @@ const ENV_KEYS: Readonly<Record<AgentRole, { model: string; effort: string }>> =
93
118
 
94
119
  const LEGACY_WORKER_KEYS = { model: 'JOSH_LANE_MODEL', effort: 'JOSH_LANE_EFFORT' }
95
120
 
96
- function is_control_character(character: string): boolean {
97
- const code = character.codePointAt(FIRST_CODE_POINT) ?? FIRST_PRINTABLE_CODE
98
-
99
- return code < FIRST_PRINTABLE_CODE || code === DELETE_CODE
100
- }
101
-
102
- function is_safe_value(value: string): boolean {
103
- if (value.length === EMPTY_LENGTH || value.length > MAX_VALUE_LENGTH) return false
104
-
105
- for (const character of value) if (is_control_character(character)) return false
106
-
107
- return true
108
- }
121
+ const { MAX_VALUE_LENGTH, is_safe_prompt, is_safe_value, printable } = agent_safe_text
109
122
 
110
123
  function trimmed(value: string | undefined): string | undefined {
111
124
  const result = value?.trim()
@@ -113,44 +126,53 @@ function trimmed(value: string | undefined): string | undefined {
113
126
  return result === undefined || result.length === EMPTY_LENGTH ? undefined : result
114
127
  }
115
128
 
116
- function override_value(
129
+ function is_verification(role: AgentRole, round: number | undefined): boolean {
130
+ return role === REVIEWER && round !== undefined && round > FIRST_REVIEW_ROUND
131
+ }
132
+
133
+ // The keys an override is read from, first match wins: a worker falls back to its legacy lane keys, and
134
+ // a verification round reads its own keys ahead of the reviewer's.
135
+ function override_keys(
117
136
  role: AgentRole,
118
- field: 'model' | 'effort',
119
- environment: AgentEnvironment,
120
- ): { key: string; value: string | undefined } {
137
+ field: OverrideField,
138
+ round: number | undefined,
139
+ ): readonly [string, ...Array<string>] {
121
140
  const key = ENV_KEYS[role][field]
122
- const value = trimmed(environment[key])
123
- if (value !== undefined) return { key, value }
124
- if (role !== WORKER) return { key, value: undefined }
141
+ if (role === WORKER) return [key, LEGACY_WORKER_KEYS[field]]
125
142
 
126
- const legacy = LEGACY_WORKER_KEYS[field]
127
- const legacy_value = trimmed(environment[legacy])
143
+ return is_verification(role, round) ? [VERIFICATION_KEYS[field], key] : [key]
144
+ }
128
145
 
129
- return legacy_value === undefined
130
- ? { key, value: undefined }
131
- : { key: legacy, value: legacy_value }
146
+ function override_value(
147
+ role: AgentRole,
148
+ field: OverrideField,
149
+ environment: AgentEnvironment,
150
+ round?: number,
151
+ ): Override {
152
+ const keys = override_keys(role, field, round)
153
+ const key = keys.find((candidate) => trimmed(environment[candidate]) !== undefined)
154
+
155
+ return key === undefined
156
+ ? { key: keys[0], value: undefined }
157
+ : { key, value: trimmed(environment[key]) }
132
158
  }
133
159
 
134
160
  function model_override(
135
161
  role: AgentRole,
136
162
  environment: AgentEnvironment,
137
163
  provider: AgentProvider,
138
- ): { key: string; value: string | undefined } {
139
- const key = ENV_KEYS[role].model
140
-
164
+ round: number | undefined,
165
+ ): Override {
141
166
  return provider === OPENAI_PROVIDER
142
- ? { key, value: undefined }
143
- : override_value(role, 'model', environment)
167
+ ? { key: ENV_KEYS[role].model, value: undefined }
168
+ : override_value(role, 'model', environment, round)
144
169
  }
145
170
 
146
171
  function rejection(key: string, value: string, expected: string): Rejected {
147
172
  return { kind: 'rejected', note: `${key}=${value} ${expected}, so the agent was not started` }
148
173
  }
149
174
 
150
- function resolved_effort(
151
- override: { key: string; value: string | undefined },
152
- fallback: AgentEffort,
153
- ): EffortResult {
175
+ function resolved_effort(override: Override, fallback: AgentEffort): EffortResult {
154
176
  const parsed = EFFORT_SCHEMA.safeParse(override.value ?? fallback)
155
177
 
156
178
  return parsed.success
@@ -290,17 +312,27 @@ function with_phase_effort(
290
312
  return { ...profile, effort: overridden_effort(profile, phase, environment) }
291
313
  }
292
314
 
293
- function resolve(
315
+ function stage_defaults(
316
+ provider: AgentProvider,
294
317
  role: AgentRole,
295
- environment: AgentEnvironment = process.env,
296
- phase?: string,
318
+ round: number | undefined,
319
+ ): AgentProfile {
320
+ return is_verification(role, round)
321
+ ? VERIFICATION_PROFILES[provider]
322
+ : PROVIDER_PROFILES[provider][role]
323
+ }
324
+
325
+ function resolve_stage(
326
+ role: AgentRole,
327
+ environment: AgentEnvironment,
328
+ stage: AgentStage,
297
329
  ): ProfileResult {
298
330
  const selected = resolve_provider(environment)
299
331
  if (selected.kind === 'rejected') return selected
300
- const defaults = PROVIDER_PROFILES[selected.provider][role]
301
- const model = model_override(role, environment, selected.provider)
302
- const effort = override_value(role, 'effort', environment)
303
- const resolved = resolved_effort(effort, phase_effort(role, phase, defaults.effort))
332
+ const defaults = stage_defaults(selected.provider, role, stage.round)
333
+ const model = model_override(role, environment, selected.provider, stage.round)
334
+ const effort = override_value(role, 'effort', environment, stage.round)
335
+ const resolved = resolved_effort(effort, phase_effort(role, stage.phase, defaults.effort))
304
336
 
305
337
  if (resolved.kind === 'rejected') return resolved
306
338
 
@@ -314,10 +346,29 @@ function resolve(
314
346
  return validate(profile, model.key)
315
347
  }
316
348
 
349
+ function resolve(
350
+ role: AgentRole,
351
+ environment: AgentEnvironment = process.env,
352
+ phase?: string,
353
+ ): ProfileResult {
354
+ return resolve_stage(role, environment, { phase })
355
+ }
356
+
357
+ // The reviewer for one review round: round 1 as `resolve(REVIEWER)` does, any later round the
358
+ // verification pass's own profile.
359
+ function resolve_review(round: number, environment: AgentEnvironment = process.env): ProfileResult {
360
+ return resolve_stage(REVIEWER, environment, { round })
361
+ }
362
+
317
363
  function describe(profile: AgentProfile): string {
318
364
  return `provider=${profile.provider} role=${profile.role} model=${profile.model} effort=${profile.effort}`
319
365
  }
320
366
 
367
+ // The run-log line a review launch writes, so each round's model is on record beside its round.
368
+ function describe_review(round: number, profile: AgentProfile): string {
369
+ return `review round ${String(round)} · ${describe(profile)}`
370
+ }
371
+
321
372
  function parse(value: unknown): AgentProfile | undefined {
322
373
  const parsed = PROFILE_SCHEMA.safeParse(value)
323
374
 
@@ -335,15 +386,20 @@ const agent_role_profile = {
335
386
  PROFILE_SCHEMA,
336
387
  REVIEWER,
337
388
  SCHEDULER,
389
+ VERIFICATION_PROFILES,
338
390
  WORKER,
339
391
  default_notice,
340
392
  describe,
393
+ describe_review,
341
394
  handoff_environment,
342
395
  has_completion_callback,
396
+ is_safe_prompt,
343
397
  is_safe_value,
344
398
  parse,
399
+ printable,
345
400
  resolve,
346
401
  resolve_provider,
402
+ resolve_review,
347
403
  warn_of_default,
348
404
  with_phase_effort,
349
405
  }