@joshuafolkken/kit 1.1178.0 → 1.1240.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 (442) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/settings.json +2 -2
  3. package/.claude/skills/dependency-update/SKILL.md +4 -8
  4. package/.claude/skills/epic-commands/SKILL.md +2 -2
  5. package/.claude/skills/epic-commands/epic-bundle.md +1 -1
  6. package/.claude/skills/workflow-commands/SKILL.md +2 -4
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +7 -3
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +23 -2
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +9 -4
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +21 -13
  13. package/.claude/skills/workflow-commands/chain-rule.md +1 -1
  14. package/.claude/skills/workflow-commands/delegation.md +2 -2
  15. package/.claude/skills/workflow-commands/followup.md +10 -0
  16. package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
  17. package/.claude/skills/workflow-commands/halfrun.md +1 -1
  18. package/.claude/skills/workflow-commands/issue-scout.md +1 -1
  19. package/.claude/skills/workflow-commands/kickoff.md +2 -1
  20. package/.claude/skills/workflow-commands/latest-gate.md +1 -1
  21. package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
  22. package/.claude/skills/workflow-commands/pre-gate-cut.md +6 -1
  23. package/.claude/skills/workflow-commands/prerequisite.md +2 -2
  24. package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
  25. package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
  26. package/.codex/hooks.json +2 -2
  27. package/.cursorrules +6 -41
  28. package/.github/actions/setup-node/action.yml +1 -1
  29. package/.github/workflows/auto-tag.yml +3 -4
  30. package/.github/workflows/ci.yml +32 -31
  31. package/.github/workflows/dependabot-auto-merge.yml +13 -6
  32. package/.github/workflows/publish.yml +50 -4
  33. package/.github/workflows/sonar-qube.yml +3 -0
  34. package/.pnpmfile.mjs +4 -3
  35. package/.prettierignore +8 -2
  36. package/CLAUDE.md +18 -20
  37. package/README.md +44 -38
  38. package/SECURITY.md +15 -1
  39. package/dist/CLAUDE.md +18 -20
  40. package/dist/commands/document-section-cli.js +734 -0
  41. package/dist/hooks/audit-provision.js +346 -0
  42. package/dist/hooks/batch-guard.js +5 -4
  43. package/dist/hooks/{chunk-G6KRCKEC.js → chunk-4TZVB44A.js} +496 -378
  44. package/dist/hooks/chunk-5VNDSNG3.js +12 -0
  45. package/dist/hooks/chunk-CDY4NFQG.js +15 -0
  46. package/dist/hooks/chunk-ELCN5LCH.js +135 -0
  47. package/dist/hooks/{chunk-VQLLNFNT.js → chunk-F3ELQ2AF.js} +3 -3
  48. package/dist/hooks/{chunk-I4XSL4TT.js → chunk-GPTFMK5E.js} +1 -1
  49. package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
  50. package/dist/hooks/{chunk-UAGLLIGZ.js → chunk-KHGB5S3Y.js} +7 -6
  51. package/dist/hooks/{chunk-GDS5N2KB.js → chunk-KHX4U3BG.js} +735 -683
  52. package/dist/hooks/{chunk-VESKRQOF.js → chunk-KRBERBBI.js} +3 -3
  53. package/dist/hooks/{chunk-DO6NWWHU.js → chunk-QVSWU24O.js} +4 -4
  54. package/dist/hooks/{chunk-S6X6WYKR.js → chunk-WK2XSHSI.js} +1643 -923
  55. package/dist/hooks/{chunk-ZAL3NRGL.js → chunk-XZYKEM6J.js} +25 -10
  56. package/dist/hooks/{chunk-32GQTL4F.js → chunk-ZQGD4GBY.js} +4 -2
  57. package/dist/hooks/codex-hook-adapter.js +14 -12
  58. package/dist/hooks/duplicate-read-guard.js +6 -5
  59. package/dist/hooks/format-edited.js +4 -3
  60. package/dist/hooks/inputs.json +203 -179
  61. package/dist/hooks/investigation-guard.js +6 -5
  62. package/dist/hooks/pretool-guard.js +12 -10
  63. package/dist/hooks/session-lang.js +2 -4
  64. package/dist/hooks/stop-guard.js +66 -72
  65. package/dist/josh.js +1088 -78
  66. package/dist/version/index.d.ts +7 -2
  67. package/dist/version/index.js +152 -75
  68. package/eslint/base.js +23 -1
  69. package/eslint/rules/code-quality.js +3 -0
  70. package/eslint/rules/early-return-one-liner.js +98 -0
  71. package/eslint/rules/unicorn.js +16 -1
  72. package/package.json +7 -6
  73. package/pnpm-workspace.yaml +9 -4
  74. package/prettier/basic.d.ts +3 -1
  75. package/prettier/index.d.ts +3 -1
  76. package/prettier/static.d.ts +3 -1
  77. package/prompts/coding-standards.md +1 -1
  78. package/prompts/collaboration-workflow/file-edits.md +4 -4
  79. package/prompts/collaboration-workflow/glossary.md +1 -1
  80. package/prompts/collaboration-workflow/issue-template.md +1 -0
  81. package/prompts/collaboration-workflow/operating-rules.md +17 -9
  82. package/prompts/collaboration-workflow/overview.md +2 -2
  83. package/prompts/collaboration-workflow/principles.md +14 -12
  84. package/prompts/collaboration-workflow/report-format.md +1 -1
  85. package/prompts/collaboration-workflow/residency.md +1 -1
  86. package/prompts/collaboration-workflow/rule-delivery.md +6 -6
  87. package/prompts/collaboration-workflow/shell-body.md +6 -6
  88. package/prompts/collaboration-workflow/turn-batching.md +1 -1
  89. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
  90. package/prompts/collaboration-workflow/wip-cap.md +10 -10
  91. package/prompts/review-rubric.md +1 -2
  92. package/prompts/review.md +1 -1
  93. package/scripts/agent/agent-headless.ts +29 -0
  94. package/scripts/agent/agent-launch-environment.ts +6 -2
  95. package/scripts/agent/codex-agent-argv.ts +2 -1
  96. package/scripts/auto-ok/auto-ok-cli.ts +1 -1
  97. package/scripts/backlog/backlog-arrival.ts +3 -3
  98. package/scripts/backlog/backlog-budget.ts +1 -1
  99. package/scripts/backlog/backlog-drive-args.ts +135 -0
  100. package/scripts/backlog/backlog-drive-cli.ts +11 -137
  101. package/scripts/backlog/backlog-drive-launch.ts +39 -0
  102. package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
  103. package/scripts/backlog/backlog-drive-named.ts +3 -3
  104. package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
  105. package/scripts/backlog/backlog-drive-owner.ts +1 -1
  106. package/scripts/backlog/backlog-drive-restore.ts +1 -1
  107. package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
  108. package/scripts/backlog/backlog-drive.ts +1 -1
  109. package/scripts/backlog/backlog-next.ts +16 -5
  110. package/scripts/backlog/backlog-offer-cli.ts +6 -6
  111. package/scripts/backlog/backlog-offer.ts +1 -1
  112. package/scripts/backlog/backlog-plan-cli.ts +1 -1
  113. package/scripts/backlog/backlog-pool.ts +57 -11
  114. package/scripts/backlog/backlog-ready.ts +1 -1
  115. package/scripts/backlog/backlog-scope.ts +1 -1
  116. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  117. package/scripts/backlog/backlog-stalled.ts +1 -1
  118. package/scripts/build/build-codex-hooks.ts +3 -1
  119. package/scripts/build/build-commands.ts +66 -0
  120. package/scripts/build/build-hooks.ts +2 -1
  121. package/scripts/build/build-library.ts +12 -3
  122. package/scripts/build/build.ts +70 -0
  123. package/scripts/build/local-bin.ts +3 -13
  124. package/scripts/delegation/investigation-reads.ts +3 -1
  125. package/scripts/discovery/repo-origin.ts +5 -5
  126. package/scripts/doctor/doctor-consumer.ts +2 -2
  127. package/scripts/doctor/doctor-io.ts +2 -3
  128. package/scripts/document/capped-print-part.ts +30 -0
  129. package/scripts/document/capped-print.ts +3 -4
  130. package/scripts/document/document-byte-budget.ts +11 -2
  131. package/scripts/document/document-scan.ts +117 -0
  132. package/scripts/document/document-section.ts +143 -16
  133. package/scripts/document/entry-read-budget.ts +5 -3
  134. package/scripts/document/entry-read-set.ts +10 -89
  135. package/scripts/document/read-set-cost.ts +98 -0
  136. package/scripts/epic/epic-add-body.ts +1 -0
  137. package/scripts/epic/epic-audit-checks.ts +1 -1
  138. package/scripts/epic/epic-audit-rationale.ts +4 -3
  139. package/scripts/epic/epic-bundle-cli.ts +14 -3
  140. package/scripts/epic/epic-bundle-poll.ts +41 -0
  141. package/scripts/epic/epic-busy.ts +1 -1
  142. package/scripts/epic/epic-candidate-confirm.ts +3 -2
  143. package/scripts/epic/epic-classify.ts +1 -1
  144. package/scripts/epic/epic-cli-argv.ts +78 -46
  145. package/scripts/epic/epic-cli-remove.ts +11 -19
  146. package/scripts/epic/epic-cli.ts +74 -73
  147. package/scripts/epic/epic-close-comment.ts +1 -1
  148. package/scripts/epic/epic-close.ts +1 -1
  149. package/scripts/epic/epic-cross-repo.ts +5 -5
  150. package/scripts/epic/epic-graph.ts +1 -1
  151. package/scripts/epic/epic-index.ts +1 -1
  152. package/scripts/epic/epic-issue.ts +2 -6
  153. package/scripts/epic/epic-lane-offer.ts +2 -2
  154. package/scripts/epic/epic-next.ts +29 -42
  155. package/scripts/epic/epic-solo-stale.ts +1 -1
  156. package/scripts/epic/epic-triage.ts +8 -4
  157. package/scripts/epic/epic-validate.ts +1 -1
  158. package/scripts/followup/git-followup-finish.ts +2 -2
  159. package/scripts/followup/git-followup-label.ts +2 -2
  160. package/scripts/followup/git-followup-merged.ts +2 -2
  161. package/scripts/followup/git-pr-followup-checks.ts +142 -0
  162. package/scripts/followup/git-pr-followup.ts +4 -129
  163. package/scripts/gate/core-admission.ts +162 -0
  164. package/scripts/gate/core-budget.ts +190 -68
  165. package/scripts/gate/gate-plan.ts +80 -7
  166. package/scripts/gate/gate-report.ts +28 -9
  167. package/scripts/gate/gate-skip.ts +38 -7
  168. package/scripts/gate/hook-gate-reuse.ts +12 -10
  169. package/scripts/gate/machine-capacity.ts +175 -0
  170. package/scripts/gate/pre-commit-type-check.ts +4 -1
  171. package/scripts/gate/project-checks.ts +6 -9
  172. package/scripts/gate/type-check-step.ts +2 -3
  173. package/scripts/gate/verification-gate.ts +34 -10
  174. package/scripts/gh/git-gh-api-path.ts +2 -2
  175. package/scripts/gh/git-gh-issue-rest.ts +3 -3
  176. package/scripts/gh/git-gh-issue-write.ts +53 -7
  177. package/scripts/gh/git-gh-pr-read.ts +64 -19
  178. package/scripts/gh/git-gh-pr-rest.ts +1 -1
  179. package/scripts/gh/git-gh-pr.ts +5 -1
  180. package/scripts/gh/git-pr-ai-review.ts +1 -1
  181. package/scripts/gh/git-pr-checks-eval.ts +1 -3
  182. package/scripts/gh/git-pr-checks-parse.ts +1 -1
  183. package/scripts/gh/git-pr-checks-watch.ts +6 -5
  184. package/scripts/gh/git-pr-checks.ts +7 -5
  185. package/scripts/gh/git-pr-coderabbit.ts +1 -1
  186. package/scripts/gh/git-pr-error.ts +1 -3
  187. package/scripts/gh/git-pr.ts +9 -13
  188. package/scripts/gh/github-issue-url.ts +2 -2
  189. package/scripts/git/constants.ts +2 -6
  190. package/scripts/git/git-command.ts +20 -218
  191. package/scripts/git/git-diff-reads.ts +225 -0
  192. package/scripts/git/git-error.ts +4 -12
  193. package/scripts/git/git-pre-push-hook.ts +139 -0
  194. package/scripts/git/git-prompt.ts +2 -6
  195. package/scripts/git/git-spawn-sync.ts +3 -1
  196. package/scripts/git/git-spawn.ts +1 -1
  197. package/scripts/git/git-staging.ts +4 -6
  198. package/scripts/git/git-status.ts +3 -9
  199. package/scripts/hooks/codex-hook-adapter.ts +1 -1
  200. package/scripts/hooks/edited-cspell.ts +2 -4
  201. package/scripts/hooks/format-edited-file.ts +3 -12
  202. package/scripts/hooks/pre-push-unit.ts +31 -9
  203. package/scripts/hooks/reserved-run.ts +2 -2
  204. package/scripts/hooks/run-hook.sh +5 -4
  205. package/scripts/hooks/stop-guard.ts +5 -3
  206. package/scripts/init/init-actions.ts +0 -5
  207. package/scripts/init/init-ai-copy.ts +13 -2
  208. package/scripts/init/init-logic-json-merge.ts +19 -0
  209. package/scripts/init/init-logic-secretlint.ts +7 -7
  210. package/scripts/init/init-logic-workspace.ts +41 -2
  211. package/scripts/init/init-logic.ts +16 -26
  212. package/scripts/init/init.ts +10 -16
  213. package/scripts/init/initial-commit.ts +4 -4
  214. package/scripts/init/kit-development-versions.ts +40 -0
  215. package/scripts/init/kit-setup-state.ts +2 -2
  216. package/scripts/init/package-manager-pin.ts +35 -0
  217. package/scripts/init/start-exec.ts +68 -19
  218. package/scripts/init/start-setup-pr.ts +9 -9
  219. package/scripts/init/start-steps.ts +6 -10
  220. package/scripts/issue/defect-rate-cli.ts +1 -1
  221. package/scripts/issue/git-next-issues.ts +1 -1
  222. package/scripts/issue/issue-auto-ok.ts +32 -7
  223. package/scripts/issue/issue-backlinks-cli.ts +2 -2
  224. package/scripts/issue/issue-backlinks.ts +9 -9
  225. package/scripts/issue/issue-comment-cli.ts +19 -13
  226. package/scripts/issue/issue-file-cli.ts +72 -22
  227. package/scripts/issue/issue-file.ts +29 -1
  228. package/scripts/issue/issue-labels.ts +13 -0
  229. package/scripts/issue/issue-release-cli.ts +155 -0
  230. package/scripts/issue/issue-release.ts +58 -0
  231. package/scripts/issue/issue-scout-closed.ts +1 -1
  232. package/scripts/issue/issue-state-cli.ts +30 -74
  233. package/scripts/josh/josh-command-map.ts +2 -0
  234. package/scripts/josh/josh-command-types.ts +14 -0
  235. package/scripts/josh/josh-commands-ai.ts +4 -4
  236. package/scripts/josh/josh-commands-backlog.ts +4 -1
  237. package/scripts/josh/josh-commands-development.ts +18 -0
  238. package/scripts/josh/josh-commands-document.ts +1 -0
  239. package/scripts/josh/josh-commands-issue.ts +8 -1
  240. package/scripts/josh/josh-commands-lane.ts +24 -1
  241. package/scripts/josh/josh-commands-maintenance.ts +6 -6
  242. package/scripts/josh/josh-commands-run.ts +12 -7
  243. package/scripts/josh/josh-in-process.ts +28 -9
  244. package/scripts/josh/josh-logic.ts +34 -15
  245. package/scripts/josh/process-identity.ts +1 -3
  246. package/scripts/josh/session-language-cli.ts +3 -4
  247. package/scripts/lane/lane-await.ts +5 -1
  248. package/scripts/lane/lane-child-invocation.ts +2 -2
  249. package/scripts/lane/lane-cli.ts +12 -4
  250. package/scripts/lane/lane-dispatch.ts +14 -10
  251. package/scripts/lane/lane-environment.ts +5 -1
  252. package/scripts/lane/lane-handoff.ts +1 -1
  253. package/scripts/lane/lane-install.ts +2 -13
  254. package/scripts/lane/lane-launch-cli.ts +15 -1
  255. package/scripts/lane/lane-ledger.ts +145 -0
  256. package/scripts/lane/lane-leftover.ts +44 -30
  257. package/scripts/lane/lane-load.ts +108 -0
  258. package/scripts/lane/lane-measure-cli.ts +149 -0
  259. package/scripts/lane/lane-occupancy.ts +3 -0
  260. package/scripts/lane/lane-open.ts +5 -5
  261. package/scripts/lane/lane-paths.ts +13 -0
  262. package/scripts/lane/lane-reap.ts +1 -3
  263. package/scripts/lane/lane-registry.ts +11 -0
  264. package/scripts/lane/lane-relaunch.ts +1 -1
  265. package/scripts/lane/lane-stats.ts +224 -0
  266. package/scripts/lane/lane-stray.ts +140 -0
  267. package/scripts/lane/lane-vacant.ts +36 -0
  268. package/scripts/lane/openai-lane-ship.ts +2 -2
  269. package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
  270. package/scripts/lane/openai-lane-supervisor.ts +7 -165
  271. package/scripts/lane/openai-review-broker.ts +2 -4
  272. package/scripts/lib/ancestor-directories.ts +35 -0
  273. package/scripts/lib/animation-helpers.ts +2 -2
  274. package/scripts/lib/buffered-process.ts +3 -7
  275. package/scripts/lib/cli-flags.ts +71 -1
  276. package/scripts/lib/spawn-exit.ts +2 -2
  277. package/scripts/lib/timeouts.ts +35 -2
  278. package/scripts/lines/line-budget.ts +4 -6
  279. package/scripts/notify/telegram-test.ts +6 -0
  280. package/scripts/observations/observation-ledger-line.ts +6 -6
  281. package/scripts/observations/observation-ledger-migrate.ts +55 -4
  282. package/scripts/observations/observation-ledger.ts +26 -7
  283. package/scripts/overrides/lockfile-overrides.ts +1 -1
  284. package/scripts/overrides/overrides-logic.ts +1 -1
  285. package/scripts/propagate/propagate-git.ts +1 -1
  286. package/scripts/propagate/propagate-publish.ts +2 -2
  287. package/scripts/propagate/propagate-steps.ts +20 -15
  288. package/scripts/propagate/propagate.ts +36 -27
  289. package/scripts/refactor/import-graph.ts +18 -1
  290. package/scripts/refactor/refactor-lint.ts +4 -4
  291. package/scripts/release/github-release-environment.ts +1 -0
  292. package/scripts/release/github-release-runs.ts +115 -0
  293. package/scripts/release/github-release.ts +13 -67
  294. package/scripts/repo/repository-labels.ts +2 -2
  295. package/scripts/repo/required-checks-logic.ts +1 -3
  296. package/scripts/review/live-evidence.ts +2 -4
  297. package/scripts/review/review-brief-cli.ts +1 -1
  298. package/scripts/review/review-finding-ledger.ts +1 -1
  299. package/scripts/review/review-findings-cli.ts +1 -3
  300. package/scripts/rules/decision-oracle-batch.ts +128 -0
  301. package/scripts/rules/decision-oracle-tokens.ts +21 -0
  302. package/scripts/rules/decision-oracle.ts +8 -120
  303. package/scripts/rules/delivered-rules.ts +26 -182
  304. package/scripts/rules/direct-filing.ts +1 -1
  305. package/scripts/rules/early-heartbeat.ts +3 -3
  306. package/scripts/rules/git-argv.ts +1 -1
  307. package/scripts/rules/git-force.ts +1 -1
  308. package/scripts/rules/implementation-cut.ts +2 -2
  309. package/scripts/rules/index-guard.ts +1 -1
  310. package/scripts/rules/issue-comments.ts +118 -0
  311. package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
  312. package/scripts/rules/lane-background.ts +2 -2
  313. package/scripts/rules/lane-park.ts +18 -0
  314. package/scripts/rules/lane-split-park.ts +128 -0
  315. package/scripts/rules/oracle-firing.ts +1 -1
  316. package/scripts/rules/poll-loop.ts +1 -1
  317. package/scripts/rules/pre-gate-cut.ts +1 -1
  318. package/scripts/rules/protected-files.ts +59 -4
  319. package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
  320. package/scripts/rules/shell-body-trigger.ts +1 -1
  321. package/scripts/rules/stop-rules.ts +18 -7
  322. package/scripts/rules/test-declared-commit.ts +4 -4
  323. package/scripts/rules/wip-cap.ts +63 -0
  324. package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
  325. package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
  326. package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
  327. package/scripts/run/{run-carry.ts → carry/run-carry.ts} +1 -1
  328. package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
  329. package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +18 -5
  330. package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
  331. package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
  332. package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
  333. package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +1 -0
  334. package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
  335. package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
  336. package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +20 -2
  337. package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
  338. package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
  339. package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
  340. package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
  341. package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
  342. package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
  343. package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
  344. package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
  345. package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
  346. package/scripts/run/run-ending.ts +2 -2
  347. package/scripts/run/run-halfrun-resume.ts +6 -1
  348. package/scripts/run/run-headless.ts +1 -1
  349. package/scripts/run/run-invocation.ts +85 -84
  350. package/scripts/run/run-liveness.ts +1 -1
  351. package/scripts/run/run-parent-cut-hook.ts +1 -1
  352. package/scripts/run/run-preflight.ts +6 -6
  353. package/scripts/run/run-prep-cli.ts +1 -1
  354. package/scripts/run/run-prrun-resume.ts +1 -1
  355. package/scripts/run/run-report-cli.ts +4 -4
  356. package/scripts/run/run-report.ts +2 -2
  357. package/scripts/run/run-status-cli.ts +1 -1
  358. package/scripts/run/run-step-cli.ts +3 -3
  359. package/scripts/run/run-step.ts +7 -4
  360. package/scripts/run/run-stop-notify.ts +1 -1
  361. package/scripts/run/run-stranded-detect.ts +4 -4
  362. package/scripts/run/run-stranded.ts +2 -2
  363. package/scripts/run/run-watcher-guard-cli.ts +1 -1
  364. package/scripts/run/run-watcher-guard.ts +5 -3
  365. package/scripts/run/run-watcher-hook.ts +1 -1
  366. package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +2 -2
  367. package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
  368. package/scripts/run/ship/run-ship-lock.ts +50 -0
  369. package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
  370. package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
  371. package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +4 -3
  372. package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
  373. package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
  374. package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +11 -12
  375. package/scripts/run/ship/run-ship-stop-text.ts +13 -0
  376. package/scripts/run/{run-ship-sync.ts → ship/run-ship-sync.ts} +25 -11
  377. package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
  378. package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +25 -11
  379. package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +58 -7
  380. package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +59 -8
  381. package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
  382. package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +13 -15
  383. package/scripts/run/wake/run-wake-restart.ts +58 -0
  384. package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
  385. package/scripts/run/{run-wake.ts → wake/run-wake.ts} +16 -65
  386. package/scripts/safe-chain/preinstall-command.ts +20 -21
  387. package/scripts/safe-chain/project-config.ts +19 -15
  388. package/scripts/sync/sync-ai-files.ts +1 -1
  389. package/scripts/sync/sync-configs.ts +2 -1
  390. package/scripts/sync/sync.ts +15 -2
  391. package/scripts/test/unit-worker-share.ts +1 -0
  392. package/scripts/time-runtime/time-background.ts +5 -1
  393. package/scripts/time-runtime/time-instant.ts +3 -1
  394. package/scripts/time-runtime/time-spans.ts +6 -139
  395. package/scripts/time-runtime/time-tool-call.ts +160 -0
  396. package/scripts/version/effective-upstream.ts +2 -20
  397. package/scripts/version/latest-corepack.ts +124 -93
  398. package/scripts/version/latest-regression.ts +1 -1
  399. package/scripts/version/latest-update.ts +4 -4
  400. package/scripts/version/npm-registry.ts +20 -5
  401. package/scripts/version/publishable-range-check.ts +2 -2
  402. package/scripts/version/publishable-range.ts +1 -1
  403. package/scripts/version/release-age.ts +48 -38
  404. package/scripts/version/release-hold.ts +1 -1
  405. package/scripts/version/safe-chain-version-update.ts +40 -0
  406. package/scripts/version/upgrade-shell-command.ts +48 -8
  407. package/scripts/version/version-check-logic.ts +23 -14
  408. package/scripts/version/version-commands.ts +23 -11
  409. package/scripts/version/version-remote.ts +28 -13
  410. package/templates/pnpm-workspace.basic.yaml +4 -0
  411. package/templates/workflows/dependabot-auto-merge.yml +5 -5
  412. package/templates/workflows/github-release.yml +6 -0
  413. package/scripts/build/build-config-merge.ts +0 -27
  414. package/scripts/build/build-managed-marker.ts +0 -27
  415. package/scripts/build/build-self-sync-guard.ts +0 -27
  416. package/scripts/build/build-version.ts +0 -26
  417. package/scripts/version/preinstall-version-update.ts +0 -86
  418. /package/scripts/git/{schemas.ts → git-schemas.ts} +0 -0
  419. /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
  420. /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
  421. /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
  422. /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
  423. /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
  424. /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
  425. /package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +0 -0
  426. /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
  427. /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
  428. /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
  429. /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
  430. /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
  431. /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
  432. /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
  433. /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
  434. /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
  435. /package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +0 -0
  436. /package/scripts/run/{run-ship-pre-detach.ts → ship/run-ship-pre-detach.ts} +0 -0
  437. /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
  438. /package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +0 -0
  439. /package/scripts/run/{run-ship.ts → ship/run-ship.ts} +0 -0
  440. /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
  441. /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
  442. /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
@@ -4,13 +4,11 @@ import { fileURLToPath } from 'node:url'
4
4
  import { hook_decision } from '#scripts/josh/hook-decision'
5
5
  import { josh_command } from '#scripts/josh/josh-run'
6
6
  import { lane_await, type AwaitState } from '#scripts/lane/lane-await'
7
- import { lane_launch_cli, type LaunchOutcome } from '#scripts/lane/lane-launch-cli'
8
7
  import { lane_registry } from '#scripts/lane/lane-registry'
9
- import { cli_flags } from '#scripts/lib/cli-flags'
10
8
  import { error_text } from '#scripts/lib/error-message'
11
- import { run_carry, type RunCarry } from '#scripts/run/run-carry'
12
- import { run_event_stream_emit } from '#scripts/run/run-event-stream-emit'
13
- import { run_merge_cli, type MergeResult } from '#scripts/run/run-merge-cli'
9
+ import { run_carry, type RunCarry } from '#scripts/run/carry/run-carry'
10
+ import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
11
+ import { run_merge_cli, type MergeResult } from '#scripts/run/merge/run-merge-cli'
14
12
  import { z } from 'zod'
15
13
  import {
16
14
  backlog_drive,
@@ -19,7 +17,9 @@ import {
19
17
  type LoopPorts,
20
18
  type OfferRead,
21
19
  } from './backlog-drive'
20
+ import { backlog_drive_args, type DriveContext } from './backlog-drive-args'
22
21
  import { backlog_drive_finish } from './backlog-drive-finish'
22
+ import { backlog_drive_launch } from './backlog-drive-launch'
23
23
  import { backlog_drive_named } from './backlog-drive-named'
24
24
  import { backlog_drive_named_offer } from './backlog-drive-named-offer'
25
25
  import { backlog_drive_offer_argv } from './backlog-drive-offer-argv'
@@ -44,114 +44,7 @@ const FAILURE_EXIT_CODE = 1
44
44
  const should_forward_stderr = true
45
45
  const POLL_MS = 5000
46
46
  const OFFER_MS = 60_000
47
- const MS_PER_MINUTE = 60_000
48
47
  const FIRST_LINE = 0
49
- const LIST_SEPARATOR = ','
50
- const COUNT_PATTERN = /^\d+$/u
51
- const ISSUE_PATTERN = /^[1-9]\d*$/u
52
- const USAGE =
53
- 'Usage: josh backlog:drive --owner <pid> [--active <ISO-8601>] [--max <n>] [--idle <minutes>] [--only] [--exclude <n>[,<n>...]] [--await <n>[,<n>...]] [--window <minutes>] [--stopped <n>]'
54
-
55
- const OPTIONS = {
56
- active: { type: 'string' },
57
- await: { type: 'string' },
58
- exclude: { type: 'string' },
59
- idle: { type: 'string' },
60
- max: { type: 'string' },
61
- only: { type: 'boolean' },
62
- owner: { type: 'string' },
63
- stopped: { type: 'string' },
64
- window: { type: 'string' },
65
- } as const
66
-
67
- interface DriveContext {
68
- owner: string
69
- active: string | undefined
70
- // Flags forwarded to `backlog:offer` unchanged — the declared budgets.
71
- forwarded: ReadonlyArray<string>
72
- exclude: ReadonlyArray<string>
73
- awaited: ReadonlyArray<string>
74
- window_ms: number | undefined
75
- window: string | undefined
76
- is_only: boolean
77
- // The child whose merge answered `stop` before the hand-back: the resumed run starts already stopping.
78
- stopped: string | undefined
79
- }
80
-
81
- type Values = Partial<Record<Exclude<keyof typeof OPTIONS, 'only'>, string>> & { only?: boolean }
82
-
83
- function read_values(argv: ReadonlyArray<string>): Values | undefined {
84
- return cli_flags.parse_or_undefined({ args: [...argv], options: OPTIONS, strict: true })?.values
85
- }
86
-
87
- // A comma list of issue numbers; one entry that is not an issue number refuses the whole list.
88
- function to_issues(raw: string | undefined): ReadonlyArray<string> | undefined {
89
- if (raw === undefined) return []
90
-
91
- const issues = raw.split(LIST_SEPARATOR)
92
-
93
- return issues.some((issue) => !ISSUE_PATTERN.test(issue)) ? undefined : issues
94
- }
95
-
96
- function forwarded_of(values: Values): ReadonlyArray<string> | undefined {
97
- const pairs = (['max', 'idle'] as const).filter((name) => values[name] !== undefined)
98
-
99
- if (pairs.some((name) => !COUNT_PATTERN.test(values[name] ?? ''))) return undefined
100
-
101
- return pairs.flatMap((name) => [`--${name}`, values[name] ?? ''])
102
- }
103
-
104
- // An absent window is no bound; a present one must be a count, which `is_valid_window` checks first.
105
- function window_of(raw: string | undefined): number | undefined {
106
- return raw === undefined ? undefined : Number(raw) * MS_PER_MINUTE
107
- }
108
-
109
- function is_valid_window(raw: string | undefined): boolean {
110
- return raw === undefined || COUNT_PATTERN.test(raw)
111
- }
112
-
113
- function is_valid_single(values: Values): boolean {
114
- const is_valid_stopped = values.stopped === undefined || ISSUE_PATTERN.test(values.stopped)
115
-
116
- return is_valid_stopped && is_valid_window(values.window)
117
- }
118
-
119
- function owner_of(values: Values | undefined): string | undefined {
120
- const owner = values?.owner
121
-
122
- return owner !== undefined && COUNT_PATTERN.test(owner) ? owner : undefined
123
- }
124
-
125
- function to_context(values: Values, owner: string): DriveContext | undefined {
126
- if (!is_valid_single(values)) return undefined
127
-
128
- const forwarded = forwarded_of(values)
129
- const exclude = to_issues(values.exclude)
130
- const awaited = to_issues(values.await)
131
-
132
- if (forwarded === undefined || exclude === undefined || awaited === undefined) return undefined
133
-
134
- const window_ms = window_of(values.window)
135
-
136
- return {
137
- owner,
138
- active: values.active,
139
- forwarded,
140
- exclude,
141
- awaited,
142
- window_ms,
143
- window: values.window,
144
- is_only: values.only === true,
145
- stopped: values.stopped,
146
- }
147
- }
148
-
149
- function parse(argv: ReadonlyArray<string>): DriveContext | undefined {
150
- const values = read_values(argv)
151
- const owner = owner_of(values)
152
-
153
- return values === undefined || owner === undefined ? undefined : to_context(values, owner)
154
- }
155
48
 
156
49
  // The `--json` object `backlog:offer` prints, keyed under its `JSON_KEY`.
157
50
  const offer_read_schema = z.object({
@@ -235,14 +128,6 @@ async function merge(issue: string, owner: string): Promise<MergeResult> {
235
128
  return result
236
129
  }
237
130
 
238
- async function launch(issue: string, owner: string): Promise<LaunchOutcome['kind']> {
239
- await backlog_drive_owner.assert_current(owner)
240
-
241
- const outcome = await lane_launch_cli.launch_lane({ issue, stash: undefined })
242
-
243
- return outcome.kind
244
- }
245
-
246
131
  // `lane:await`'s re-confirmed check, one state per child. A child already gone when the loop starts —
247
132
  // a restart after it ended — is read as having appeared, so it is collected after the re-confirm window
248
133
  // rather than waited on until the never-appeared timeout. One that never appeared is collected too:
@@ -278,17 +163,6 @@ async function open_lanes(): Promise<ReadonlyArray<string>> {
278
163
  return lanes.filter((lane) => !lane.is_stranded).map((lane) => lane.issue)
279
164
  }
280
165
 
281
- function resume_line(state: DriveState, context: DriveContext): string {
282
- const exclude = [...new Set([...context.exclude, ...state.exclude])]
283
- const flags = ['--owner', context.owner, '--active', state.active, ...context.forwarded]
284
-
285
- if (exclude.length > 0) flags.push('--exclude', exclude.join(LIST_SEPARATOR))
286
- if (context.window !== undefined) flags.push('--window', context.window)
287
- if (state.stopped_by !== undefined) flags.push('--stopped', state.stopped_by)
288
-
289
- return `resume: ${flags.join(' ')}`
290
- }
291
-
292
166
  function end_line(end: DriveEnd): string {
293
167
  const token = end.token === undefined || end.token === end.reason ? [] : [end.token]
294
168
  const issue = end.issue === undefined ? [] : [`#${end.issue}`]
@@ -306,7 +180,7 @@ function ports_of(
306
180
  is_finished: finished_checker(seeded),
307
181
  merge: async (issue) => await merge(issue, context.owner),
308
182
  offer: async (state) => await read_offer(state, context),
309
- launch: async (issue) => await launch(issue, context.owner),
183
+ launch: async (issue) => await backlog_drive_launch.launch(issue, context.owner),
310
184
  free_lanes: backlog_ready.drive_free_lane_count,
311
185
  now: () => new Date(),
312
186
  sleep: async (milliseconds) => {
@@ -345,7 +219,7 @@ async function drive(context: DriveContext): Promise<number> {
345
219
  const end = await backlog_drive.run_loop(last.state, config, ports_of(context, seeded, last))
346
220
 
347
221
  console.info(end_line(end))
348
- console.info(resume_line(last.state, context))
222
+ console.info(backlog_drive_args.resume_line(last.state, context))
349
223
 
350
224
  return SUCCESS_EXIT_CODE
351
225
  }
@@ -361,10 +235,10 @@ async function run_safe(context: DriveContext): Promise<number> {
361
235
  }
362
236
 
363
237
  async function run(argv: ReadonlyArray<string>): Promise<number> {
364
- const context = parse(argv)
238
+ const context = backlog_drive_args.parse(argv)
365
239
 
366
240
  if (context === undefined) {
367
- console.error(USAGE)
241
+ console.error(backlog_drive_args.USAGE)
368
242
 
369
243
  return FAILURE_EXIT_CODE
370
244
  }
@@ -384,8 +258,8 @@ const backlog_drive_cli = {
384
258
  merge,
385
259
  merge_token,
386
260
  offer_argv: backlog_drive_offer_argv.offer_argv,
387
- parse,
388
- resume_line,
261
+ parse: backlog_drive_args.parse,
262
+ resume_line: backlog_drive_args.resume_line,
389
263
  to_offer,
390
264
  }
391
265
 
@@ -0,0 +1,39 @@
1
+ import { git_stash } from '#scripts/git/stash/git-stash'
2
+ import { lane_launch_cli, type LaunchOutcome } from '#scripts/lane/lane-launch-cli'
3
+ import { backlog_drive_owner } from './backlog-drive-owner'
4
+
5
+ // The driver's lane launch (joshuafolkken/kit#3272). The parent that ran `josh latest` in the primary
6
+ // checkout leaves its rewritten lock file in a stash under this message — `backlogrun-lanes.md` →
7
+ // "Once per repository, before the first lane opens" — and the lane that pops it carries the update
8
+ // into its pull request. **The stash itself is the "first lane" marker**: each launch asks whether it
9
+ // is still on the stack, and the pop that `lane:launch --stash` runs consumes it, so the first launch
10
+ // after the push takes it and every later one finds nothing. A restarted driver needs no memory of
11
+ // which lane was first. Two entries under the message are a state a person resolves, so neither is
12
+ // guessed at — the launch goes ahead without one, as `stash:pop` would refuse it anyway. That skip is
13
+ // said on stderr, because `stash:pop` refusing would have been heard and a silent skip would let the
14
+ // update never reach a pull request.
15
+ const LATEST_STASH_MESSAGE = 'backlogrun: josh latest before lanes'
16
+
17
+ async function latest_stash(): Promise<string | undefined> {
18
+ const selection = git_stash.select(await git_stash.list(), LATEST_STASH_MESSAGE)
19
+
20
+ if (selection.kind === 'ambiguous') {
21
+ console.error(
22
+ `Launching without the "${LATEST_STASH_MESSAGE}" stash: ${selection.selectors.join(', ')} all match — keep one.`,
23
+ )
24
+ }
25
+
26
+ return selection.kind === 'match' ? LATEST_STASH_MESSAGE : undefined
27
+ }
28
+
29
+ async function launch(issue: string, owner: string): Promise<LaunchOutcome['kind']> {
30
+ await backlog_drive_owner.assert_current(owner)
31
+
32
+ const outcome = await lane_launch_cli.launch_lane({ issue, stash: await latest_stash() })
33
+
34
+ return outcome.kind
35
+ }
36
+
37
+ const backlog_drive_launch = { LATEST_STASH_MESSAGE, launch }
38
+
39
+ export { backlog_drive_launch }
@@ -1,8 +1,8 @@
1
1
  import { EPIC_LABEL, has_label_name, NEEDS_DECISION_LABEL } from '#scripts/issue/issue-labels'
2
2
  import type { IssueState } from '#scripts/issue/issue-state'
3
3
  import { issue_state_cli } from '#scripts/issue/issue-state-cli'
4
- import type { RunCarry } from '#scripts/run/run-carry'
5
- import type { MergeResult } from '#scripts/run/run-merge-cli'
4
+ import type { RunCarry } from '#scripts/run/carry/run-carry'
5
+ import type { MergeResult } from '#scripts/run/merge/run-merge-cli'
6
6
  import { backlog_budget } from './backlog-budget'
7
7
  import type { DriveState, OfferRead } from './backlog-drive'
8
8
  import { backlog_drive_named } from './backlog-drive-named'
@@ -58,9 +58,7 @@ function settled_outcome(state: IssueState): MergeResult['outcome'] | undefined
58
58
  async function classify(issue: string, named: OfferRead, owner: string): Promise<OfferRead> {
59
59
  const result = await issue_state_cli.read_issue(issue)
60
60
 
61
- if (result.kind !== 'state') {
62
- return { verdict: 'issue-state', issues: [], retries: NO_RETRIES }
63
- }
61
+ if (result.kind !== 'state') return { verdict: 'issue-state', issues: [], retries: NO_RETRIES }
64
62
 
65
63
  const outcome = settled_outcome(result.state)
66
64
 
@@ -1,8 +1,8 @@
1
1
  import { has_label_name, NEEDS_DECISION_LABEL } from '#scripts/issue/issue-labels'
2
2
  import { issue_state_cli } from '#scripts/issue/issue-state-cli'
3
- import { run_carry, type RunCarry } from '#scripts/run/run-carry'
4
- import type { RunEvent } from '#scripts/run/run-event-stream'
5
- import type { MergeResult } from '#scripts/run/run-merge-cli'
3
+ import { run_carry, type RunCarry } from '#scripts/run/carry/run-carry'
4
+ import type { RunEvent } from '#scripts/run/event/run-event-stream'
5
+ import type { MergeResult } from '#scripts/run/merge/run-merge-cli'
6
6
  import type { DriveState, OfferRead } from './backlog-drive'
7
7
  import { backlog_drive_restore } from './backlog-drive-restore'
8
8
  import { backlog_named } from './backlog-named'
@@ -1,4 +1,4 @@
1
- import type { RunCarry } from '#scripts/run/run-carry'
1
+ import type { RunCarry } from '#scripts/run/carry/run-carry'
2
2
  import type { DriveState } from './backlog-drive'
3
3
 
4
4
  const LIST_SEPARATOR = ','
@@ -1,4 +1,4 @@
1
- import { run_carry, type RunCarry } from '#scripts/run/run-carry'
1
+ import { run_carry, type RunCarry } from '#scripts/run/carry/run-carry'
2
2
 
3
3
  function owns_record(carry: RunCarry, owner: string): boolean {
4
4
  const current = run_carry.owner_of(Number(owner))
@@ -1,4 +1,4 @@
1
- import { run_event_stream, type RunEvent } from '#scripts/run/run-event-stream'
1
+ import { run_event_stream, type RunEvent } from '#scripts/run/event/run-event-stream'
2
2
 
3
3
  const ISSUE_IN_EVENT = /#([1-9]\d*)\b/u
4
4
  const SETTLED_KINDS: ReadonlySet<string> = new Set([
@@ -1,6 +1,6 @@
1
1
  import { doctor_consumer } from '#scripts/doctor/doctor-consumer'
2
2
  import { find_package_directory } from '#scripts/josh/josh-logic'
3
- import type { RunCarry } from '#scripts/run/run-carry'
3
+ import type { RunCarry } from '#scripts/run/carry/run-carry'
4
4
  import { run_retrospective } from '#scripts/run/run-retrospective'
5
5
 
6
6
  // Whether a drain owes the retrospective, read through the owed rule `run:step` reads, so a drain it
@@ -17,7 +17,7 @@
17
17
  // without a process, a network or a clock.
18
18
 
19
19
  import type { LaunchOutcome } from '#scripts/lane/lane-launch-cli'
20
- import type { MergeResult } from '#scripts/run/run-merge-cli'
20
+ import type { MergeResult } from '#scripts/run/merge/run-merge-cli'
21
21
 
22
22
  const RUN_VERDICT = 'run'
23
23
  const STOP_VERDICT = 'stop'
@@ -146,6 +146,19 @@ function report(result: EpicNextResult, context: PoolContext): number {
146
146
  return SUCCESS_EXIT_CODE
147
147
  }
148
148
 
149
+ // The settled set the views were classified against, so a standalone row and an epic child weigh the
150
+ // same blocker the same way.
151
+ function running_of(
152
+ views: ReadonlyArray<EpicView>,
153
+ graphs: ReadonlyArray<ReadonlyArray<EpicChild>>,
154
+ context: PoolContext,
155
+ ): ReadonlySet<string> {
156
+ return (
157
+ views[0]?.running ??
158
+ backlog_pool.running_set(graphs, context.opted_in.issues, context.repo, context.tracking.index)
159
+ )
160
+ }
161
+
149
162
  // The two halves as one result. The epic half is already classified by `epic:next`; the standalone
150
163
  // half is classified here; the verdict is decided from the merge, so a backlog with a runnable epic
151
164
  // child and nothing else says `run` exactly as one with a runnable standalone issue does.
@@ -161,10 +174,7 @@ function combine(views: ReadonlyArray<EpicView>, context: PoolContext): EpicNext
161
174
  tracked: epic_index.withheld_children(context.tracking.index, context.opted_in.issues),
162
175
  exclude: context.exclude,
163
176
  repo: context.repo,
164
- // The settled set the views were classified against, so a standalone row and an epic child weigh
165
- // the same blocker the same way.
166
- running:
167
- views[0]?.running ?? backlog_pool.running_set(graphs, context.opted_in.issues, context.repo),
177
+ running: running_of(views, graphs, context),
168
178
  })
169
179
 
170
180
  // The same checkout map `epic:next` hands `build_result`. Without it every bundle heading reads
@@ -194,6 +204,7 @@ function settled_views(
194
204
  views[0]?.running ?? running,
195
205
  context.opted_in.issues,
196
206
  context.repo,
207
+ context.tracking.index,
197
208
  )
198
209
 
199
210
  return settled.size === running.size ? views : settled_views(reads, settled, context)
@@ -212,7 +223,7 @@ function views_from(reads: ReadonlyArray<EpicRead>, context: PoolContext): Reado
212
223
 
213
224
  return settled_views(
214
225
  reads,
215
- backlog_pool.running_set(graphs, context.opted_in.issues, context.repo),
226
+ backlog_pool.running_set(graphs, context.opted_in.issues, context.repo, context.tracking.index),
216
227
  context,
217
228
  )
218
229
  }
@@ -3,8 +3,8 @@ import { fileURLToPath } from 'node:url'
3
3
  import { josh_command } from '#scripts/josh/josh-run'
4
4
  import { cli_flags } from '#scripts/lib/cli-flags'
5
5
  import { rule_value_cache } from '#scripts/rules/rule-value-cache'
6
- import { run_event_stream } from '#scripts/run/run-event-stream'
7
- import { run_event_stream_emit } from '#scripts/run/run-event-stream-emit'
6
+ import { run_event_stream } from '#scripts/run/event/run-event-stream'
7
+ import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
8
8
  import { z } from 'zod'
9
9
  import { backlog_offer, type OfferAnswer } from './backlog-offer'
10
10
 
@@ -74,8 +74,8 @@ function to_count(raw: string | undefined): number | undefined {
74
74
 
75
75
  // A count flag that was given but unreadable makes the invocation unreadable rather than defaulted —
76
76
  // the contract `backlog:budget` follows for its own counts.
77
- function count_of(raw: string | undefined, fallback: number): number | undefined {
78
- return raw === undefined ? fallback : to_count(raw)
77
+ function count_of(raw: string | undefined): number | undefined {
78
+ return raw === undefined ? DEFAULT_COUNT : to_count(raw)
79
79
  }
80
80
 
81
81
  interface OfferCounts {
@@ -86,8 +86,8 @@ interface OfferCounts {
86
86
  // The two counts the mapping needs: `--running` decides `wait`, `--retries` decides `retry`. Either
87
87
  // given but unreadable refuses the whole invocation.
88
88
  function counts_of(values: ParsedValues): OfferCounts | undefined {
89
- const running = count_of(cli_flags.string_of(values.running), DEFAULT_COUNT)
90
- const retries = count_of(cli_flags.string_of(values.retries), DEFAULT_COUNT)
89
+ const running = count_of(cli_flags.string_of(values.running))
90
+ const retries = count_of(cli_flags.string_of(values.retries))
91
91
 
92
92
  if (running === undefined || retries === undefined) return undefined
93
93
 
@@ -5,7 +5,7 @@ import { backlog_next } from './backlog-next'
5
5
  // answered — its stdout tokens and its exit code — into the one word `backlog:budget` is then asked
6
6
  // with, plus the issue numbers to start and the consecutive-retry count carried to the next ask.
7
7
  //
8
- // **The mapping is `docs/josh-commands-automation.md` → "`josh backlog:offer`", held here so it cannot
8
+ // **The mapping is `docs/josh-commands-backlog.md` → "`josh backlog:offer`", held here so it cannot
9
9
  // drift.** It fixes which `backlog:next` answer becomes which `backlog:budget` word, and it carries two
10
10
  // context branches an agent once applied by hand every iteration: `wait` is `blocked` while this run
11
11
  // has children in flight and `exhausted` when it has none, and `retry` is `blocked` until the third
@@ -4,8 +4,8 @@ import { auto_ok_cli } from '#scripts/auto-ok/auto-ok-cli'
4
4
  import { epic_index } from '#scripts/epic/epic-index'
5
5
  import type { EpicNextResult } from '#scripts/epic/epic-report'
6
6
  import { git_gh_command } from '#scripts/gh/git-gh-command'
7
+ import { open_issue_schema, type OpenIssueData } from '#scripts/git/git-schemas'
7
8
  import { read_json_listing } from '#scripts/git/parse-json-array'
8
- import { open_issue_schema, type OpenIssueData } from '#scripts/git/schemas'
9
9
  import { run_invocation } from '#scripts/run/run-invocation'
10
10
  import { backlog_named } from './backlog-named'
11
11
  import { backlog_next, type OptedIn } from './backlog-next'
@@ -5,7 +5,7 @@ import { epic_index } from '#scripts/epic/epic-index'
5
5
  import { epic_issue } from '#scripts/epic/epic-issue'
6
6
  import type { EpicView } from '#scripts/epic/epic-next-views'
7
7
  import { epic_outside_blocker } from '#scripts/epic/epic-outside-blocker'
8
- import type { OpenIssueData } from '#scripts/git/schemas'
8
+ import type { OpenIssueData } from '#scripts/git/git-schemas'
9
9
  import { git_next_issues } from '#scripts/issue/git-next-issues'
10
10
  import {
11
11
  ALREADY_DONE_LABEL,
@@ -37,6 +37,9 @@ const DECISION_LABELS: ReadonlySet<string> = new Set([NEEDS_DECISION_LABEL, ALRE
37
37
  // `tracked` is the narrowed set `epic_index.withheld_children` builds, not every child an epic
38
38
  // tracks (joshuafolkken/kit#1668) — the narrowing itself is defined there, once, for this half and
39
39
  // `auto-ok:next` alike.
40
+ // Child number to every open epic whose task list names it — `epic_index.build_tracking_index`.
41
+ type TrackingIndex = ReadonlyMap<number, ReadonlyArray<number>>
42
+
40
43
  // The part of the context that decides which rows the standalone half considers at all.
41
44
  type ConsideredContext = Pick<StandaloneContext, 'tracked' | 'exclude' | 'repo'>
42
45
 
@@ -69,24 +72,64 @@ function needs_person(issue: OpenIssueData, context: StandaloneContext): boolean
69
72
  )
70
73
  }
71
74
 
75
+ // Each open epic's key, read off the tracking index — child number to the epics listing it.
76
+ function epic_keys_of(tracking: TrackingIndex, repo: string): ReadonlySet<string> {
77
+ return new Set([...tracking.values()].flat().map((number) => epic_graph.key_of({ repo, number })))
78
+ }
79
+
80
+ // The set with every epic that tracks a member added, nested epics included (joshuafolkken/kit#3289).
81
+ // An epic is a container rather than work, so it is never run itself — but it closes once its children
82
+ // do, so a row blocked by it waits on time exactly when one of those children is in this run.
83
+ function with_tracking_epics(
84
+ running: ReadonlySet<string>,
85
+ tracking: TrackingIndex,
86
+ repo: string,
87
+ ): ReadonlySet<string> {
88
+ const tracking_epics = [...tracking]
89
+ .filter(([child]) => running.has(epic_graph.key_of({ repo, number: child })))
90
+ .flatMap(([, epics]) => epics.map((number) => epic_graph.key_of({ repo, number })))
91
+ const grown = new Set([...running, ...tracking_epics])
92
+
93
+ return grown.size === running.size ? running : with_tracking_epics(grown, tracking, repo)
94
+ }
95
+
96
+ // The epics re-derived from the members alone, so an epic whose last running child was just taken out
97
+ // leaves with it rather than lingering from an earlier pass.
98
+ function rooted(
99
+ running: ReadonlySet<string>,
100
+ tracking: TrackingIndex,
101
+ repo: string,
102
+ ): ReadonlySet<string> {
103
+ const epics = epic_keys_of(tracking, repo)
104
+ const members = new Set([...running].filter((key) => !epics.has(key)))
105
+
106
+ return with_tracking_epics(members, tracking, repo)
107
+ }
108
+
109
+ function is_stuck(issue: OpenIssueData, running: ReadonlySet<string>, repo: string): boolean {
110
+ return has_any_label(issue.labels, DECISION_LABELS) || waits_outside(issue, running, repo)
111
+ }
112
+
72
113
  // The running set with every standalone row a person has to resolve taken out, repeated until a pass
73
114
  // removes nothing — a row waiting on such a row waits on that person too, however long the chain.
74
115
  function settle_standalone(
75
116
  running: ReadonlySet<string>,
76
117
  issues: ReadonlyArray<OpenIssueData>,
77
118
  repo: string,
119
+ tracking: TrackingIndex,
78
120
  ): ReadonlySet<string> {
79
121
  const settled = new Set(running)
80
122
 
81
- // An epic row is never in the set, so deleting its key changes nothing and it needs no filter.
123
+ // An epic row is never a standalone row, so deleting its key here never happens; `rooted` decides it.
82
124
  for (const issue of issues) {
83
- const is_stuck =
84
- has_any_label(issue.labels, DECISION_LABELS) || waits_outside(issue, running, repo)
85
-
86
- if (is_stuck) settled.delete(epic_graph.key_of({ repo, number: issue.number }))
125
+ if (is_stuck(issue, running, repo)) {
126
+ settled.delete(epic_graph.key_of({ repo, number: issue.number }))
127
+ }
87
128
  }
88
129
 
89
- return settled.size === running.size ? running : settle_standalone(settled, issues, repo)
130
+ const next = rooted(settled, tracking, repo)
131
+
132
+ return next.size === running.size ? next : settle_standalone(next, issues, repo, tracking)
90
133
  }
91
134
 
92
135
  // An opted-in row as the graph's node type. Both halves of the pool are then the same shape, so one
@@ -182,18 +225,21 @@ function classify_standalone(
182
225
  // (joshuafolkken/kit#1943). A child is opted in through its epic's `auto-ok` even where it carries none
183
226
  // of its own, which is why the set is built from the graphs that were read rather than from labels. A
184
227
  // standalone row a person has to resolve is left out, so a row waiting on it waits for that person too;
185
- // an epic child in the same state is taken out by `epic_next_views.settle_views`.
228
+ // an epic child in the same state is taken out by `epic_next_views.settle_views`. An open epic with a
229
+ // child in the set is in it too, so a row blocked by that epic waits rather than needs a person. A
230
+ // closed child is left out: it runs nothing, and kept in the set it would hold its epic there.
186
231
  function running_set(
187
232
  graphs: ReadonlyArray<ReadonlyArray<EpicChild>>,
188
233
  issues: ReadonlyArray<OpenIssueData>,
189
234
  repo: string,
235
+ tracking: TrackingIndex,
190
236
  ): ReadonlySet<string> {
191
237
  const running = epic_outside_blocker.running_keys([
192
- ...graphs.flat(),
238
+ ...graphs.flat().filter((child) => child.state === 'OPEN'),
193
239
  ...to_children(standalone_rows(issues), repo),
194
240
  ])
195
241
 
196
- return settle_standalone(running, issues, repo)
242
+ return settle_standalone(rooted(running, tracking, repo), issues, repo, tracking)
197
243
  }
198
244
 
199
245
  // A cycle no single epic can see, because its links cross from one graph into another. Each epic's own
@@ -336,4 +382,4 @@ const backlog_pool = {
336
382
  }
337
383
 
338
384
  export { backlog_pool }
339
- export type { ConsideredContext, StandaloneContext }
385
+ export type { ConsideredContext, StandaloneContext, TrackingIndex }
@@ -3,7 +3,7 @@ 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
5
  import { lane_registry } from '#scripts/lane/lane-registry'
6
- import type { RunCarry } from '#scripts/run/run-carry'
6
+ import type { RunCarry } from '#scripts/run/carry/run-carry'
7
7
  import { run_headless } from '#scripts/run/run-headless'
8
8
  import { run_invocation } from '#scripts/run/run-invocation'
9
9
  import { backlog_stalled } from './backlog-stalled'
@@ -1,5 +1,5 @@
1
1
  import type { EpicNextResult } from '#scripts/epic/epic-report'
2
- import type { OpenIssueData } from '#scripts/git/schemas'
2
+ import type { OpenIssueData } from '#scripts/git/git-schemas'
3
3
  import { AUTO_OK_LABEL, EPIC_LABEL, has_any_label } from '#scripts/issue/issue-labels'
4
4
 
5
5
  // Which open issues the backlog will not run, and why (joshuafolkken/kit#1652).
@@ -1,6 +1,6 @@
1
1
  import { telegram_notify } from '#scripts/notify/telegram-notify'
2
- import { run_event_stream, type RunEvent } from '#scripts/run/run-event-stream'
3
- import { run_event_stream_emit } from '#scripts/run/run-event-stream-emit'
2
+ import { run_event_stream, type RunEvent } from '#scripts/run/event/run-event-stream'
3
+ import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
4
4
  import { backlog_ready, type ReadyPorts } from './backlog-ready'
5
5
  import { backlog_stalled, type StallReading, type StallVerdict } from './backlog-stalled'
6
6
 
@@ -1,5 +1,5 @@
1
1
  import { epic_triage } from '#scripts/epic/epic-triage'
2
- import { run_event_stream, type RunEvent } from '#scripts/run/run-event-stream'
2
+ import { run_event_stream, type RunEvent } from '#scripts/run/event/run-event-stream'
3
3
 
4
4
  // When ready backlog work sits undispatched while lanes are free, nobody notices until a person asks
5
5
  // (joshuafolkken/kit#2359). This is the mechanical judge of that state: three facts read, never
@@ -10,7 +10,9 @@ const CODEX_HOOKS = path.join(REPO_ROOT, '.codex', 'hooks.json')
10
10
 
11
11
  // Regenerate `.codex/hooks.json` from `.claude/settings.json` (joshuafolkken/kit#2997). The output is
12
12
  // committed rather than gitignored: Codex reads it straight from a fresh clone of kit, before any
13
- // build has run, and `josh init` / `josh sync` copy it to consumers from the package.
13
+ // build has run, and `josh init` / `josh sync` copy it to consumers from the package. It is run by
14
+ // hand, never from `pnpm build`, so a build never rewrites a committed file (joshuafolkken/kit#3324):
15
+ // `codex-project-config.test.ts` fails the gate when the committed copy has drifted.
14
16
  function build_codex_hooks(): string {
15
17
  writeFileSync(CODEX_HOOKS, codex_hooks.codex_hooks_text(readFileSync(CLAUDE_SETTINGS, 'utf8')))
16
18