@joshuafolkken/kit 1.1178.0 → 1.1229.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/settings.json +2 -2
  3. package/.claude/skills/dependency-update/SKILL.md +4 -8
  4. package/.claude/skills/epic-commands/SKILL.md +2 -2
  5. package/.claude/skills/epic-commands/epic-bundle.md +1 -1
  6. package/.claude/skills/workflow-commands/SKILL.md +2 -4
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +6 -2
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +23 -2
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +7 -2
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +21 -13
  13. package/.claude/skills/workflow-commands/chain-rule.md +1 -1
  14. package/.claude/skills/workflow-commands/delegation.md +2 -2
  15. package/.claude/skills/workflow-commands/followup.md +5 -0
  16. package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
  17. package/.claude/skills/workflow-commands/halfrun.md +1 -1
  18. package/.claude/skills/workflow-commands/issue-scout.md +1 -1
  19. package/.claude/skills/workflow-commands/kickoff.md +1 -1
  20. package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
  21. package/.claude/skills/workflow-commands/pre-gate-cut.md +6 -1
  22. package/.claude/skills/workflow-commands/prerequisite.md +2 -2
  23. package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
  24. package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
  25. package/.codex/hooks.json +2 -2
  26. package/.cursorrules +6 -41
  27. package/.github/actions/setup-node/action.yml +1 -1
  28. package/.github/workflows/auto-tag.yml +3 -4
  29. package/.github/workflows/ci.yml +32 -31
  30. package/.github/workflows/dependabot-auto-merge.yml +13 -6
  31. package/.github/workflows/publish.yml +48 -4
  32. package/.github/workflows/sonar-qube.yml +3 -0
  33. package/.pnpmfile.mjs +4 -3
  34. package/.prettierignore +8 -2
  35. package/CLAUDE.md +18 -20
  36. package/README.md +26 -19
  37. package/SECURITY.md +15 -1
  38. package/dist/CLAUDE.md +18 -20
  39. package/dist/commands/document-section-cli.js +734 -0
  40. package/dist/hooks/audit-provision.js +346 -0
  41. package/dist/hooks/batch-guard.js +5 -4
  42. package/dist/hooks/{chunk-S6X6WYKR.js → chunk-55HHG5PX.js} +1210 -711
  43. package/dist/hooks/{chunk-ZAL3NRGL.js → chunk-5VMKCCC4.js} +25 -10
  44. package/dist/hooks/chunk-5VNDSNG3.js +12 -0
  45. package/dist/hooks/{chunk-32GQTL4F.js → chunk-6HJJXRBX.js} +4 -2
  46. package/dist/hooks/chunk-CDY4NFQG.js +15 -0
  47. package/dist/hooks/chunk-ELCN5LCH.js +135 -0
  48. package/dist/hooks/{chunk-VESKRQOF.js → chunk-F56MFS4B.js} +3 -3
  49. package/dist/hooks/{chunk-UAGLLIGZ.js → chunk-FIVDHQGT.js} +7 -6
  50. package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
  51. package/dist/hooks/{chunk-G6KRCKEC.js → chunk-JUDKDCDP.js} +496 -378
  52. package/dist/hooks/{chunk-GDS5N2KB.js → chunk-NV2JRZZO.js} +718 -681
  53. package/dist/hooks/{chunk-VQLLNFNT.js → chunk-RNWHN5BP.js} +3 -3
  54. package/dist/hooks/{chunk-DO6NWWHU.js → chunk-TQNPDZNH.js} +4 -4
  55. package/dist/hooks/{chunk-I4XSL4TT.js → chunk-VC3PZPZO.js} +1 -1
  56. package/dist/hooks/codex-hook-adapter.js +14 -12
  57. package/dist/hooks/duplicate-read-guard.js +6 -5
  58. package/dist/hooks/format-edited.js +4 -3
  59. package/dist/hooks/inputs.json +217 -195
  60. package/dist/hooks/investigation-guard.js +6 -5
  61. package/dist/hooks/pretool-guard.js +12 -10
  62. package/dist/hooks/session-lang.js +2 -4
  63. package/dist/hooks/stop-guard.js +66 -58
  64. package/dist/josh.js +868 -74
  65. package/dist/version/index.d.ts +7 -2
  66. package/dist/version/index.js +152 -75
  67. package/eslint/base.js +23 -1
  68. package/eslint/rules/code-quality.js +3 -0
  69. package/eslint/rules/early-return-one-liner.js +98 -0
  70. package/eslint/rules/unicorn.js +16 -1
  71. package/package.json +7 -6
  72. package/pnpm-workspace.yaml +9 -4
  73. package/prettier/basic.d.ts +3 -1
  74. package/prettier/index.d.ts +3 -1
  75. package/prettier/static.d.ts +3 -1
  76. package/prompts/coding-standards.md +1 -1
  77. package/prompts/collaboration-workflow/file-edits.md +4 -4
  78. package/prompts/collaboration-workflow/glossary.md +1 -1
  79. package/prompts/collaboration-workflow/operating-rules.md +17 -9
  80. package/prompts/collaboration-workflow/overview.md +2 -2
  81. package/prompts/collaboration-workflow/principles.md +14 -12
  82. package/prompts/collaboration-workflow/report-format.md +1 -1
  83. package/prompts/collaboration-workflow/residency.md +1 -1
  84. package/prompts/collaboration-workflow/rule-delivery.md +6 -6
  85. package/prompts/collaboration-workflow/shell-body.md +6 -6
  86. package/prompts/collaboration-workflow/turn-batching.md +1 -1
  87. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
  88. package/prompts/collaboration-workflow/wip-cap.md +10 -10
  89. package/prompts/review-rubric.md +1 -2
  90. package/prompts/review.md +1 -1
  91. package/scripts/agent/agent-headless.ts +29 -0
  92. package/scripts/agent/agent-launch-environment.ts +6 -2
  93. package/scripts/agent/codex-agent-argv.ts +2 -1
  94. package/scripts/auto-ok/auto-ok-cli.ts +1 -1
  95. package/scripts/backlog/backlog-arrival.ts +3 -3
  96. package/scripts/backlog/backlog-budget.ts +1 -1
  97. package/scripts/backlog/backlog-drive-args.ts +135 -0
  98. package/scripts/backlog/backlog-drive-cli.ts +11 -137
  99. package/scripts/backlog/backlog-drive-launch.ts +39 -0
  100. package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
  101. package/scripts/backlog/backlog-drive-named.ts +3 -3
  102. package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
  103. package/scripts/backlog/backlog-drive-owner.ts +1 -1
  104. package/scripts/backlog/backlog-drive-restore.ts +1 -1
  105. package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
  106. package/scripts/backlog/backlog-drive.ts +1 -1
  107. package/scripts/backlog/backlog-next.ts +16 -5
  108. package/scripts/backlog/backlog-offer-cli.ts +6 -6
  109. package/scripts/backlog/backlog-offer.ts +1 -1
  110. package/scripts/backlog/backlog-plan-cli.ts +1 -1
  111. package/scripts/backlog/backlog-pool.ts +57 -11
  112. package/scripts/backlog/backlog-ready.ts +1 -1
  113. package/scripts/backlog/backlog-scope.ts +1 -1
  114. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  115. package/scripts/backlog/backlog-stalled.ts +1 -1
  116. package/scripts/build/build-codex-hooks.ts +3 -1
  117. package/scripts/build/build-commands.ts +66 -0
  118. package/scripts/build/build-hooks.ts +2 -1
  119. package/scripts/build/build-library.ts +12 -3
  120. package/scripts/build/build.ts +70 -0
  121. package/scripts/build/local-bin.ts +3 -13
  122. package/scripts/delegation/investigation-reads.ts +3 -1
  123. package/scripts/discovery/repo-origin.ts +5 -5
  124. package/scripts/doctor/doctor-consumer.ts +2 -2
  125. package/scripts/doctor/doctor-io.ts +2 -3
  126. package/scripts/document/capped-print-part.ts +30 -0
  127. package/scripts/document/capped-print.ts +3 -4
  128. package/scripts/document/document-byte-budget.ts +11 -2
  129. package/scripts/document/document-scan.ts +117 -0
  130. package/scripts/document/document-section.ts +143 -16
  131. package/scripts/document/entry-read-budget.ts +5 -3
  132. package/scripts/document/entry-read-set.ts +10 -89
  133. package/scripts/document/read-set-cost.ts +98 -0
  134. package/scripts/epic/epic-add-body.ts +1 -0
  135. package/scripts/epic/epic-audit-checks.ts +1 -1
  136. package/scripts/epic/epic-audit-rationale.ts +4 -3
  137. package/scripts/epic/epic-bundle-cli.ts +14 -3
  138. package/scripts/epic/epic-bundle-poll.ts +41 -0
  139. package/scripts/epic/epic-busy.ts +1 -1
  140. package/scripts/epic/epic-candidate-confirm.ts +3 -2
  141. package/scripts/epic/epic-classify.ts +1 -1
  142. package/scripts/epic/epic-cli-argv.ts +78 -46
  143. package/scripts/epic/epic-cli-remove.ts +11 -19
  144. package/scripts/epic/epic-cli.ts +74 -73
  145. package/scripts/epic/epic-close-comment.ts +1 -1
  146. package/scripts/epic/epic-close.ts +1 -1
  147. package/scripts/epic/epic-cross-repo.ts +5 -5
  148. package/scripts/epic/epic-graph.ts +1 -1
  149. package/scripts/epic/epic-index.ts +1 -1
  150. package/scripts/epic/epic-issue.ts +2 -6
  151. package/scripts/epic/epic-lane-offer.ts +2 -2
  152. package/scripts/epic/epic-next.ts +29 -42
  153. package/scripts/epic/epic-solo-stale.ts +1 -1
  154. package/scripts/epic/epic-triage.ts +8 -4
  155. package/scripts/epic/epic-validate.ts +1 -1
  156. package/scripts/followup/git-followup-finish.ts +2 -2
  157. package/scripts/followup/git-followup-label.ts +2 -2
  158. package/scripts/followup/git-followup-merged.ts +2 -2
  159. package/scripts/followup/git-pr-followup-checks.ts +142 -0
  160. package/scripts/followup/git-pr-followup.ts +4 -129
  161. package/scripts/gate/core-budget.ts +69 -1
  162. package/scripts/gate/gate-plan.ts +47 -7
  163. package/scripts/gate/gate-skip.ts +38 -7
  164. package/scripts/gate/hook-gate-reuse.ts +12 -10
  165. package/scripts/gate/pre-commit-type-check.ts +4 -1
  166. package/scripts/gate/project-checks.ts +6 -9
  167. package/scripts/gate/type-check-step.ts +2 -3
  168. package/scripts/gate/verification-gate.ts +27 -8
  169. package/scripts/gh/git-gh-api-path.ts +2 -2
  170. package/scripts/gh/git-gh-issue-rest.ts +3 -3
  171. package/scripts/gh/git-gh-issue-write.ts +53 -7
  172. package/scripts/gh/git-gh-pr-read.ts +64 -19
  173. package/scripts/gh/git-gh-pr-rest.ts +1 -1
  174. package/scripts/gh/git-gh-pr.ts +5 -1
  175. package/scripts/gh/git-pr-ai-review.ts +1 -1
  176. package/scripts/gh/git-pr-checks-eval.ts +1 -3
  177. package/scripts/gh/git-pr-checks-parse.ts +1 -1
  178. package/scripts/gh/git-pr-checks-watch.ts +6 -5
  179. package/scripts/gh/git-pr-checks.ts +7 -5
  180. package/scripts/gh/git-pr-coderabbit.ts +1 -1
  181. package/scripts/gh/git-pr-error.ts +1 -3
  182. package/scripts/gh/git-pr.ts +9 -13
  183. package/scripts/gh/github-issue-url.ts +2 -2
  184. package/scripts/git/constants.ts +2 -6
  185. package/scripts/git/git-command.ts +20 -218
  186. package/scripts/git/git-diff-reads.ts +225 -0
  187. package/scripts/git/git-error.ts +4 -12
  188. package/scripts/git/git-pre-push-hook.ts +139 -0
  189. package/scripts/git/git-prompt.ts +2 -6
  190. package/scripts/git/git-spawn-sync.ts +3 -1
  191. package/scripts/git/git-spawn.ts +1 -1
  192. package/scripts/git/git-staging.ts +4 -6
  193. package/scripts/git/git-status.ts +3 -9
  194. package/scripts/hooks/codex-hook-adapter.ts +1 -1
  195. package/scripts/hooks/edited-cspell.ts +2 -4
  196. package/scripts/hooks/format-edited-file.ts +3 -12
  197. package/scripts/hooks/pre-push-unit.ts +31 -9
  198. package/scripts/hooks/reserved-run.ts +2 -2
  199. package/scripts/hooks/run-hook.sh +5 -4
  200. package/scripts/hooks/stop-guard.ts +5 -3
  201. package/scripts/init/init-actions.ts +0 -5
  202. package/scripts/init/init-ai-copy.ts +13 -2
  203. package/scripts/init/init-logic-json-merge.ts +19 -0
  204. package/scripts/init/init-logic-secretlint.ts +7 -7
  205. package/scripts/init/init-logic-workspace.ts +41 -2
  206. package/scripts/init/init-logic.ts +16 -26
  207. package/scripts/init/init.ts +5 -15
  208. package/scripts/init/initial-commit.ts +4 -4
  209. package/scripts/init/kit-development-versions.ts +40 -0
  210. package/scripts/init/kit-setup-state.ts +2 -2
  211. package/scripts/init/start-exec.ts +68 -19
  212. package/scripts/init/start-setup-pr.ts +9 -9
  213. package/scripts/init/start-steps.ts +6 -10
  214. package/scripts/issue/defect-rate-cli.ts +1 -1
  215. package/scripts/issue/git-next-issues.ts +1 -1
  216. package/scripts/issue/issue-auto-ok.ts +1 -1
  217. package/scripts/issue/issue-backlinks-cli.ts +2 -2
  218. package/scripts/issue/issue-backlinks.ts +9 -9
  219. package/scripts/issue/issue-comment-cli.ts +19 -13
  220. package/scripts/issue/issue-file-cli.ts +45 -18
  221. package/scripts/issue/issue-file.ts +24 -1
  222. package/scripts/issue/issue-scout-closed.ts +1 -1
  223. package/scripts/issue/issue-state-cli.ts +30 -74
  224. package/scripts/josh/josh-command-map.ts +2 -0
  225. package/scripts/josh/josh-command-types.ts +11 -0
  226. package/scripts/josh/josh-commands-ai.ts +4 -4
  227. package/scripts/josh/josh-commands-backlog.ts +4 -1
  228. package/scripts/josh/josh-commands-development.ts +10 -0
  229. package/scripts/josh/josh-commands-document.ts +1 -0
  230. package/scripts/josh/josh-commands-lane.ts +22 -0
  231. package/scripts/josh/josh-commands-maintenance.ts +4 -4
  232. package/scripts/josh/josh-commands-run.ts +12 -7
  233. package/scripts/josh/josh-in-process.ts +28 -9
  234. package/scripts/josh/josh-logic.ts +33 -15
  235. package/scripts/josh/process-identity.ts +1 -3
  236. package/scripts/josh/session-language-cli.ts +3 -4
  237. package/scripts/lane/lane-await.ts +5 -1
  238. package/scripts/lane/lane-child-invocation.ts +2 -2
  239. package/scripts/lane/lane-cli.ts +3 -3
  240. package/scripts/lane/lane-dispatch.ts +14 -10
  241. package/scripts/lane/lane-environment.ts +5 -1
  242. package/scripts/lane/lane-handoff.ts +1 -1
  243. package/scripts/lane/lane-install.ts +2 -13
  244. package/scripts/lane/lane-launch-cli.ts +15 -1
  245. package/scripts/lane/lane-ledger.ts +145 -0
  246. package/scripts/lane/lane-leftover.ts +1 -1
  247. package/scripts/lane/lane-load.ts +108 -0
  248. package/scripts/lane/lane-measure-cli.ts +149 -0
  249. package/scripts/lane/lane-occupancy.ts +3 -0
  250. package/scripts/lane/lane-reap.ts +1 -3
  251. package/scripts/lane/lane-relaunch.ts +1 -1
  252. package/scripts/lane/lane-stats.ts +224 -0
  253. package/scripts/lane/lane-vacant.ts +36 -0
  254. package/scripts/lane/openai-lane-ship.ts +2 -2
  255. package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
  256. package/scripts/lane/openai-lane-supervisor.ts +7 -165
  257. package/scripts/lane/openai-review-broker.ts +2 -4
  258. package/scripts/lib/ancestor-directories.ts +35 -0
  259. package/scripts/lib/animation-helpers.ts +2 -2
  260. package/scripts/lib/buffered-process.ts +3 -7
  261. package/scripts/lib/cli-flags.ts +71 -1
  262. package/scripts/lib/spawn-exit.ts +2 -2
  263. package/scripts/lib/timeouts.ts +35 -2
  264. package/scripts/lines/line-budget.ts +4 -6
  265. package/scripts/notify/telegram-test.ts +6 -0
  266. package/scripts/observations/observation-ledger-line.ts +6 -6
  267. package/scripts/observations/observation-ledger-migrate.ts +55 -4
  268. package/scripts/observations/observation-ledger.ts +26 -7
  269. package/scripts/overrides/lockfile-overrides.ts +1 -1
  270. package/scripts/overrides/overrides-logic.ts +1 -1
  271. package/scripts/propagate/propagate-git.ts +1 -1
  272. package/scripts/propagate/propagate-publish.ts +2 -2
  273. package/scripts/propagate/propagate-steps.ts +20 -15
  274. package/scripts/propagate/propagate.ts +36 -27
  275. package/scripts/refactor/import-graph.ts +18 -1
  276. package/scripts/refactor/refactor-lint.ts +4 -4
  277. package/scripts/repo/repository-labels.ts +2 -2
  278. package/scripts/repo/required-checks-logic.ts +1 -3
  279. package/scripts/review/live-evidence.ts +2 -4
  280. package/scripts/review/review-brief-cli.ts +1 -1
  281. package/scripts/review/review-finding-ledger.ts +1 -1
  282. package/scripts/review/review-findings-cli.ts +1 -3
  283. package/scripts/rules/decision-oracle-batch.ts +128 -0
  284. package/scripts/rules/decision-oracle-tokens.ts +21 -0
  285. package/scripts/rules/decision-oracle.ts +8 -120
  286. package/scripts/rules/delivered-rules.ts +26 -182
  287. package/scripts/rules/direct-filing.ts +1 -1
  288. package/scripts/rules/early-heartbeat.ts +3 -3
  289. package/scripts/rules/git-argv.ts +1 -1
  290. package/scripts/rules/git-force.ts +1 -1
  291. package/scripts/rules/implementation-cut.ts +2 -2
  292. package/scripts/rules/index-guard.ts +1 -1
  293. package/scripts/rules/issue-comments.ts +118 -0
  294. package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
  295. package/scripts/rules/lane-background.ts +2 -2
  296. package/scripts/rules/lane-park.ts +18 -0
  297. package/scripts/rules/lane-split-park.ts +128 -0
  298. package/scripts/rules/oracle-firing.ts +1 -1
  299. package/scripts/rules/poll-loop.ts +1 -1
  300. package/scripts/rules/pre-gate-cut.ts +1 -1
  301. package/scripts/rules/protected-files.ts +59 -4
  302. package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
  303. package/scripts/rules/shell-body-trigger.ts +1 -1
  304. package/scripts/rules/stop-rules.ts +18 -7
  305. package/scripts/rules/test-declared-commit.ts +4 -4
  306. package/scripts/rules/wip-cap.ts +63 -0
  307. package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
  308. package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
  309. package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
  310. package/scripts/run/{run-carry.ts → carry/run-carry.ts} +1 -1
  311. package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
  312. package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +2 -2
  313. package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
  314. package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
  315. package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
  316. package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
  317. package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
  318. package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +16 -2
  319. package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
  320. package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
  321. package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
  322. package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
  323. package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
  324. package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
  325. package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
  326. package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
  327. package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
  328. package/scripts/run/run-ending.ts +2 -2
  329. package/scripts/run/run-halfrun-resume.ts +6 -1
  330. package/scripts/run/run-headless.ts +1 -1
  331. package/scripts/run/run-invocation.ts +85 -84
  332. package/scripts/run/run-liveness.ts +1 -1
  333. package/scripts/run/run-parent-cut-hook.ts +1 -1
  334. package/scripts/run/run-preflight.ts +6 -6
  335. package/scripts/run/run-prep-cli.ts +1 -1
  336. package/scripts/run/run-prrun-resume.ts +1 -1
  337. package/scripts/run/run-report-cli.ts +4 -4
  338. package/scripts/run/run-report.ts +2 -2
  339. package/scripts/run/run-status-cli.ts +1 -1
  340. package/scripts/run/run-step-cli.ts +3 -3
  341. package/scripts/run/run-step.ts +2 -2
  342. package/scripts/run/run-stop-notify.ts +1 -1
  343. package/scripts/run/run-stranded-detect.ts +4 -4
  344. package/scripts/run/run-stranded.ts +2 -2
  345. package/scripts/run/run-watcher-guard-cli.ts +1 -1
  346. package/scripts/run/run-watcher-guard.ts +5 -3
  347. package/scripts/run/run-watcher-hook.ts +1 -1
  348. package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +2 -2
  349. package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
  350. package/scripts/run/ship/run-ship-lock.ts +50 -0
  351. package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
  352. package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
  353. package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +4 -3
  354. package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
  355. package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
  356. package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +11 -12
  357. package/scripts/run/ship/run-ship-stop-text.ts +13 -0
  358. package/scripts/run/{run-ship-sync.ts → ship/run-ship-sync.ts} +25 -11
  359. package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
  360. package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +23 -8
  361. package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +3 -3
  362. package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +59 -8
  363. package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
  364. package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +8 -2
  365. package/scripts/run/wake/run-wake-restart.ts +58 -0
  366. package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
  367. package/scripts/run/{run-wake.ts → wake/run-wake.ts} +1 -1
  368. package/scripts/safe-chain/preinstall-command.ts +20 -21
  369. package/scripts/safe-chain/project-config.ts +19 -15
  370. package/scripts/sync/sync-ai-files.ts +1 -1
  371. package/scripts/sync/sync-configs.ts +2 -1
  372. package/scripts/sync/sync.ts +15 -2
  373. package/scripts/test/unit-worker-share.ts +1 -0
  374. package/scripts/time-runtime/time-background.ts +5 -1
  375. package/scripts/time-runtime/time-instant.ts +3 -1
  376. package/scripts/time-runtime/time-spans.ts +6 -139
  377. package/scripts/time-runtime/time-tool-call.ts +160 -0
  378. package/scripts/version/effective-upstream.ts +2 -20
  379. package/scripts/version/latest-corepack.ts +17 -17
  380. package/scripts/version/latest-regression.ts +1 -1
  381. package/scripts/version/latest-update.ts +4 -4
  382. package/scripts/version/npm-registry.ts +20 -5
  383. package/scripts/version/publishable-range-check.ts +2 -2
  384. package/scripts/version/publishable-range.ts +1 -1
  385. package/scripts/version/release-age.ts +48 -38
  386. package/scripts/version/release-hold.ts +1 -1
  387. package/scripts/version/safe-chain-version-update.ts +40 -0
  388. package/scripts/version/upgrade-shell-command.ts +48 -8
  389. package/scripts/version/version-check-logic.ts +23 -14
  390. package/scripts/version/version-commands.ts +23 -11
  391. package/scripts/version/version-remote.ts +28 -13
  392. package/templates/pnpm-workspace.basic.yaml +4 -0
  393. package/templates/workflows/dependabot-auto-merge.yml +5 -5
  394. package/templates/workflows/github-release.yml +2 -0
  395. package/scripts/build/build-config-merge.ts +0 -27
  396. package/scripts/build/build-managed-marker.ts +0 -27
  397. package/scripts/build/build-self-sync-guard.ts +0 -27
  398. package/scripts/build/build-version.ts +0 -26
  399. package/scripts/version/preinstall-version-update.ts +0 -86
  400. /package/scripts/git/{schemas.ts → git-schemas.ts} +0 -0
  401. /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
  402. /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
  403. /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
  404. /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
  405. /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
  406. /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
  407. /package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +0 -0
  408. /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
  409. /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
  410. /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
  411. /package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +0 -0
  412. /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
  413. /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
  414. /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
  415. /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
  416. /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
  417. /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
  418. /package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +0 -0
  419. /package/scripts/run/{run-ship-pre-detach.ts → ship/run-ship-pre-detach.ts} +0 -0
  420. /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
  421. /package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +0 -0
  422. /package/scripts/run/{run-ship.ts → ship/run-ship.ts} +0 -0
  423. /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
  424. /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
  425. /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
@@ -1,6 +1,6 @@
1
1
  import { cli_body } from '#scripts/josh/cli-body'
2
2
  import type { InsertKind, InsertPosition } from './epic-chains'
3
- import { epic_cli_argv, ISSUE_NUMBER_PATTERN } from './epic-cli-argv'
3
+ import { epic_cli_argv, ISSUE_NUMBER_PATTERN, type EpicArgv, type FormFlags } from './epic-cli-argv'
4
4
  import { epic_cli_remove } from './epic-cli-remove'
5
5
  import { epic_parse, type ExternalChild } from './epic-parse'
6
6
 
@@ -27,25 +27,22 @@ const ORDER_AFTER_FLAG = '--order-after'
27
27
  // from a file, or from stdin as `-`. The text is a judgement, so the caller writes it; what the command
28
28
  // contributes is placing it in the epic's `## Decisions` and on each child (joshuafolkken/kit#1350).
29
29
  const DECISION_FLAG = '--decision-file'
30
- // Which flags consume the argument after them — per parser, for the reason `epic-cli-argv.ts` gives.
31
- const VALUE_FLAGS: ReadonlySet<string> = new Set([RATIONALE_FLAG, ORIGIN_FLAG])
32
- const ADD_VALUE_FLAGS: ReadonlySet<string> = new Set([
33
- BEFORE_FLAG,
34
- AFTER_FLAG,
35
- ORDER_BEFORE_FLAG,
36
- ORDER_AFTER_FLAG,
37
- DECISION_FLAG,
38
- ])
39
- const ADD_KNOWN_FLAGS: ReadonlySet<string> = new Set([
40
- ADD_FLAG,
41
- BEFORE_FLAG,
42
- AFTER_FLAG,
43
- ORDER_BEFORE_FLAG,
44
- ORDER_AFTER_FLAG,
45
- DECISION_FLAG,
46
- ])
30
+ // Which flags each form knows, and which of them consume the argument after them — per form, for the
31
+ // reason `epic-cli-argv.ts` gives.
32
+ const CREATE_FORM: FormFlags = {
33
+ switches: [ORDERED_FLAG],
34
+ value_flags: [RATIONALE_FLAG, ORIGIN_FLAG],
35
+ }
36
+ const PROMOTE_FORM: FormFlags = { ...CREATE_FORM, switches: [PROMOTE_FLAG, ORDERED_FLAG] }
37
+ const RECONCILE_FORM: FormFlags = { switches: [RECONCILE_FLAG] }
38
+ const CHECK_FORM: FormFlags = { switches: [] }
39
+ const ADD_FORM: FormFlags = {
40
+ switches: [ADD_FLAG],
41
+ value_flags: [BEFORE_FLAG, AFTER_FLAG, ORDER_BEFORE_FLAG, ORDER_AFTER_FLAG, DECISION_FLAG],
42
+ }
47
43
 
48
- const { count_flag, is_value_unusable, read_flag_value } = epic_cli_argv
44
+ const { count_flag, is_switch_set, is_value_unusable, positionals_of, read_flag_value, read_form } =
45
+ epic_cli_argv
49
46
 
50
47
  interface CreateArguments {
51
48
  title: string
@@ -65,13 +62,6 @@ interface PromoteArguments {
65
62
  origin?: string | undefined
66
63
  }
67
64
 
68
- function to_positional_arguments(
69
- argv: ReadonlyArray<string>,
70
- value_flags: ReadonlySet<string> = VALUE_FLAGS,
71
- ): Array<string> {
72
- return epic_cli_argv.to_positional_arguments(argv, value_flags)
73
- }
74
-
75
65
  // Deduplicated because a repeated number would render a duplicate task-list row, and with
76
66
  // `--ordered` would ask GitHub to make an issue block itself.
77
67
  function to_child_numbers(raw_children: ReadonlyArray<string>): Array<number> {
@@ -83,22 +73,33 @@ function to_child_numbers(raw_children: ReadonlyArray<string>): Array<number> {
83
73
  return [...new Set(numbers)]
84
74
  }
85
75
 
86
- // The title comes first so the children can stay a bare list of numbers, which is how they are
87
- // written down when the split is made.
88
- function parse_create_arguments(argv: ReadonlyArray<string>): CreateArguments | undefined {
89
- const [title, ...raw_children] = to_positional_arguments(argv)
76
+ // The flags a creation and a promotion share, read the same way for both.
77
+ function read_batch_flags(
78
+ parsed: EpicArgv,
79
+ ): Pick<CreateArguments, 'is_ordered' | 'rationale_path' | 'origin'> {
80
+ return {
81
+ is_ordered: is_switch_set(parsed, ORDERED_FLAG),
82
+ rationale_path: read_flag_value(parsed, RATIONALE_FLAG),
83
+ origin: read_flag_value(parsed, ORIGIN_FLAG),
84
+ }
85
+ }
86
+
87
+ function to_create_arguments(parsed: EpicArgv): CreateArguments | undefined {
88
+ const [title, ...raw_children] = parsed.positionals
90
89
  if (title === undefined || title.length === 0) return undefined
91
90
 
92
91
  const children = to_child_numbers(raw_children)
93
92
  if (children.length === 0) return undefined
94
93
 
95
- return {
96
- title,
97
- children,
98
- is_ordered: argv.includes(ORDERED_FLAG),
99
- rationale_path: read_flag_value(argv, RATIONALE_FLAG),
100
- origin: read_flag_value(argv, ORIGIN_FLAG),
101
- }
94
+ return { title, children, ...read_batch_flags(parsed) }
95
+ }
96
+
97
+ // The title comes first so the children can stay a bare list of numbers, which is how they are
98
+ // written down when the split is made.
99
+ function parse_create_arguments(argv: ReadonlyArray<string>): CreateArguments | undefined {
100
+ const parsed = read_form(argv, CREATE_FORM)
101
+
102
+ return parsed === undefined ? undefined : to_create_arguments(parsed)
102
103
  }
103
104
 
104
105
  // Whether the invocation is a promotion rather than a creation.
@@ -118,7 +119,7 @@ interface ReconcileArguments {
118
119
  // The epic to reconcile. The number after `--reconcile` is the only subject; there are no children
119
120
  // and no position — the repair reads what the epic already records rather than being told an order.
120
121
  function parse_reconcile_arguments(argv: ReadonlyArray<string>): ReconcileArguments | undefined {
121
- const [raw_epic] = to_positional_arguments(argv)
122
+ const [raw_epic] = positionals_of(argv, RECONCILE_FORM)
122
123
  if (raw_epic === undefined || !ISSUE_NUMBER_PATTERN.test(raw_epic)) return undefined
123
124
 
124
125
  return { epic_number: Number(raw_epic) }
@@ -127,20 +128,20 @@ function parse_reconcile_arguments(argv: ReadonlyArray<string>): ReconcileArgume
127
128
  // The promoted issue and its children. The number after `--promote` is the epic; everything else
128
129
  // positional is a child. Refused when the epic would also be listed as its own child, which would
129
130
  // have it block itself under `--ordered`.
130
- function parse_promote_arguments(argv: ReadonlyArray<string>): PromoteArguments | undefined {
131
- const [raw_epic, ...raw_children] = to_positional_arguments(argv)
131
+ function to_promote_arguments(parsed: EpicArgv): PromoteArguments | undefined {
132
+ const [raw_epic, ...raw_children] = parsed.positionals
132
133
  if (raw_epic === undefined || !ISSUE_NUMBER_PATTERN.test(raw_epic)) return undefined
133
134
  const epic_number = Number(raw_epic)
134
135
  const children = to_child_numbers(raw_children).filter((child) => child !== epic_number)
135
136
  if (children.length === 0) return undefined
136
137
 
137
- return {
138
- epic_number,
139
- children,
140
- is_ordered: argv.includes(ORDERED_FLAG),
141
- rationale_path: read_flag_value(argv, RATIONALE_FLAG),
142
- origin: read_flag_value(argv, ORIGIN_FLAG),
143
- }
138
+ return { epic_number, children, ...read_batch_flags(parsed) }
139
+ }
140
+
141
+ function parse_promote_arguments(argv: ReadonlyArray<string>): PromoteArguments | undefined {
142
+ const parsed = read_form(argv, PROMOTE_FORM)
143
+
144
+ return parsed === undefined ? undefined : to_promote_arguments(parsed)
144
145
  }
145
146
 
146
147
  // `--add <E> <N...> [--before <M> | --after <M> | --order-before <M> | --order-after <M>]`: the epic
@@ -195,36 +196,33 @@ const POSITION_FLAGS: ReadonlyArray<PositionFlag> = [
195
196
  // of each direction, or a dependency-writing flag beside an order-only one. `read_flag_value` would
196
197
  // answer with the first, which is a silent choice rather than a refusal — and here the silent choice
197
198
  // would decide whether a `blocked-by` is written at all.
198
- function is_position_ambiguous(argv: ReadonlyArray<string>): boolean {
199
- return POSITION_FLAGS.reduce((total, entry) => total + count_flag(argv, entry.flag), 0) > 1
199
+ function count_position_flags(parsed: EpicArgv): number {
200
+ return POSITION_FLAGS.reduce((total, entry) => total + count_flag(parsed, entry.flag), 0)
200
201
  }
201
202
 
202
203
  interface PositionTarget extends PositionFlag {
203
204
  raw: string
204
205
  }
205
206
 
206
- function to_position_target(
207
- argv: ReadonlyArray<string>,
208
- entry: PositionFlag,
209
- ): PositionTarget | undefined {
210
- const raw = read_flag_value(argv, entry.flag)
207
+ function to_position_target(parsed: EpicArgv, entry: PositionFlag): PositionTarget | undefined {
208
+ const raw = read_flag_value(parsed, entry.flag)
211
209
 
212
210
  return raw === undefined ? undefined : { ...entry, raw }
213
211
  }
214
212
 
215
- function read_position_target(argv: ReadonlyArray<string>): PositionTarget | undefined {
216
- if (is_position_ambiguous(argv)) return undefined
213
+ function read_position_target(parsed: EpicArgv): PositionTarget | undefined {
214
+ if (count_position_flags(parsed) > 1) return undefined
217
215
 
218
- return POSITION_FLAGS.map((entry) => to_position_target(argv, entry)).find(
216
+ return POSITION_FLAGS.map((entry) => to_position_target(parsed, entry)).find(
219
217
  (found) => found !== undefined,
220
218
  )
221
219
  }
222
220
 
223
221
  // A target that is not an issue number is refused for the same reason two flags are: guessing would
224
222
  // insert somewhere.
225
- function parse_position(argv: ReadonlyArray<string>): PositionOutcome {
226
- const has_flag = POSITION_FLAGS.some((entry) => argv.includes(entry.flag))
227
- const target = read_position_target(argv)
223
+ function parse_position(parsed: EpicArgv): PositionOutcome {
224
+ const has_flag = count_position_flags(parsed) > 0
225
+ const target = read_position_target(parsed)
228
226
  if (target === undefined) return has_flag ? REFUSED_POSITION : NO_POSITION
229
227
  if (!ISSUE_NUMBER_PATTERN.test(target.raw)) return REFUSED_POSITION
230
228
 
@@ -235,10 +233,6 @@ function parse_position(argv: ReadonlyArray<string>): PositionOutcome {
235
233
  }
236
234
  }
237
235
 
238
- function has_unknown_flag(argv: ReadonlyArray<string>): boolean {
239
- return epic_cli_argv.has_unknown_flag(argv, ADD_KNOWN_FLAGS)
240
- }
241
-
242
236
  // Whether *this* is why the insertion could not be read, so the refusal can say so. Without it the
243
237
  // caller prints the generic requirements line, and a shell that expanded `--decision-file $REC` to
244
238
  // nothing is answered with "an epic number and at least one child issue number are required" — advice
@@ -248,9 +242,9 @@ function is_decision_path_unusable(argv: ReadonlyArray<string>): boolean {
248
242
  }
249
243
 
250
244
  function read_add_subject(
251
- argv: ReadonlyArray<string>,
245
+ parsed: EpicArgv,
252
246
  ): { epic_number: number; children: Array<number> } | undefined {
253
- const [raw_epic, ...raw_children] = to_positional_arguments(argv, ADD_VALUE_FLAGS)
247
+ const [raw_epic, ...raw_children] = parsed.positionals
254
248
  if (raw_epic === undefined || !ISSUE_NUMBER_PATTERN.test(raw_epic)) return undefined
255
249
  const epic_number = Number(raw_epic)
256
250
  const children = to_child_numbers(raw_children).filter((child) => child !== epic_number)
@@ -258,21 +252,28 @@ function read_add_subject(
258
252
  return children.length === 0 ? undefined : { epic_number, children }
259
253
  }
260
254
 
261
- function parse_add_arguments(argv: ReadonlyArray<string>): AddArguments | undefined {
262
- if (has_unknown_flag(argv) || is_decision_path_unusable(argv)) return undefined
263
- const subject = read_add_subject(argv)
255
+ function to_add_arguments(parsed: EpicArgv): AddArguments | undefined {
256
+ const subject = read_add_subject(parsed)
264
257
  if (subject === undefined) return undefined
265
- const outcome = parse_position(argv)
258
+ const outcome = parse_position(parsed)
266
259
  if (outcome.is_refused) return undefined
267
260
 
268
261
  return {
269
262
  ...subject,
270
263
  position: outcome.position,
271
264
  is_order_only: outcome.is_order_only,
272
- decision_path: read_flag_value(argv, DECISION_FLAG),
265
+ decision_path: read_flag_value(parsed, DECISION_FLAG),
273
266
  }
274
267
  }
275
268
 
269
+ // An unknown flag is refused by the strict read: a mistyped positioning flag would otherwise leave its
270
+ // value positional, so it becomes a child and the edit lands somewhere else (joshuafolkken/kit#890).
271
+ function parse_add_arguments(argv: ReadonlyArray<string>): AddArguments | undefined {
272
+ const parsed = is_decision_path_unusable(argv) ? undefined : read_form(argv, ADD_FORM)
273
+
274
+ return parsed === undefined ? undefined : to_add_arguments(parsed)
275
+ }
276
+
276
277
  // The one refusal `--add` has to explain rather than merely report. `into owner/repo#N` is a legal
277
278
  // thing for a person to type, and this command cannot serve it: it reads and edits issues in the
278
279
  // repository it is run from. Falling through to the usage line would read as "that form does not
@@ -288,7 +289,7 @@ interface CrossRepoAddTarget {
288
289
  // problem, and
289
290
  // a suggestion built from it would drop or mangle what the person typed.
290
291
  function find_cross_repo_add_target(argv: ReadonlyArray<string>): CrossRepoAddTarget | undefined {
291
- const [raw_epic] = to_positional_arguments(argv, ADD_VALUE_FLAGS)
292
+ const [raw_epic] = positionals_of(argv, ADD_FORM)
292
293
  if (raw_epic === undefined) return undefined
293
294
  const epic = epic_parse.parse_external_reference(raw_epic)
294
295
  if (epic === undefined) return undefined
@@ -351,7 +352,7 @@ function format_cross_repo_refusal(found: CrossRepoAddTarget): string {
351
352
  }
352
353
 
353
354
  function parse_check_argument(argv: ReadonlyArray<string>): number | undefined {
354
- const [raw] = to_positional_arguments(argv)
355
+ const [raw] = positionals_of(argv, CHECK_FORM)
355
356
  if (raw === undefined || !ISSUE_NUMBER_PATTERN.test(raw)) return undefined
356
357
 
357
358
  return Number(raw)
@@ -1,6 +1,6 @@
1
1
  import { git_gh_command } from '#scripts/gh/git-gh-command'
2
+ import { rest_comment_schema, type RestCommentData } from '#scripts/git/git-schemas'
2
3
  import { read_json_listing } from '#scripts/git/parse-json-array'
3
- import { rest_comment_schema, type RestCommentData } from '#scripts/git/schemas'
4
4
  import { epic_parse, type ExternalChild } from './epic-parse'
5
5
 
6
6
  // The comment the epic auto-close announces itself with, and how a run recognizes the one a previous
@@ -1,10 +1,10 @@
1
1
  import { git_gh_command } from '#scripts/gh/git-gh-command'
2
+ import { epic_child_schema, epic_issue_schema, type EpicChildData } from '#scripts/git/git-schemas'
2
3
  import { cutoff_cause, cutoff_of, type ScanCutoff } from '#scripts/git/listing-cutoff'
3
4
  import {
4
5
  parse_json_array_or_undefined,
5
6
  parse_json_object_safe,
6
7
  } from '#scripts/git/parse-json-array'
7
- import { epic_child_schema, epic_issue_schema, type EpicChildData } from '#scripts/git/schemas'
8
8
  import { EPIC_LABEL } from '#scripts/issue/issue-labels'
9
9
  import { error_text } from '#scripts/lib/error-message'
10
10
  import { epic_close_comment } from './epic-close-comment'
@@ -2,6 +2,7 @@ import { repo_map_logic } from '#scripts/discovery/repo-map-logic'
2
2
  import { repo_origin } from '#scripts/discovery/repo-origin'
3
3
  import { git_gh_api_path } from '#scripts/gh/git-gh-api-path'
4
4
  import { git_gh_exec } from '#scripts/gh/git-gh-exec'
5
+ import { GH_API_TIMEOUT_MS } from '#scripts/lib/timeouts'
5
6
  import { propagate_publish } from '#scripts/propagate/propagate-publish'
6
7
  import { derive_versions_endpoint } from '#scripts/version/version-command-config'
7
8
  import { load } from 'js-yaml'
@@ -69,7 +70,6 @@ function is_published(repo: string, version: string): boolean {
69
70
  return propagate_publish.is_version_published(versions, version)
70
71
  }
71
72
 
72
- const GH_TIMEOUT_MS = 20_000
73
73
  // The manifest arrives base64-encoded in the API's `content` field; `jq` decodes and reads it.
74
74
  const VERSION_JQ = '.content | @base64d | fromjson | {version, private, workspaces} | tojson'
75
75
  const PNPM_WORKSPACE_FILE = 'pnpm-workspace.yaml'
@@ -145,7 +145,7 @@ function pnpm_workspace_path(repo: string): string {
145
145
 
146
146
  // A read that failed for any reason other than a missing file leaves the layout unknown.
147
147
  function classify_workspace_failure(repo: string): WorkspaceCheck {
148
- const status = git_gh_exec.exec_gh_api_status_sync(pnpm_workspace_path(repo), GH_TIMEOUT_MS)
148
+ const status = git_gh_exec.exec_gh_api_status_sync(pnpm_workspace_path(repo), GH_API_TIMEOUT_MS)
149
149
 
150
150
  return status === NOT_FOUND_STATUS ? 'single' : 'unreadable'
151
151
  }
@@ -154,7 +154,7 @@ function read_pnpm_workspace(repo: string): WorkspaceCheck {
154
154
  const stdout = git_gh_exec.read_gh_api_sync({
155
155
  path: pnpm_workspace_path(repo),
156
156
  jq_filter: WORKSPACE_JQ,
157
- timeout_ms: GH_TIMEOUT_MS,
157
+ timeout_ms: GH_API_TIMEOUT_MS,
158
158
  })
159
159
  if (stdout === undefined) return classify_workspace_failure(repo)
160
160
 
@@ -229,7 +229,7 @@ function to_manifest(repo: string, stdout: string): ManifestAnswer {
229
229
  // here is trustworthy in a way a registry 404 is not: the repository's issues are already being read,
230
230
  // so access is established and what is missing is the file.
231
231
  function classify_manifest_failure(repo: string): ManifestAnswer {
232
- const status = git_gh_exec.exec_gh_api_status_sync(manifest_path(repo), GH_TIMEOUT_MS)
232
+ const status = git_gh_exec.exec_gh_api_status_sync(manifest_path(repo), GH_API_TIMEOUT_MS)
233
233
 
234
234
  return { kind: status === NOT_FOUND_STATUS ? 'absent' : 'unreadable' }
235
235
  }
@@ -238,7 +238,7 @@ function fetch_manifest(repo: string): ManifestAnswer {
238
238
  const stdout = git_gh_exec.read_gh_api_sync({
239
239
  path: manifest_path(repo),
240
240
  jq_filter: VERSION_JQ,
241
- timeout_ms: GH_TIMEOUT_MS,
241
+ timeout_ms: GH_API_TIMEOUT_MS,
242
242
  })
243
243
 
244
244
  return stdout === undefined ? classify_manifest_failure(repo) : to_manifest(repo, stdout)
@@ -121,7 +121,7 @@ function find_stuck_children(children: ReadonlyArray<EpicChild>): Array<string>
121
121
  const index = index_children(children)
122
122
  const remaining = new Set(children.map((child) => key_of(child)))
123
123
 
124
- const peel = (): number => {
124
+ function peel(): number {
125
125
  const ready = [...remaining].filter((node) => !has_remaining_blocker(index, node, remaining))
126
126
 
127
127
  for (const node of ready) remaining.delete(node)
@@ -1,7 +1,7 @@
1
1
  import { git_gh_command } from '#scripts/gh/git-gh-command'
2
+ import type { OpenIssueData } from '#scripts/git/git-schemas'
2
3
  import { cutoff_of, type ScanCutoff } from '#scripts/git/listing-cutoff'
3
4
  import { parse_json_array_or_undefined } from '#scripts/git/parse-json-array'
4
- import type { OpenIssueData } from '#scripts/git/schemas'
5
5
  import { EPIC_LABEL, has_any_label } from '#scripts/issue/issue-labels'
6
6
  import { z } from 'zod'
7
7
  import { epic_parse } from './epic-parse'
@@ -1,6 +1,6 @@
1
1
  import { git_gh_issue_rest } from '#scripts/gh/git-gh-issue-rest'
2
+ import { blocked_by_schema } from '#scripts/git/git-schemas'
2
3
  import { parse_json_object_safe } from '#scripts/git/parse-json-array'
3
- import { blocked_by_schema } from '#scripts/git/schemas'
4
4
  import { z } from 'zod'
5
5
  import type { IssueReference } from './epic-reference'
6
6
 
@@ -29,11 +29,7 @@ const PULL_REQUEST_SEGMENT = '/pull/'
29
29
  const epic_issue_schema = z.object({
30
30
  number: z.number(),
31
31
  title: z.string().default(''),
32
- body: z
33
- .string()
34
- .nullable()
35
- .default('')
36
- .transform((value) => value ?? ''),
32
+ body: z.union([z.string(), z.null().transform(() => '')]).default(''),
37
33
  state: z.string().default(UNKNOWN_STATE),
38
34
  // The issue endpoint answers for a pull request as readily as for an issue, and nothing in the
39
35
  // other fields separates them — an open PR reports `state: OPEN`, a merged one `MERGED`. The URL
@@ -187,10 +187,10 @@ function solo_offer(
187
187
  read: BusyRead,
188
188
  request: LaneRequest,
189
189
  ): LaneOffer {
190
- const { offered, notice } = epic_solo.select(answer.children, read, request.repo)
190
+ const { offered, notice = '' } = epic_solo.select(answer.children, read, request.repo)
191
191
 
192
192
  if (offered.length === NO_LANES && answer.children.length > NO_LANES) {
193
- return { children: [], verdict: WAIT_VERDICT, notice: notice ?? '' }
193
+ return { children: [], verdict: WAIT_VERDICT, notice }
194
194
  }
195
195
 
196
196
  return { ...answer, children: offered, notice: offered_notice(offered, read, request) }
@@ -5,6 +5,7 @@ import { git_gh_command } from '#scripts/gh/git-gh-command'
5
5
  import { PROJECT_ROOT } from '#scripts/init/init-paths'
6
6
  import { josh_environment_file } from '#scripts/josh/josh-environment-file'
7
7
  import { lane_capacity } from '#scripts/lane/lane-capacity'
8
+ import { cli_flags } from '#scripts/lib/cli-flags'
8
9
  import { issue_citation } from '#scripts/rules/issue-citation'
9
10
  import { epic_classify } from './epic-classify'
10
11
  import { epic_cross_repo } from './epic-cross-repo'
@@ -31,12 +32,10 @@ import { epic_report, type EpicNextResult } from './epic-report'
31
32
  const SUCCESS_EXIT_CODE = 0
32
33
  const FAILURE_EXIT_CODE = 1
33
34
  const ARGV_OFFSET = 2
34
- const REPO_FLAG = '--repo'
35
- // Asking for every free lane is opt-in, so the answer stays one token for a caller that has not been
36
- // changed. `--repo` alone still prints exactly one number — what joshuafolkken/kit#1491 changed for
37
- // it is *when* that number appears, not how many arrive (joshuafolkken/kit#1491).
38
- const LANES_FLAG = '--lanes'
39
- const FLAG_PREFIX = '--'
35
+ // Asking for every free lane (`--lanes`) is opt-in, so the answer stays one token for a caller that
36
+ // has not been changed. `--repo` alone still prints exactly one number — what joshuafolkken/kit#1491
37
+ // changed for it is *when* that number appears, not how many arrive (joshuafolkken/kit#1491).
38
+ const OPTIONS = { repo: { type: 'string' }, lanes: { type: 'boolean' } } as const
40
39
  const USAGE =
41
40
  'Usage: josh epic:next <epic-number|owner/repo#number>... [--repo <owner/repo>] [--lanes]'
42
41
  // A repository that could not be read is refused rather than stood in for. Since
@@ -66,30 +65,6 @@ interface NextOptions {
66
65
  usage?: string
67
66
  }
68
67
 
69
- // A value that is itself a flag is no value at all. `--repo --lanes` used to narrow to a repository
70
- // literally named `--lanes` and report `No runnable child in --lanes` with exit 0; refused here, it
71
- // reaches the usage line instead — the caller meant a repository and named none.
72
- function parse_repo(rest: ReadonlyArray<string>): string | undefined {
73
- const flag_index = rest.indexOf(REPO_FLAG)
74
- const value = flag_index === -1 ? undefined : rest[flag_index + 1]
75
-
76
- return value?.startsWith(FLAG_PREFIX) === true ? undefined : value
77
- }
78
-
79
- // The leading arguments are epic references and everything from the first flag on is options. Split
80
- // on the flag rather than taking a fixed count, so `epic:next 858 909 --repo X` reads two epics and
81
- // `epic:next 858 --repo X` still reads one (joshuafolkken/kit#1493).
82
- function split_at_flag(argv: ReadonlyArray<string>): {
83
- head: ReadonlyArray<string>
84
- rest: ReadonlyArray<string>
85
- } {
86
- const flag_at = argv.findIndex((entry) => entry.startsWith(FLAG_PREFIX))
87
-
88
- return flag_at === -1
89
- ? { head: argv, rest: [] }
90
- : { head: argv.slice(0, flag_at), rest: argv.slice(flag_at) }
91
- }
92
-
93
68
  // Every leading entry, or nothing. One entry that does not parse fails the whole read rather than
94
69
  // being dropped: a mistyped second epic would otherwise run the first one alone and report nothing
95
70
  // at all about the one that was missed — an unattended run silently doing half of what was asked.
@@ -106,21 +81,34 @@ function parse_references(head: ReadonlyArray<string>): ReadonlyArray<EpicRefere
106
81
  return references.length === 0 ? undefined : references
107
82
  }
108
83
 
84
+ function with_repo(
85
+ references: ReadonlyArray<EpicReference>,
86
+ values: { repo?: string; lanes?: boolean },
87
+ ): NextOptions {
88
+ const { repo, lanes: is_all_lanes = false } = values
89
+
90
+ if (repo === undefined) return is_all_lanes ? { usage: USAGE } : { references }
91
+
92
+ return { references, repo, is_all_lanes }
93
+ }
94
+
95
+ // The positional arguments are epic references and the flags are options, so `epic:next 858 909 --repo
96
+ // X` reads two epics and `epic:next 858 --repo X` still reads one (joshuafolkken/kit#1493). The read is
97
+ // strict: an unknown flag reaches the usage line rather than being ignored, and a value that is itself
98
+ // a flag is no value at all — `--repo --lanes` used to narrow to a repository literally named
99
+ // `--lanes` and report `No runnable child in --lanes` with exit 0.
100
+ //
109
101
  // `--lanes` without `--repo` is refused rather than ignored: the lane count is a property of one
110
102
  // repository, so the aggregate listing has nothing to apply it to, and a flag that silently does
111
- // nothing is a caller believing it asked for something.
103
+ // nothing is a caller believing it asked for something. An empty name (`--repo=` from an unset
104
+ // variable) is no repository either: narrowed to it, the answer is `No runnable child in .` and exit 0.
112
105
  function parse_options(argv: ReadonlyArray<string>): NextOptions {
113
- const { head, rest } = split_at_flag(argv)
114
- const references = parse_references(head)
115
- if (references === undefined) return { usage: USAGE }
116
- const repo = parse_repo(rest)
117
- const is_all_lanes = rest.includes(LANES_FLAG)
118
-
119
- if (repo === undefined) {
120
- return is_all_lanes || rest.includes(REPO_FLAG) ? { usage: USAGE } : { references }
121
- }
106
+ const parsed = cli_flags.arguments_of(argv, OPTIONS)
107
+ if (parsed === undefined) return { usage: USAGE }
108
+ const references = parse_references(parsed.positionals)
109
+ if (references === undefined || parsed.values.repo === '') return { usage: USAGE }
122
110
 
123
- return { references, repo, is_all_lanes }
111
+ return with_repo(references, parsed.values)
124
112
  }
125
113
 
126
114
  // The answer for one epic, from an already-fetched snapshot. Split from the fetch so the whole
@@ -446,7 +434,6 @@ const epic_next = {
446
434
  is_order_declared,
447
435
  repo_verdict,
448
436
  UNCHECKED_EXCLUSION,
449
- split_at_flag,
450
437
  parse_options,
451
438
  refuse_reads,
452
439
  views_of,
@@ -1,5 +1,5 @@
1
1
  import { setTimeout as sleep } from 'node:timers/promises'
2
- import type { OpenIssueData } from '#scripts/git/schemas'
2
+ import type { OpenIssueData } from '#scripts/git/git-schemas'
3
3
  import { has_any_label, IN_PROGRESS_LABEL, RUN_SOLO_LABEL } from '#scripts/issue/issue-labels'
4
4
  import { lane_await } from '#scripts/lane/lane-await'
5
5
  import { lane_registry } from '#scripts/lane/lane-registry'
@@ -20,12 +20,16 @@ type TriageVerdict = 'triage'
20
20
 
21
21
  const TRIAGE_VERDICT: TriageVerdict = 'triage'
22
22
 
23
- function is_triaged(child: EpicChild): boolean {
24
- const { labels } = child
25
-
23
+ // Read off a bare label list too, so `josh issue:file` refuses an untriaged `auto-ok` filing by the
24
+ // same answer the offer paths withhold it by (joshuafolkken/kit#3313).
25
+ function has_triage_label(labels: ReadonlyArray<string>): boolean {
26
26
  return has_label_name(labels, RUN_SOLO_LABEL) || has_label_name(labels, RUN_LANE_LABEL)
27
27
  }
28
28
 
29
+ function is_triaged(child: EpicChild): boolean {
30
+ return has_triage_label(child.labels)
31
+ }
32
+
29
33
  // The candidates nobody has judged, in the order they were given.
30
34
  function untriaged(children: ReadonlyArray<EpicChild>): ReadonlyArray<EpicChild> {
31
35
  return children.filter((child) => !is_triaged(child))
@@ -41,7 +45,7 @@ function message(children: ReadonlyArray<EpicChild>, repo: string): string {
41
45
  )
42
46
  }
43
47
 
44
- const epic_triage = { TRIAGE_VERDICT, is_triaged, message, untriaged }
48
+ const epic_triage = { TRIAGE_VERDICT, has_triage_label, is_triaged, message, untriaged }
45
49
 
46
50
  export type { TriageVerdict }
47
51
  export { epic_triage }
@@ -1,5 +1,5 @@
1
+ import { epic_subject_schema } from '#scripts/git/git-schemas'
1
2
  import { parse_json_object_safe } from '#scripts/git/parse-json-array'
2
- import { epic_subject_schema } from '#scripts/git/schemas'
3
3
  import { EPIC_LABEL, has_label_name } from '#scripts/issue/issue-labels'
4
4
  import { epic_parse, type DeclarationState } from './epic-parse'
5
5
 
@@ -1,8 +1,8 @@
1
1
  import { git_next_issues } from '#scripts/issue/git-next-issues'
2
2
  import { review_attest } from '#scripts/review/review-attest'
3
3
  import { review_stamps } from '#scripts/review/review-stamps'
4
- import { run_hold } from '#scripts/run/run-hold'
5
- import { run_progress_clock } from '#scripts/run/run-progress-clock'
4
+ import { run_hold } from '#scripts/run/hold/run-hold'
5
+ import { run_progress_clock } from '#scripts/run/progress/run-progress-clock'
6
6
  import { parse_completed_issue_number } from './followup-issue-number'
7
7
  import { git_followup_cleanup, type CleanupStep } from './git-followup-cleanup'
8
8
  import { git_followup_pending } from './git-followup-pending'
@@ -1,6 +1,6 @@
1
1
  import { git_gh_command } from '#scripts/gh/git-gh-command'
2
+ import { issue_label_schema } from '#scripts/git/git-schemas'
2
3
  import { parse_json_object_safe } from '#scripts/git/parse-json-array'
3
- import { issue_label_schema } from '#scripts/git/schemas'
4
4
  import { IN_PROGRESS_LABEL, label_name_of } from '#scripts/issue/issue-labels'
5
5
  import { z } from 'zod'
6
6
 
@@ -19,7 +19,7 @@ import { z } from 'zod'
19
19
  // No procedure covered the ordinary ending: merged, closed, and still marked as running.
20
20
 
21
21
  // The `number,labels,body` read answers labels as objects, and this is the one field of it this
22
- // step needs. The element shape is `schemas.ts`'s rather than a second spelling of it; the wrapper
22
+ // step needs. The element shape is `git-schemas.ts`'s rather than a second spelling of it; the wrapper
23
23
  // is local because a read's *field list* is what multiplies helpers when it is shared
24
24
  // (`git-gh-issue-read.ts`).
25
25
  const labels_read_schema = z.object({ labels: z.array(issue_label_schema).optional() })
@@ -66,8 +66,8 @@ function is_posted_since(comment: IssueComment, merged_at: string): boolean {
66
66
  return Date.parse(comment.created_at) >= Date.parse(merged_at)
67
67
  }
68
68
 
69
- function parse_comments(comments_json: string | undefined): Array<IssueComment> {
70
- const parsed = COMMENTS_SCHEMA.safeParse(json_value.parse_or_undefined(comments_json ?? ''))
69
+ function parse_comments(comments_json: string | undefined = ''): Array<IssueComment> {
70
+ const parsed = COMMENTS_SCHEMA.safeParse(json_value.parse_or_undefined(comments_json))
71
71
 
72
72
  return parsed.success ? parsed.data : []
73
73
  }