@joshuafolkken/kit 1.1035.0 → 1.1094.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 (605) hide show
  1. package/.claude/settings.json +9 -8
  2. package/.claude/skills/epic-commands/SKILL.md +5 -7
  3. package/.claude/skills/verify-ui/SKILL.md +1 -1
  4. package/.claude/skills/workflow-commands/SKILL.md +21 -13
  5. package/.claude/skills/workflow-commands/background-commands.md +5 -4
  6. package/.claude/skills/workflow-commands/backlogrun-child.md +3 -3
  7. package/.claude/skills/workflow-commands/backlogrun-lanes.md +9 -7
  8. package/.claude/skills/workflow-commands/backlogrun-park.md +6 -0
  9. package/.claude/skills/workflow-commands/backlogrun-progress.md +31 -37
  10. package/.claude/skills/workflow-commands/backlogrun-steps.md +25 -25
  11. package/.claude/skills/workflow-commands/backlogrun.md +2 -2
  12. package/.claude/skills/workflow-commands/chain-rule.md +6 -4
  13. package/.claude/skills/workflow-commands/delegation.md +3 -3
  14. package/.claude/skills/workflow-commands/followup-reference.md +3 -3
  15. package/.claude/skills/workflow-commands/followup.md +7 -3
  16. package/.claude/skills/workflow-commands/fullrun-steps.md +3 -2
  17. package/.claude/skills/workflow-commands/fullrun.md +3 -2
  18. package/.claude/skills/workflow-commands/halfrun.md +16 -19
  19. package/.claude/skills/workflow-commands/issue-comments.md +2 -2
  20. package/.claude/skills/workflow-commands/issue-fold-existing.md +10 -10
  21. package/.claude/skills/workflow-commands/issue-scout.md +1 -1
  22. package/.claude/skills/workflow-commands/kickoff.md +9 -4
  23. package/.claude/skills/workflow-commands/latest-gate.md +1 -1
  24. package/.claude/skills/workflow-commands/needs-human-review.md +1 -1
  25. package/.claude/skills/workflow-commands/observation-filing.md +44 -89
  26. package/.claude/skills/workflow-commands/pre-gate-cut.md +33 -36
  27. package/.claude/skills/workflow-commands/prerequisite.md +1 -1
  28. package/.claude/skills/workflow-commands/prrun.md +36 -0
  29. package/.claude/skills/workflow-commands/retrospective.md +1 -1
  30. package/.claude/skills/workflow-commands/working-tree-hold.md +24 -4
  31. package/.codex/hooks.json +16 -20
  32. package/.github/actions/setup-pnpm/action.yml +93 -0
  33. package/.github/dependabot.yml +5 -1
  34. package/.github/workflows/auto-tag.yml +21 -0
  35. package/.github/workflows/ci.yml +26 -128
  36. package/.github/workflows/pr-classification.yml +5 -41
  37. package/.github/workflows/publish.yml +42 -48
  38. package/.github/workflows/sonar-qube.yml +37 -4
  39. package/AGENTS.md +12 -2
  40. package/CLAUDE.md +15 -16
  41. package/GEMINI.md +14 -2
  42. package/README.md +59 -18
  43. package/SECURITY.md +36 -6
  44. package/cspell/index.yaml +2 -1
  45. package/dist/CLAUDE.md +15 -16
  46. package/dist/config-merge/index.js +15 -3
  47. package/dist/hooks/batch-guard.js +4 -4
  48. package/dist/hooks/{chunk-Y27M6A7X.js → chunk-3C4YCCEF.js} +5 -13
  49. package/dist/hooks/{chunk-D67KVGGY.js → chunk-7ZPFAVR7.js} +17 -88
  50. package/dist/hooks/{chunk-HOYJU5RM.js → chunk-E54KGFEQ.js} +11 -4
  51. package/dist/hooks/{chunk-KG6PZD72.js → chunk-IL27YTPH.js} +2 -2
  52. package/dist/hooks/{chunk-GJT4BNVE.js → chunk-KHWHKJOM.js} +3 -3
  53. package/dist/hooks/{chunk-HU66PWWL.js → chunk-M2XDCUKD.js} +1076 -509
  54. package/dist/hooks/{chunk-GLBDBJOK.js → chunk-NKOG3XAV.js} +199 -304
  55. package/dist/hooks/{chunk-ZXNE6NY3.js → chunk-O4LL4HTI.js} +2 -7
  56. package/dist/hooks/{chunk-XPLP27WA.js → chunk-OIZUHPJI.js} +5 -27
  57. package/dist/hooks/{chunk-EIFCAJRB.js → chunk-UD24EAIL.js} +2546 -1636
  58. package/dist/hooks/codex-hook-adapter.js +10 -11
  59. package/dist/hooks/duplicate-read-guard.js +5 -5
  60. package/dist/hooks/format-edited.js +15 -34
  61. package/dist/hooks/inputs.json +279 -0
  62. package/dist/hooks/investigation-guard.js +5 -5
  63. package/dist/hooks/pretool-guard.js +23 -16
  64. package/dist/hooks/session-lang.js +0 -1
  65. package/dist/hooks/stop-guard.js +188 -509
  66. package/dist/josh.js +166 -225
  67. package/dist/managed-marker/index.js +2 -1
  68. package/dist/version/index.js +412 -64
  69. package/eslint/rules/promise.js +5 -0
  70. package/package.json +13 -8
  71. package/prompts/collaboration-workflow/file-edits.md +6 -53
  72. package/prompts/collaboration-workflow/issue-citation.md +5 -5
  73. package/prompts/collaboration-workflow/operating-rules.md +4 -4
  74. package/prompts/collaboration-workflow/overview.md +13 -1
  75. package/prompts/collaboration-workflow/report-format.md +1 -1
  76. package/prompts/collaboration-workflow/residency.md +4 -4
  77. package/prompts/collaboration-workflow/rule-delivery.md +5 -36
  78. package/prompts/collaboration-workflow/shell-body.md +7 -9
  79. package/prompts/collaboration-workflow/turn-batching.md +21 -37
  80. package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
  81. package/prompts/collaboration-workflow/wip-cap.md +36 -36
  82. package/prompts/collaboration-workflow.md +5 -8
  83. package/prompts/review-rubric.md +1 -1
  84. package/prompts/review.md +3 -3
  85. package/prompts/sonar-hotspot-handling.md +12 -4
  86. package/prompts/testing-guide.md +3 -3
  87. package/scripts/adopt/adopt-toolkits.ts +2 -2
  88. package/scripts/adopt/adopt.ts +0 -2
  89. package/scripts/agent/agent-argv.ts +0 -2
  90. package/scripts/agent/agent-diagnostics.ts +1 -1
  91. package/scripts/agent/agent-role-profile.ts +0 -7
  92. package/scripts/agent/claude-agent-argv.ts +0 -2
  93. package/scripts/agent/codex-agent-argv.ts +1 -1
  94. package/scripts/agent/codex-hooks.ts +92 -0
  95. package/scripts/agent/interactive-ask.ts +1 -1
  96. package/scripts/auto-ok/auto-ok-cli.ts +15 -25
  97. package/scripts/backlog/backlog-budget.ts +0 -2
  98. package/scripts/backlog/backlog-defect-priority.ts +2 -2
  99. package/scripts/backlog/backlog-drive-cli.ts +10 -8
  100. package/scripts/backlog/backlog-drive-named-offer.ts +16 -3
  101. package/scripts/backlog/backlog-drive-named.ts +2 -1
  102. package/scripts/backlog/backlog-drive.ts +76 -16
  103. package/scripts/backlog/backlog-named.ts +0 -3
  104. package/scripts/backlog/backlog-next.ts +32 -22
  105. package/scripts/backlog/backlog-offer-cli.ts +0 -5
  106. package/scripts/backlog/backlog-plan-cli.ts +6 -8
  107. package/scripts/backlog/backlog-plan.ts +1 -1
  108. package/scripts/backlog/backlog-pool.ts +34 -17
  109. package/scripts/backlog/backlog-rank.ts +106 -0
  110. package/scripts/backlog/backlog-ready.ts +19 -3
  111. package/scripts/backlog/backlog-scope.ts +1 -3
  112. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  113. package/scripts/backlog/backlog-stalled.ts +0 -1
  114. package/scripts/backlog/backlog-waves.ts +47 -31
  115. package/scripts/behavior/behavior-cli.ts +1 -1
  116. package/scripts/build/build-codex-hooks.ts +26 -0
  117. package/scripts/build/build-hooks.ts +92 -11
  118. package/scripts/build/hook-bundle-stamp.ts +111 -0
  119. package/scripts/cases/cases-cli.ts +1 -1
  120. package/scripts/cases/cases-logic.ts +1 -1
  121. package/scripts/ci/composite-actions.ts +30 -0
  122. package/scripts/ci/pr-classification.ts +3 -3
  123. package/scripts/claude/workflow-destination.ts +10 -1
  124. package/scripts/clone/clone-fingerprint.ts +0 -1
  125. package/scripts/clone/clone-scan-cli.ts +1 -1
  126. package/scripts/clone/clone-scan.ts +1 -1
  127. package/scripts/config-merge/json-format.ts +0 -1
  128. package/scripts/config-merge/patch-yaml-list.ts +1 -1
  129. package/scripts/cost-runtime/codex-usage.ts +2 -9
  130. package/scripts/cost-runtime/cost-attribute.ts +0 -1
  131. package/scripts/cost-runtime/cost-cli.ts +0 -3
  132. package/scripts/cost-runtime/cost-corpus.ts +0 -2
  133. package/scripts/cost-runtime/cost-format.ts +0 -3
  134. package/scripts/cost-runtime/cost-pricing.ts +0 -1
  135. package/scripts/cost-runtime/cost-tokens.ts +0 -1
  136. package/scripts/delegation/delegation-cli.ts +1 -1
  137. package/scripts/delegation/delegation-policy.ts +11 -1
  138. package/scripts/delegation/duplicate-read-guard.ts +0 -8
  139. package/scripts/delegation/fanout-cli.ts +1 -1
  140. package/scripts/delegation/fanout.ts +0 -2
  141. package/scripts/delegation/investigation-reads.ts +1 -164
  142. package/scripts/discovery/repo-map-logic.ts +0 -1
  143. package/scripts/discovery/repo-party-cli.ts +1 -1
  144. package/scripts/doctor/doctor-consumer.ts +4 -6
  145. package/scripts/doctor/doctor-logic.ts +0 -4
  146. package/scripts/doctor/doctor.ts +14 -9
  147. package/scripts/document/document-byte-budget.ts +8 -9
  148. package/scripts/document/document-byte-check.ts +0 -1
  149. package/scripts/document/document-read-cli.ts +0 -2
  150. package/scripts/document/document-section-cli.ts +1 -1
  151. package/scripts/document/document-section.ts +0 -3
  152. package/scripts/document/edit-files-cli.ts +0 -1
  153. package/scripts/document/entry-read-budget.ts +13 -4
  154. package/scripts/document/lane-child-read-set.ts +1 -6
  155. package/scripts/document/read-files-cli.ts +0 -4
  156. package/scripts/document/read-set-cli.ts +0 -2
  157. package/scripts/document/read-set-point-of-use.ts +1 -0
  158. package/scripts/document/read-set-pricing.ts +0 -1
  159. package/scripts/document/read-set-trim.ts +1 -1
  160. package/scripts/epic/epic-add.ts +1 -1
  161. package/scripts/epic/epic-audit-cli.ts +1 -4
  162. package/scripts/epic/epic-audit-orphans.ts +1 -3
  163. package/scripts/epic/epic-audit-rationale.ts +0 -1
  164. package/scripts/epic/epic-audit-report.ts +0 -2
  165. package/scripts/epic/epic-audit.ts +5 -4
  166. package/scripts/epic/epic-bundle-cli.ts +1 -1
  167. package/scripts/epic/epic-bundle-evidence.ts +0 -1
  168. package/scripts/epic/epic-bundle-referenced.ts +1 -5
  169. package/scripts/epic/epic-bundle.ts +0 -4
  170. package/scripts/epic/epic-busy.ts +4 -5
  171. package/scripts/epic/epic-candidate-confirm.ts +0 -5
  172. package/scripts/epic/epic-classify.ts +2 -2
  173. package/scripts/epic/epic-cli-argv.ts +0 -1
  174. package/scripts/epic/epic-close-comment.ts +1 -1
  175. package/scripts/epic/epic-close.ts +5 -2
  176. package/scripts/epic/epic-cross-repo.ts +2 -4
  177. package/scripts/epic/epic-decision.ts +0 -1
  178. package/scripts/epic/epic-fetch.ts +2 -2
  179. package/scripts/epic/epic-index.ts +2 -3
  180. package/scripts/epic/epic-issue.ts +1 -15
  181. package/scripts/epic/epic-lane-offer.ts +20 -30
  182. package/scripts/epic/epic-nested.ts +1 -1
  183. package/scripts/epic/epic-next-read.ts +1 -3
  184. package/scripts/epic/epic-next-views.ts +0 -1
  185. package/scripts/epic/epic-next.ts +1 -10
  186. package/scripts/epic/epic-parse.ts +0 -1
  187. package/scripts/epic/epic-promote.ts +0 -9
  188. package/scripts/epic/epic-rank.ts +37 -0
  189. package/scripts/epic/epic-read.ts +1 -2
  190. package/scripts/epic/epic-reconcile.ts +1 -1
  191. package/scripts/epic/epic-reference.ts +1 -13
  192. package/scripts/epic/epic-relation-recheck.ts +1 -1
  193. package/scripts/epic/epic-relations.ts +1 -1
  194. package/scripts/epic/epic-remove.ts +1 -1
  195. package/scripts/epic/epic-report.ts +0 -1
  196. package/scripts/epic/epic-run.ts +2 -3
  197. package/scripts/epic/epic-shape.ts +1 -1
  198. package/scripts/epic/epic-solo-stale.ts +107 -0
  199. package/scripts/epic/epic-solo.ts +32 -30
  200. package/scripts/epic/epic-triage.ts +1 -1
  201. package/scripts/epic/epic-validate.ts +1 -1
  202. package/scripts/epic/epic.ts +1 -1
  203. package/scripts/exports/unused-members-cli.ts +53 -0
  204. package/scripts/{git → followup}/followup-issue-number.ts +1 -6
  205. package/scripts/{git → followup}/git-followup-cleanup.ts +2 -1
  206. package/scripts/{git → followup}/git-followup-finish.ts +1 -3
  207. package/scripts/{git → followup}/git-followup-flush.ts +2 -2
  208. package/scripts/{git → followup}/git-followup-issue-close.ts +2 -2
  209. package/scripts/{git → followup}/git-followup-label.ts +4 -4
  210. package/scripts/followup/git-followup-merged.ts +112 -0
  211. package/scripts/{git → followup}/git-followup-pending.ts +1 -1
  212. package/scripts/{git → followup}/git-followup-workflow.ts +19 -11
  213. package/scripts/{git → followup}/git-pr-followup-wrapup.ts +9 -3
  214. package/scripts/{git → followup}/git-pr-followup.ts +53 -16
  215. package/scripts/gate/core-budget.ts +3 -9
  216. package/scripts/gate/gate-plan.ts +7 -4
  217. package/scripts/gate/gate-report.ts +0 -6
  218. package/scripts/gate/gate-tree.ts +1 -1
  219. package/scripts/gate/pre-commit-type-check.ts +0 -6
  220. package/scripts/gate/project-checks.ts +7 -8
  221. package/scripts/gate/scoped-green.ts +0 -1
  222. package/scripts/gate/type-check-step.ts +1 -1
  223. package/scripts/gate/verification-gate.ts +0 -2
  224. package/scripts/gh/fix-gh-packages.ts +15 -12
  225. package/scripts/gh/gh-spawn.ts +2 -2
  226. package/scripts/{git → gh}/git-ai-review-scan.ts +1 -5
  227. package/scripts/{git → gh}/git-gh-check.ts +2 -1
  228. package/scripts/{git → gh}/git-gh-exec.ts +1 -1
  229. package/scripts/{git → gh}/git-gh-failure.ts +1 -1
  230. package/scripts/{git → gh}/git-gh-helpers.ts +0 -1
  231. package/scripts/{git → gh}/git-gh-issue-list.ts +2 -0
  232. package/scripts/{git → gh}/git-gh-issue-read.ts +1 -1
  233. package/scripts/{git → gh}/git-gh-issue-rest.ts +6 -3
  234. package/scripts/{git → gh}/git-gh-pr-read.ts +24 -0
  235. package/scripts/{git → gh}/git-gh-pr-rest.ts +5 -2
  236. package/scripts/{git → gh}/git-gh-pr-review.ts +2 -2
  237. package/scripts/{git → gh}/git-gh-pr-rollup.ts +1 -3
  238. package/scripts/{git → gh}/git-gh-pr.ts +3 -1
  239. package/scripts/{git → gh}/git-pr-ai-review.ts +2 -5
  240. package/scripts/{git → gh}/git-pr-checks-eval.ts +0 -10
  241. package/scripts/{git → gh}/git-pr-checks-parse.ts +4 -20
  242. package/scripts/{git → gh}/git-pr-checks.ts +2 -1
  243. package/scripts/{git → gh}/git-pr-coderabbit.ts +2 -5
  244. package/scripts/{git → gh}/git-pr-confirmation.ts +1 -3
  245. package/scripts/{git → gh}/git-pr-managed-config.ts +2 -3
  246. package/scripts/{git → gh}/git-pr.ts +3 -3
  247. package/scripts/git/changed-file-scope.ts +0 -1
  248. package/scripts/git/git-branch.ts +3 -6
  249. package/scripts/git/git-command.ts +1 -1
  250. package/scripts/git/git-commit.ts +1 -1
  251. package/scripts/git/git-common-directory.ts +80 -24
  252. package/scripts/git/git-error.ts +66 -9
  253. package/scripts/git/git-execa-error.ts +6 -1
  254. package/scripts/git/git-issue.ts +1 -1
  255. package/scripts/git/git-preflight.ts +24 -6
  256. package/scripts/git/git-prompt.ts +6 -31
  257. package/scripts/git/git-push-transport.ts +12 -69
  258. package/scripts/git/git-push.ts +1 -1
  259. package/scripts/git/git-spawn.ts +79 -3
  260. package/scripts/git/git-ssh-keepalive.ts +74 -0
  261. package/scripts/git/git-staging.ts +1 -0
  262. package/scripts/git/git-status.ts +1 -1
  263. package/scripts/git/git-workflow.ts +1 -1
  264. package/scripts/git/gone-branch.ts +4 -2
  265. package/scripts/git/listing-cutoff.ts +1 -1
  266. package/scripts/git/main-merge.ts +1 -1
  267. package/scripts/git/main-sync.ts +19 -1
  268. package/scripts/git/parse-json-array.ts +0 -3
  269. package/scripts/git/repository-lock.ts +16 -16
  270. package/scripts/git/{git-stash.ts → stash/git-stash.ts} +1 -1
  271. package/scripts/git/{stash-pop-args.ts → stash/stash-pop-args.ts} +1 -1
  272. package/scripts/git/{stash-pop-cli.ts → stash/stash-pop-cli.ts} +0 -1
  273. package/scripts/git/{stash-sweep-lock.ts → stash/stash-sweep-lock.ts} +3 -3
  274. package/scripts/hooks/codex-hook-adapter.ts +0 -1
  275. package/scripts/hooks/edited-cspell.ts +1 -1
  276. package/scripts/hooks/format-edited-cli.ts +15 -0
  277. package/scripts/hooks/format-edited-file.ts +4 -9
  278. package/scripts/hooks/pre-push-unit.ts +0 -4
  279. package/scripts/hooks/pretool-guard-cli.ts +21 -0
  280. package/scripts/hooks/pretool-guard.ts +36 -19
  281. package/scripts/hooks/reserved-run.ts +1 -1
  282. package/scripts/hooks/step-zero-notice.ts +76 -0
  283. package/scripts/hooks/stop-guard.ts +47 -21
  284. package/scripts/init/claude-plugin-config.ts +4 -5
  285. package/scripts/init/hook-command-rewrite.ts +16 -8
  286. package/scripts/init/hook-launch.ts +41 -15
  287. package/scripts/init/init-basic.ts +2 -5
  288. package/scripts/init/init-logic.ts +7 -0
  289. package/scripts/init/init.ts +0 -3
  290. package/scripts/init/project-profile.ts +6 -11
  291. package/scripts/init/start-exec.ts +8 -2
  292. package/scripts/init/start-plan.ts +18 -5
  293. package/scripts/init/start-setup-pr.ts +52 -25
  294. package/scripts/init/start-steps.ts +42 -9
  295. package/scripts/init/start.ts +40 -10
  296. package/scripts/issue/behavior-change-lint.ts +0 -1
  297. package/scripts/issue/defect-rate-cli.ts +3 -3
  298. package/scripts/issue/defect-rate.ts +1 -2
  299. package/scripts/{git → issue}/git-next-issues.ts +37 -8
  300. package/scripts/issue/issue-backlinks-cli.ts +1 -5
  301. package/scripts/issue/issue-cite-cli.ts +2 -6
  302. package/scripts/issue/issue-cite.ts +19 -3
  303. package/scripts/issue/issue-classification.ts +1 -9
  304. package/scripts/issue/issue-closing-pr.ts +4 -4
  305. package/scripts/issue/issue-comment-cli.ts +2 -2
  306. package/scripts/issue/issue-file-cli.ts +5 -5
  307. package/scripts/issue/issue-file.ts +2 -2
  308. package/scripts/issue/issue-fold-cli.ts +1 -1
  309. package/scripts/issue/issue-fold-existing-cli.ts +1 -1
  310. package/scripts/{git → issue}/issue-labels.ts +20 -0
  311. package/scripts/issue/issue-lint-cli.ts +1 -1
  312. package/scripts/issue/issue-merged.ts +2 -2
  313. package/scripts/issue/issue-rank.ts +89 -0
  314. package/scripts/issue/issue-read-cli.ts +8 -47
  315. package/scripts/issue/issue-read.ts +0 -2
  316. package/scripts/issue/issue-report-failures.ts +80 -0
  317. package/scripts/issue/issue-scout-cli.ts +1 -7
  318. package/scripts/issue/issue-scout-closed.ts +2 -2
  319. package/scripts/issue/issue-scout.ts +0 -2
  320. package/scripts/issue/issue-state-cli.ts +5 -46
  321. package/scripts/issue/issue-state.ts +1 -5
  322. package/scripts/issue/markdown-section.ts +1 -1
  323. package/scripts/issue/measure-rerun-cli.ts +1 -1
  324. package/scripts/issue/reproduction-measure.ts +1 -1
  325. package/scripts/josh/cli-body.ts +1 -1
  326. package/scripts/josh/file-map-stamp.ts +0 -3
  327. package/scripts/josh/josh-command-map.ts +2 -102
  328. package/scripts/josh/josh-commands-ai.ts +9 -102
  329. package/scripts/josh/josh-commands-backlog.ts +5 -1
  330. package/scripts/josh/josh-commands-development.ts +10 -3
  331. package/scripts/josh/josh-commands-issue.ts +2 -2
  332. package/scripts/josh/josh-commands-maintenance.ts +7 -0
  333. package/scripts/josh/josh-commands-project.ts +7 -1
  334. package/scripts/josh/josh-commands-run.ts +114 -0
  335. package/scripts/josh/josh-commands-versioning.ts +8 -0
  336. package/scripts/josh/josh-commands-workflow.ts +2 -2
  337. package/scripts/josh/path-decision.ts +0 -3
  338. package/scripts/josh/process-owner.ts +16 -0
  339. package/scripts/josh/session-language-cli.ts +0 -1
  340. package/scripts/lane/lane-await.ts +3 -6
  341. package/scripts/lane/lane-capacity.ts +6 -3
  342. package/scripts/lane/lane-child-invocation.ts +20 -7
  343. package/scripts/lane/lane-child-marker.ts +16 -2
  344. package/scripts/lane/lane-cli.ts +33 -18
  345. package/scripts/lane/lane-close.ts +1 -3
  346. package/scripts/lane/lane-dispatch.ts +3 -4
  347. package/scripts/lane/lane-environment.ts +0 -9
  348. package/scripts/lane/lane-handoff.ts +38 -0
  349. package/scripts/lane/lane-launch-cli.ts +21 -11
  350. package/scripts/lane/lane-open.ts +0 -3
  351. package/scripts/lane/lane-output.ts +0 -1
  352. package/scripts/lane/lane-registry.ts +9 -0
  353. package/scripts/lane/lane-report.ts +10 -4
  354. package/scripts/lane/openai-lane-ship.ts +2 -0
  355. package/scripts/lane/openai-lane-supervisor-decision.ts +2 -1
  356. package/scripts/lane/openai-lane-supervisor.ts +6 -4
  357. package/scripts/lane/openai-review-broker.ts +3 -0
  358. package/scripts/lib/bounded-pool.ts +1 -0
  359. package/scripts/lib/json-value.ts +5 -4
  360. package/scripts/lib/poll.ts +3 -1
  361. package/scripts/lib/timed-fetch.ts +15 -0
  362. package/scripts/lib/timeouts.ts +13 -0
  363. package/scripts/{yaml → lib}/yaml-document.ts +3 -1
  364. package/scripts/{bytes → lines}/bytes-command.ts +0 -2
  365. package/scripts/lines/lines-command.ts +0 -1
  366. package/scripts/lint/lint-related-scope.ts +0 -1
  367. package/scripts/lint/lint-related.ts +1 -1
  368. package/scripts/{git → notify}/telegram-notify.ts +2 -2
  369. package/scripts/{git → notify}/telegram-test.ts +4 -4
  370. package/scripts/observations/observation-ledger-home.ts +0 -1
  371. package/scripts/observations/observation-ledger-line.ts +0 -1
  372. package/scripts/observations/observation-ledger-migrate.ts +1 -1
  373. package/scripts/observations/observations-flush-landing.ts +3 -3
  374. package/scripts/observations/observations-flush.ts +2 -2
  375. package/scripts/overrides/lockfile-overrides.ts +1 -1
  376. package/scripts/overrides/overrides-logic.ts +1 -1
  377. package/scripts/package/package-scout-cli.ts +2 -7
  378. package/scripts/package/package-scout.ts +0 -2
  379. package/scripts/propagate/propagate-git-failure.ts +0 -1
  380. package/scripts/propagate/propagate-git.ts +0 -2
  381. package/scripts/propagate/propagate-plan.ts +185 -0
  382. package/scripts/propagate/propagate-publish.ts +2 -3
  383. package/scripts/propagate/propagate-run.ts +5 -3
  384. package/scripts/propagate/propagate-steps.ts +19 -6
  385. package/scripts/propagate/propagate-targets.ts +13 -6
  386. package/scripts/propagate/propagate.ts +60 -46
  387. package/scripts/refactor/import-graph.ts +0 -1
  388. package/scripts/refactor/refactor-lint.ts +0 -2
  389. package/scripts/refactor/refactor-scan.ts +0 -1
  390. package/scripts/refactor/refactor-targets.ts +0 -1
  391. package/scripts/registry-migration/migrate-logic.ts +1 -2
  392. package/scripts/registry-migration/migrate.ts +19 -9
  393. package/scripts/release/github-release-cli.ts +8 -6
  394. package/scripts/release/github-release-environment.ts +46 -0
  395. package/scripts/release/github-release.ts +151 -94
  396. package/scripts/release/release-history.ts +0 -1
  397. package/scripts/release/release-plan.ts +0 -1
  398. package/scripts/release/release-publish.ts +2 -3
  399. package/scripts/release/release-scope-cli.ts +1 -2
  400. package/scripts/release/release-tag.ts +2 -4
  401. package/scripts/repo/auto-merge-setting-logic.ts +1 -4
  402. package/scripts/repo/repo-setting.ts +1 -1
  403. package/scripts/repo/repository-labels.ts +3 -3
  404. package/scripts/repo/required-checks-logic.ts +206 -0
  405. package/scripts/repo/required-checks-report.ts +71 -0
  406. package/scripts/repo/ruleset-check-cli.ts +53 -0
  407. package/scripts/repo/ruleset-check.ts +147 -0
  408. package/scripts/report/report-lint-cli.ts +0 -4
  409. package/scripts/review/disposition-cli.ts +1 -1
  410. package/scripts/review/live-evidence.ts +1 -1
  411. package/scripts/review/review-attest-cli.ts +0 -6
  412. package/scripts/review/review-attest.ts +48 -16
  413. package/scripts/review/review-brief-cli.ts +72 -8
  414. package/scripts/review/review-brief.ts +67 -9
  415. package/scripts/review/review-diff-parts.ts +89 -0
  416. package/scripts/review/review-diff-write.ts +97 -0
  417. package/scripts/review/review-finding-ledger.ts +0 -1
  418. package/scripts/review/review-record-cli.ts +1 -1
  419. package/scripts/review/review-record.ts +0 -1
  420. package/scripts/review/review-round2-cli.ts +1 -1
  421. package/scripts/review/review-stamps.ts +6 -2
  422. package/scripts/review/review-tree.ts +14 -2
  423. package/scripts/rules/decision-oracle-stage.ts +18 -0
  424. package/scripts/rules/decision-oracle.ts +10 -7
  425. package/scripts/rules/delivered-rules.ts +9 -1
  426. package/scripts/rules/destructive-command.ts +119 -0
  427. package/scripts/rules/direct-filing.ts +1 -1
  428. package/scripts/rules/file-body.ts +0 -3
  429. package/scripts/rules/filing-cap.ts +0 -2
  430. package/scripts/rules/gh-api.ts +7 -2
  431. package/scripts/rules/git-argv.ts +18 -7
  432. package/scripts/rules/git-force.ts +0 -2
  433. package/scripts/rules/implementation-cut.ts +1 -2
  434. package/scripts/rules/index-guard.ts +71 -0
  435. package/scripts/rules/issue-citation.ts +50 -25
  436. package/scripts/rules/issue-fold.ts +1 -2
  437. package/scripts/rules/lane-background.ts +15 -7
  438. package/scripts/rules/lane-carry-conflict.ts +1 -2
  439. package/scripts/rules/lane-park.ts +14 -8
  440. package/scripts/rules/last-prompt.ts +1 -0
  441. package/scripts/rules/oracle-consulted.ts +1 -1
  442. package/scripts/rules/oracle-firing.ts +1 -0
  443. package/scripts/rules/oracle-list-cli.ts +0 -3
  444. package/scripts/rules/permission-guards.ts +14 -0
  445. package/scripts/rules/pre-gate-cut.ts +2 -2
  446. package/scripts/rules/protected-files.ts +74 -0
  447. package/scripts/rules/reply-language.ts +1 -1
  448. package/scripts/rules/rule-body-guard.ts +0 -3
  449. package/scripts/rules/rule-value-cli.ts +0 -1
  450. package/scripts/rules/shell-body-trigger.ts +0 -1
  451. package/scripts/rules/shell-segments.ts +0 -1
  452. package/scripts/rules/stop-rules.ts +13 -5
  453. package/scripts/rules/tail-commands.ts +1 -1
  454. package/scripts/rules/test-declared-commit.ts +1 -1
  455. package/scripts/rules/third-party-write.ts +0 -1
  456. package/scripts/rules/worktree-guard.ts +26 -9
  457. package/scripts/run/run-carry-cli.ts +0 -2
  458. package/scripts/run/run-carry-stash.ts +2 -2
  459. package/scripts/run/run-carry-streak.ts +1 -1
  460. package/scripts/run/run-cut-cli.ts +0 -7
  461. package/scripts/run/run-cut.ts +8 -54
  462. package/scripts/run/run-ending-cli.ts +1 -1
  463. package/scripts/run/run-ending.ts +1 -4
  464. package/scripts/run/run-entry-cli.ts +101 -24
  465. package/scripts/run/run-entry.ts +0 -1
  466. package/scripts/run/run-event-cli.ts +0 -2
  467. package/scripts/run/run-event-follow.ts +1 -0
  468. package/scripts/run/run-event-render.ts +1 -1
  469. package/scripts/run/run-event-scope.ts +53 -4
  470. package/scripts/run/run-event-stream.ts +0 -3
  471. package/scripts/run/run-event-watch.ts +1 -0
  472. package/scripts/run/run-halfrun-resume.ts +20 -7
  473. package/scripts/run/run-headless.ts +24 -8
  474. package/scripts/run/run-hold-cli.ts +33 -10
  475. package/scripts/run/run-hold.ts +25 -5
  476. package/scripts/run/run-issue-number.ts +1 -1
  477. package/scripts/run/run-liveness-cli.ts +1 -1
  478. package/scripts/run/run-liveness.ts +2 -3
  479. package/scripts/run/run-merge-cli.ts +5 -13
  480. package/scripts/run/run-merge-steps.ts +5 -5
  481. package/scripts/run/run-merge.ts +1 -1
  482. package/scripts/run/run-next-cli.ts +1 -1
  483. package/scripts/run/run-parent-cut-hook.ts +143 -0
  484. package/scripts/run/run-preflight.ts +2 -9
  485. package/scripts/run/run-prep-cli.ts +2 -2
  486. package/scripts/run/run-prep-locate.ts +1 -1
  487. package/scripts/run/run-progress-cli.ts +4 -7
  488. package/scripts/run/run-progress-clock.ts +6 -34
  489. package/scripts/run/run-progress-config.ts +0 -4
  490. package/scripts/run/run-progress-read.ts +3 -3
  491. package/scripts/run/run-progress.ts +22 -10
  492. package/scripts/run/run-prrun-resume.ts +117 -0
  493. package/scripts/run/run-report-cli.ts +1 -2
  494. package/scripts/run/run-report.ts +0 -1
  495. package/scripts/run/run-retrospective.ts +1 -1
  496. package/scripts/run/run-review-cli.ts +0 -3
  497. package/scripts/run/run-review-steps.ts +2 -0
  498. package/scripts/run/run-review.ts +0 -2
  499. package/scripts/run/run-ship-cli.ts +27 -77
  500. package/scripts/run/run-ship-detach.ts +1 -0
  501. package/scripts/run/run-ship-next.ts +106 -0
  502. package/scripts/run/run-ship-preflight.ts +87 -0
  503. package/scripts/run/run-ship-return.ts +8 -3
  504. package/scripts/run/run-ship-review-steps.ts +23 -30
  505. package/scripts/run/run-ship-review.ts +20 -0
  506. package/scripts/run/run-ship-scoped.ts +45 -0
  507. package/scripts/run/run-ship-stage.ts +5 -1
  508. package/scripts/run/run-ship-steps.ts +105 -0
  509. package/scripts/run/run-ship.ts +2 -1
  510. package/scripts/run/run-stage-read.ts +62 -0
  511. package/scripts/run/run-stage.ts +160 -0
  512. package/scripts/run/run-status-cli.ts +1 -1
  513. package/scripts/run/run-step-cli.ts +21 -14
  514. package/scripts/run/run-step.ts +1 -1
  515. package/scripts/run/run-stop-notify.ts +1 -3
  516. package/scripts/run/run-stranded-cli.ts +0 -4
  517. package/scripts/run/run-stranded-detect.ts +2 -2
  518. package/scripts/run/run-tail-cli.ts +17 -11
  519. package/scripts/run/run-tail.ts +2 -0
  520. package/scripts/run/run-tidy-lanes.ts +2 -1
  521. package/scripts/run/run-tidy-stashes.ts +12 -12
  522. package/scripts/run/run-wake-cli.ts +4 -5
  523. package/scripts/run/run-wake-describe.ts +19 -1
  524. package/scripts/run/run-wake-loop.ts +2 -2
  525. package/scripts/run/run-wake.ts +2 -4
  526. package/scripts/run/run-watcher-guard-cli.ts +0 -4
  527. package/scripts/run/run-watcher-guard.ts +9 -7
  528. package/scripts/run/run-watcher-hook.ts +1 -1
  529. package/scripts/safe-chain/ci-installer-pin.ts +11 -5
  530. package/scripts/safe-chain/project-config.ts +1 -1
  531. package/scripts/security/security-audit-provision-logic.ts +0 -1
  532. package/scripts/security/security-audit-provision.ts +3 -2
  533. package/scripts/security/security-audit.ts +0 -1
  534. package/scripts/security/security-updates-logic.ts +0 -1
  535. package/scripts/sonar/sonar-hotspots-cli.ts +10 -52
  536. package/scripts/sonar/sonar-hotspots.ts +0 -1
  537. package/scripts/sonar/sonar-new-code-cli.ts +150 -0
  538. package/scripts/sonar/sonar-new-code.ts +79 -0
  539. package/scripts/sonar/sonar-project.ts +97 -0
  540. package/scripts/split/split-assess-cli.ts +1 -1
  541. package/scripts/split/split-assess.ts +2 -5
  542. package/scripts/sync/managed-config-scope-cli.ts +1 -1
  543. package/scripts/sync/skill-migration.ts +0 -1
  544. package/scripts/sync/sync-hook-safety.ts +2 -3
  545. package/scripts/sync/sync.ts +1 -8
  546. package/scripts/sync/template-source-logic.ts +0 -1
  547. package/scripts/sync/workflow-pin-logic.ts +16 -28
  548. package/scripts/test/test-declared-logic.ts +0 -1
  549. package/scripts/test/test-declared.ts +0 -2
  550. package/scripts/test/test-red-commit.ts +2 -2
  551. package/scripts/test/test-red.ts +14 -7
  552. package/scripts/test/test-type-logic.ts +1 -1
  553. package/scripts/test/unit-worker-share.ts +7 -16
  554. package/scripts/time-runtime/time-batch-guard.ts +1 -2
  555. package/scripts/time-runtime/time-bundle-call.ts +2 -3
  556. package/scripts/time-runtime/time-bundles.ts +0 -4
  557. package/scripts/time-runtime/time-density-hook.ts +0 -2
  558. package/scripts/time-runtime/time-followup-stage.ts +1 -1
  559. package/scripts/time-runtime/time-format.ts +0 -73
  560. package/scripts/time-runtime/time-round-trips.ts +0 -4
  561. package/scripts/time-runtime/time-single-check.ts +1 -1
  562. package/scripts/time-runtime/time-spans.ts +1 -1
  563. package/scripts/time-runtime/time-transcript-line.ts +1 -2
  564. package/scripts/ui/ui-routes-cli.ts +0 -5
  565. package/scripts/ui/ui-routes.ts +0 -5
  566. package/scripts/version/effective-upstream.ts +2 -2
  567. package/scripts/version/latest-corepack.ts +7 -9
  568. package/scripts/version/latest-scope-cli.ts +0 -2
  569. package/scripts/version/latest-update.ts +0 -2
  570. package/scripts/version/npm-registry.ts +97 -0
  571. package/scripts/version/publishable-range-check.ts +1 -1
  572. package/scripts/version/release-hold.ts +0 -4
  573. package/scripts/version/running-binary.ts +2 -2
  574. package/scripts/version/version-check-logic.ts +0 -2
  575. package/scripts/version/version-commands.ts +5 -4
  576. package/scripts/version/version-remote.ts +28 -14
  577. package/scripts/version/version-targets.ts +4 -4
  578. package/templates/sonar-project.properties +28 -1
  579. package/templates/workflows/dependabot-auto-merge.yml +5 -3
  580. package/templates/workflows/github-release.yml +81 -0
  581. package/prompts/collaboration-workflow/cross-repo-epic.md +0 -5
  582. package/prompts/collaboration-workflow/epic-audit.md +0 -5
  583. package/prompts/collaboration-workflow/epic-bundle.md +0 -5
  584. package/scripts/init/plugin-install-hint.ts +0 -47
  585. package/scripts/version/parse-json.ts +0 -13
  586. /package/scripts/{git → followup}/git-followup-stages.ts +0 -0
  587. /package/scripts/{git → gh}/git-gh-api-path.ts +0 -0
  588. /package/scripts/{git → gh}/git-gh-command.ts +0 -0
  589. /package/scripts/{git → gh}/git-gh-issue-write.ts +0 -0
  590. /package/scripts/{git → gh}/git-gh-issue.ts +0 -0
  591. /package/scripts/{git → gh}/git-gh-pr-auto-merge.ts +0 -0
  592. /package/scripts/{git → gh}/git-gh-pr-snapshot.ts +0 -0
  593. /package/scripts/{git → gh}/git-gh-reachability.ts +0 -0
  594. /package/scripts/{git → gh}/git-gh-repo.ts +0 -0
  595. /package/scripts/{git → gh}/git-gh-rest-state.ts +0 -0
  596. /package/scripts/{git → gh}/git-pr-checks-watch.ts +0 -0
  597. /package/scripts/{git → gh}/git-pr-error.ts +0 -0
  598. /package/scripts/{git → gh}/git-pr-messages.ts +0 -0
  599. /package/scripts/{git → gh}/github-issue-url.ts +0 -0
  600. /package/scripts/git/{stash-orphans.ts → stash/stash-orphans.ts} +0 -0
  601. /package/scripts/{git → issue}/issue-reference.ts +0 -0
  602. /package/scripts/{git → lib}/animation-helpers.ts +0 -0
  603. /package/scripts/{git → lib}/git-animation.ts +0 -0
  604. /package/scripts/{git → notify}/git-notify.ts +0 -0
  605. /package/scripts/{git → notify}/telegram-test-logic.ts +0 -0
@@ -1,4 +1,10 @@
1
1
  import { run_issue_number } from '#scripts/run/run-issue-number'
2
+ import { run_ship_next, type ShipResume } from '#scripts/run/run-ship-next'
3
+ import type { Stage } from '#scripts/run/run-ship-stage'
4
+
5
+ interface ShipStop extends ShipResume {
6
+ stage: Stage
7
+ }
2
8
 
3
9
  const CHILD_INVOCATION = 'fullrun'
4
10
  const RESUME_GUIDE = '.claude/skills/workflow-commands/pre-gate-cut.md'
@@ -52,17 +58,24 @@ function outage_resume_invocation(issue: string): string {
52
58
 
53
59
  // The prompt a lane child is relaunched with when its detached ship supervisor stopped at a failed
54
60
  // stage (joshuafolkken/kit#2428) — a red gate, a High/Medium review, a failed push or red CI. The agent
55
- // ended at the hand-off, so this is how control comes back to one: it reads the stopped report through
56
- // the command `run:step` names, fixes it and hands the region back. It ends with `child_invocation` for
57
- // the reason `resume_invocation` does — the parent's poll keeps matching the relaunched process.
58
- function ship_stop_invocation(issue: string): string {
59
- const preamble = `Resuming the lane child for issue #${issue} after its detached ship supervisor stopped at a failed stage — do not re-read the workflow-commands entry documents (SKILL.md, fullrun.md). Run \`pnpm josh run:step ${issue}\` and read the stopped report it names, fix what stopped the ship, then hand the region back per chain-rule.md. Do not re-plan or re-implement what is already on the branch.`
61
+ // ended at the hand-off, so this is how control comes back to one: it reads the stopped report, fixes it
62
+ // and hands the region back. It ends with `child_invocation` for the reason `resume_invocation` does —
63
+ // the parent's poll keeps matching the relaunched process.
64
+ //
65
+ // **A known stage puts the next command in the prompt** (joshuafolkken/kit#2964), so the relaunched
66
+ // session runs it rather than re-deriving it from `chain-rule.md`. An OpenAI lane's supervisor sees only
67
+ // that the ship failed, not where, so without a stage the prompt points at `run:step` as before.
68
+ function ship_stop_invocation(issue: string, stop?: ShipStop): string {
69
+ const opening = `Resuming the lane child for issue #${issue} after its detached ship supervisor stopped at a failed stage — do not re-read the workflow-commands entry documents (SKILL.md, fullrun.md).`
70
+ const next =
71
+ stop === undefined
72
+ ? `Run \`pnpm josh run:step ${issue}\` and read the stopped report it names, fix what stopped the ship, then hand the region back per chain-rule.md.`
73
+ : run_ship_next.next_step(issue, stop.stage, stop)
60
74
 
61
- return `${preamble} ${child_invocation(issue)}`
75
+ return `${opening} ${next} Do not re-plan or re-implement what is already on the branch. ${child_invocation(issue)}`
62
76
  }
63
77
 
64
78
  const lane_child_invocation = {
65
- CHILD_INVOCATION,
66
79
  child_invocation,
67
80
  outage_resume_invocation,
68
81
  process_pattern,
@@ -19,6 +19,15 @@ import { lane_paths } from './lane-paths'
19
19
  // `.claude/skills/workflow-commands/pre-gate-cut.md`.
20
20
  const KEY = 'JOSH_LANE_CHILD'
21
21
 
22
+ // **A dispatched child also asks for the five-minute prompt-cache TTL** (joshuafolkken/kit#2944). A
23
+ // headless `claude -p` session on a subscription defaults to the one-hour TTL, whose writes bill at 2x
24
+ // the input rate against 1.25x for five minutes. A replay of 1,953 lane transcripts (about 57,000
25
+ // requests) found 0.3% of request gaps over five minutes, so the shorter TTL — re-writing the prefix on
26
+ // those few gaps — still costs 12-13% less. Every launcher of a child composes its environment here,
27
+ // so this is the one place the choice is made.
28
+ const CACHE_TTL_KEY = 'CLAUDE_CODE_PROMPT_CACHE_TTL'
29
+ const CACHE_TTL = '5m'
30
+
22
31
  type MarkerSource = Readonly<Record<string, string | undefined>>
23
32
 
24
33
  // The environment fragment that marks a child dispatched for `#<issue>`. Composed from a digits-only
@@ -27,7 +36,7 @@ type MarkerSource = Readonly<Record<string, string | undefined>>
27
36
  function environment_for(issue: string): Record<string, string> {
28
37
  run_issue_number.require_issue_number(issue)
29
38
 
30
- return { [KEY]: issue }
39
+ return { [KEY]: issue, [CACHE_TTL_KEY]: CACHE_TTL }
31
40
  }
32
41
 
33
42
  // The issue a child was dispatched for, or `undefined` when the mark is absent or malformed. A blank
@@ -52,7 +61,12 @@ function is_child_of(directory: string, source: MarkerSource = process.env): boo
52
61
  return marked !== undefined && marked === lane_paths.lane_issue_of(directory, { ...source })
53
62
  }
54
63
 
55
- const lane_child_marker = { KEY, env_for: environment_for, is_child_of, marked_issue }
64
+ const lane_child_marker = {
65
+ KEY,
66
+ env_for: environment_for,
67
+ is_child_of,
68
+ marked_issue,
69
+ }
56
70
 
57
71
  export type { MarkerSource }
58
72
  export { lane_child_marker }
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { epic_busy } from '#scripts/epic/epic-busy'
4
- import { git_gh_command } from '#scripts/git/git-gh-command'
4
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
5
+ import type { OpenIssueData } from '#scripts/git/schemas'
6
+ import { issue_cite, type IssueCiter } from '#scripts/issue/issue-cite'
5
7
  import { error_text } from '#scripts/lib/error-message'
6
8
  import { run_carry } from '#scripts/run/run-carry'
7
9
  import { lane_await } from './lane-await'
@@ -150,10 +152,10 @@ function report_sweep(outcome: SweepOutcome): number {
150
152
 
151
153
  // `none` on standard output for the empty case as well, so `lane:list` keeps the contract the rest
152
154
  // of the commands follow — the prose belongs on standard error with every other explanation.
153
- function report_lanes(lanes: ReadonlyArray<LaneInfo>): number {
155
+ function report_lanes(lanes: ReadonlyArray<LaneInfo>, cite: IssueCiter): number {
154
156
  if (lanes.length === 0) console.error(lane_report.NO_LANES)
155
157
 
156
- console.info(lanes.length === 0 ? NONE_TOKEN : lane_report.describe_lanes(lanes))
158
+ console.info(lanes.length === 0 ? NONE_TOKEN : lane_report.describe_lanes(lanes, cite))
157
159
 
158
160
  return SUCCESS_EXIT_CODE
159
161
  }
@@ -192,23 +194,39 @@ const OCCUPANCY_UNREADABLE =
192
194
  // The `in-progress` issues this repository shows running, read the way `epic:next` reads lane
193
195
  // occupancy — never rebuilt (joshuafolkken/kit#2235). A read that could not see the whole listing is
194
196
  // `undefined`, so the difference is skipped rather than computed against a set known to be partial.
195
- async function to_holder_numbers(repo: string): Promise<ReadonlyArray<number> | undefined> {
197
+ async function to_holders(repo: string): Promise<ReadonlyArray<OpenIssueData> | undefined> {
196
198
  const read = await epic_busy.read_repository(repo)
197
199
  if (read.kind === 'idle') return []
198
200
 
199
- return read.kind === 'busy' ? read.issues.map((issue) => issue.number) : undefined
201
+ return read.kind === 'busy' ? read.issues : undefined
200
202
  }
201
203
 
202
- async function read_in_progress(): Promise<ReadonlyArray<number> | undefined> {
204
+ // The repository and its running issues, read once: the listing cites each lane from the titles here
205
+ // (joshuafolkken/kit#2943) and the occupancy check compares against the same set.
206
+ interface InProgress {
207
+ repo: string | undefined
208
+ holders: ReadonlyArray<OpenIssueData> | undefined
209
+ }
210
+
211
+ async function read_in_progress(): Promise<InProgress> {
203
212
  try {
204
213
  const repo = await git_gh_command.repo_get_name_with_owner()
205
214
 
206
- return repo === undefined ? undefined : await to_holder_numbers(repo)
215
+ return { repo, holders: repo === undefined ? undefined : await to_holders(repo) }
207
216
  } catch {
208
- return undefined
217
+ return { repo: undefined, holders: undefined }
209
218
  }
210
219
  }
211
220
 
221
+ function citer_of(in_progress: InProgress): IssueCiter {
222
+ const holders = in_progress.holders ?? []
223
+
224
+ return issue_cite.citer(
225
+ in_progress.repo,
226
+ new Map(holders.map((issue) => [String(issue.number), issue.title])),
227
+ )
228
+ }
229
+
212
230
  // A stranded lane's work tree is gone, so it holds no issue: it counts as no lane rather than a live
213
231
  // one, which is what turns its surviving `in-progress` label into a `stopped` anomaly.
214
232
  function live_lane_issues(lanes: ReadonlyArray<LaneInfo>): Array<number> {
@@ -217,16 +235,15 @@ function live_lane_issues(lanes: ReadonlyArray<LaneInfo>): Array<number> {
217
235
 
218
236
  // The lane/label difference, printed to standard error beside the listing. Wrapped so a GitHub read
219
237
  // that fails skips only this section — `lane:list`'s job is to list the lanes it already read.
220
- async function report_occupancy(lanes: ReadonlyArray<LaneInfo>): Promise<void> {
221
- const in_progress = await read_in_progress()
222
-
223
- if (in_progress === undefined) {
238
+ function report_occupancy(lanes: ReadonlyArray<LaneInfo>, in_progress: InProgress): void {
239
+ if (in_progress.holders === undefined) {
224
240
  console.error(OCCUPANCY_UNREADABLE)
225
241
 
226
242
  return
227
243
  }
228
244
 
229
- const report = lane_occupancy.classify(in_progress, {
245
+ const holders = in_progress.holders.map((issue) => issue.number)
246
+ const report = lane_occupancy.classify(holders, {
230
247
  kind: 'lanes',
231
248
  issues: live_lane_issues(lanes),
232
249
  })
@@ -239,9 +256,10 @@ async function list_command(rest: ReadonlyArray<string>): Promise<number> {
239
256
  if (rest.length > 0) return report_usage()
240
257
 
241
258
  const lanes = await lane_registry.list_lanes()
242
- const code = report_lanes(lanes)
259
+ const in_progress = await read_in_progress()
260
+ const code = report_lanes(lanes, citer_of(in_progress))
243
261
 
244
- await report_occupancy(lanes)
262
+ report_occupancy(lanes, in_progress)
245
263
 
246
264
  return code
247
265
  }
@@ -399,9 +417,6 @@ const lane_cli = {
399
417
  ALL_FLAG,
400
418
  NONE_TOKEN,
401
419
  USAGE,
402
- main,
403
- parse_issue,
404
- parse_lane_issue,
405
420
  refusal_message,
406
421
  run,
407
422
  }
@@ -158,6 +158,7 @@ async function close_each(issues: ReadonlyArray<string>): Promise<SweepOutcome>
158
158
  const sweep: SweepOutcome = { closed: [], failed: [] }
159
159
 
160
160
  for (const issue of issues) {
161
+ // eslint-disable-next-line no-await-in-loop -- worktree writes contend for the same registration directory
161
162
  const bucket = (await try_close(issue)) ? sweep.closed : sweep.failed
162
163
 
163
164
  bucket.push(issue)
@@ -186,10 +187,7 @@ async function close_all_lanes(): Promise<SweepOutcome> {
186
187
  const lane_close = {
187
188
  close_all_lanes,
188
189
  close_lane,
189
- lane_root_directory,
190
- lane_targets,
191
190
  prune_lanes,
192
- remove_lane,
193
191
  resolve_lane,
194
192
  }
195
193
 
@@ -1,8 +1,8 @@
1
1
  import { agent_argv, type AgentArgv, type AgentArgvResult } from '#scripts/agent/agent-argv'
2
2
  import { agent_role_profile, type AgentProfile } from '#scripts/agent/agent-role-profile'
3
- import { git_gh_command } from '#scripts/git/git-gh-command'
4
- import { IN_PROGRESS_LABEL } from '#scripts/git/issue-labels'
5
- import { telegram_notify } from '#scripts/git/telegram-notify'
3
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
4
+ import { IN_PROGRESS_LABEL } from '#scripts/issue/issue-labels'
5
+ import { telegram_notify } from '#scripts/notify/telegram-notify'
6
6
  import { detached_launch } from '#scripts/run/detached-launch'
7
7
  import { run_ending } from '#scripts/run/run-ending'
8
8
  import { run_event_stream } from '#scripts/run/run-event-stream'
@@ -444,7 +444,6 @@ async function warn_of_problem(outcome: DispatchOutcome, issue: string): Promise
444
444
  }
445
445
 
446
446
  const lane_dispatch = {
447
- CHILD_INVOCATION: lane_child_invocation.CHILD_INVOCATION,
448
447
  child_invocation,
449
448
  default_log_path,
450
449
  describe,
@@ -58,10 +58,6 @@ function is_key_line(line: string, key: string): boolean {
58
58
  return body.slice(key.length).trimStart().startsWith(ASSIGNMENT_SEPARATOR)
59
59
  }
60
60
 
61
- function is_seed_line(line: string): boolean {
62
- return is_key_line(line, PORT_SEED_KEY)
63
- }
64
-
65
61
  function is_seat_line(line: string): boolean {
66
62
  return is_key_line(line, LANE_SEAT_KEY)
67
63
  }
@@ -235,12 +231,7 @@ function with_lane_output(content: string, output: string, profile?: AgentProfil
235
231
  }
236
232
 
237
233
  const lane_environment = {
238
- LANE_OUTPUT_KEY,
239
- LANE_PROVIDER_KEY,
240
- LANE_ROLE_KEY,
241
- is_output_line,
242
234
  is_seat_line,
243
- is_seed_line,
244
235
  lane_file_content,
245
236
  read_lane_output,
246
237
  read_lane_profile,
@@ -0,0 +1,38 @@
1
+ import { git_common_directory } from '#scripts/git/git-common-directory'
2
+ import { run_ship_detach } from '#scripts/run/run-ship-detach'
3
+ import { lane_child_marker, type MarkerSource } from './lane-child-marker'
4
+
5
+ // Whether a lane has handed its post-implementation region to the detached ship (joshuafolkken/kit#2962).
6
+ //
7
+ // **A child that ran `pnpm josh ship --detach` waits on nobody when it ends.** The supervisor drives the
8
+ // gate, the review and the merge, and notifies on its own outcome, so the `Stop` hook's demand for a
9
+ // `confirmation` notify and `lane-park`'s refusal of that notify only bought a "nothing needed"
10
+ // Telegram and a wasted turn — measured on 2026-10-02 as 43 demands, 21 refusals and ~94 extra turns.
11
+ // Both hooks read this one predicate, so they cannot disagree about whether the lane was handed off.
12
+ //
13
+ // **Running, never merely recorded.** A ship that failed hands its region back to the child, whose
14
+ // record then reads `failed`; that child is waiting again and owes the notify, so only a live
15
+ // supervisor counts as a hand-off.
16
+
17
+ // A detached ship for `issue` is live in the repository `directory` belongs to — the single reading
18
+ // `lane:await`, `run:merge` and the two stop hooks share.
19
+ function is_ship_running(issue: string, directory: string): boolean {
20
+ const repository = git_common_directory.repository(directory)
21
+
22
+ return (
23
+ repository !== undefined && run_ship_detach.read_result(repository, issue)?.result === 'running'
24
+ )
25
+ }
26
+
27
+ // This session is the dispatched lane child for `directory` and its own ship is running.
28
+ function is_handed_off(directory: string, source: MarkerSource = process.env): boolean {
29
+ const issue = lane_child_marker.marked_issue(source)
30
+
31
+ if (issue === undefined || !lane_child_marker.is_child_of(directory, source)) return false
32
+
33
+ return is_ship_running(issue, directory)
34
+ }
35
+
36
+ const lane_handoff = { is_handed_off, is_ship_running }
37
+
38
+ export { lane_handoff }
@@ -2,6 +2,7 @@
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { parseArgs } from 'node:util'
4
4
  import { josh_command } from '#scripts/josh/josh-run'
5
+ import { INSTALL_TIMEOUT_MS } from '#scripts/lib/timeouts'
5
6
  import { execa } from 'execa'
6
7
 
7
8
  // `josh lane:launch <issue> [--stash <message>]` — one composite command for a `backlogrun` lane-start
@@ -57,6 +58,7 @@ function read_context(argv: ReadonlyArray<string>): LaunchContext | undefined {
57
58
  async function install(directory: string): Promise<boolean> {
58
59
  const result = await execa(PNPM, ['--dir', directory, 'install', '--frozen-lockfile'], {
59
60
  reject: false,
61
+ timeout: INSTALL_TIMEOUT_MS,
60
62
  })
61
63
 
62
64
  if ((result.exitCode ?? FAILURE_EXIT_CODE) === SUCCESS_EXIT_CODE) return true
@@ -82,29 +84,37 @@ async function prepare(stash: string | undefined, directory: string): Promise<bo
82
84
  return await install(directory)
83
85
  }
84
86
 
85
- // The launch chain in-process: the dispatched child's pid, or `undefined` when a step refused. The CLI
86
- // prints the pid; `backlog:drive` launches through this same chain (joshuafolkken/kit#2508).
87
- async function launch_lane(context: LaunchContext): Promise<string | undefined> {
87
+ // Where the chain ended. `unopened` is a `lane:open` refusal — no lane was taken; `failed` is a step
88
+ // after it, which leaves the opened lane holding a seat, so a caller cannot read the two alike
89
+ // (joshuafolkken/kit#3027).
90
+ type LaunchOutcome = { kind: 'launched'; pid: string } | { kind: 'unopened' } | { kind: 'failed' }
91
+
92
+ const UNOPENED: LaunchOutcome = { kind: 'unopened' }
93
+ const FAILED: LaunchOutcome = { kind: 'failed' }
94
+
95
+ // The launch chain in-process. The CLI prints the pid; `backlog:drive` launches through this same
96
+ // chain (joshuafolkken/kit#2508).
97
+ async function launch_lane(context: LaunchContext): Promise<LaunchOutcome> {
88
98
  const opened = await josh_command.josh_run(['lane:open', context.issue], should_forward_stderr)
89
99
 
90
- if (opened.code !== SUCCESS_EXIT_CODE) return undefined
100
+ if (opened.code !== SUCCESS_EXIT_CODE) return UNOPENED
91
101
 
92
- if (!(await prepare(context.stash, opened.out))) return undefined
102
+ if (!(await prepare(context.stash, opened.out))) return FAILED
93
103
 
94
104
  const dispatched = await josh_command.josh_run(
95
105
  ['lane:dispatch', context.issue],
96
106
  should_forward_stderr,
97
107
  )
98
108
 
99
- return dispatched.code === SUCCESS_EXIT_CODE ? dispatched.out : undefined
109
+ return dispatched.code === SUCCESS_EXIT_CODE ? { kind: 'launched', pid: dispatched.out } : FAILED
100
110
  }
101
111
 
102
112
  async function launch(context: LaunchContext): Promise<number> {
103
- const pid = await launch_lane(context)
113
+ const outcome = await launch_lane(context)
104
114
 
105
- if (pid === undefined) return FAILURE_EXIT_CODE
115
+ if (outcome.kind !== 'launched') return FAILURE_EXIT_CODE
106
116
 
107
- console.info(pid)
117
+ console.info(outcome.pid)
108
118
 
109
119
  return SUCCESS_EXIT_CODE
110
120
  }
@@ -125,9 +135,9 @@ async function main(argv: ReadonlyArray<string>): Promise<void> {
125
135
  process.exitCode = await run(argv)
126
136
  }
127
137
 
128
- const lane_launch_cli = { USAGE, install, launch, launch_lane, prepare, read_context, run }
138
+ const lane_launch_cli = { launch_lane, read_context, run }
129
139
 
130
140
  if (process.argv[1] === fileURLToPath(import.meta.url)) await main(process.argv.slice(ARGV_OFFSET))
131
141
 
132
- export type { LaunchContext }
142
+ export type { LaunchContext, LaunchOutcome }
133
143
  export { lane_launch_cli }
@@ -279,10 +279,7 @@ async function open_lane(issue: string): Promise<OpenOutcome> {
279
279
  }
280
280
 
281
281
  const lane_open = {
282
- build_plan,
283
- guard_unreadable,
284
282
  open_lane,
285
- read_root_environment,
286
283
  }
287
284
 
288
285
  export type { OpenOutcome }
@@ -115,7 +115,6 @@ function describe_refusal(refusal: Refusal, issue: string): string {
115
115
  const lane_output = {
116
116
  NO_OUTPUT,
117
117
  describe_refusal,
118
- invalid_reason,
119
118
  read_output,
120
119
  record_output,
121
120
  }
@@ -217,6 +217,14 @@ async function list_lanes(): Promise<Array<LaneInfo>> {
217
217
  return lanes.toSorted((left, right) => Number(left.issue) - Number(right.issue))
218
218
  }
219
219
 
220
+ // The lanes a parent is still waiting on. A stranded lane has no child working in it, so it is not
221
+ // in-flight — the one definition the watcher guard and the headless stop rule both read.
222
+ async function has_lanes_in_flight(): Promise<boolean> {
223
+ const lanes = await list_lanes()
224
+
225
+ return lanes.some((lane) => !lane.is_stranded)
226
+ }
227
+
220
228
  /** The seats live lanes hold — what a new lane's seat has to avoid. */
221
229
  function used_seats(lanes: ReadonlyArray<LaneInfo>): Array<number> {
222
230
  return lanes.map((lane) => lane.seat).filter((seat): seat is number => seat !== undefined)
@@ -246,6 +254,7 @@ async function find_open_lane(issue: string): Promise<LaneInfo | undefined> {
246
254
  const lane_registry = {
247
255
  find_lane,
248
256
  find_open_lane,
257
+ has_lanes_in_flight,
249
258
  list_lanes,
250
259
  main_repository_root,
251
260
  parse_block,
@@ -1,4 +1,5 @@
1
1
  import { agent_role_profile } from '#scripts/agent/agent-role-profile'
2
+ import { issue_cite, type IssueCiter } from '#scripts/issue/issue-cite'
2
3
  import { run_liveness } from '#scripts/run/run-liveness'
3
4
  import type { LaneInfo } from './lane-registry'
4
5
 
@@ -48,9 +49,14 @@ function event_column(lane: LaneInfo): string {
48
49
  return state ?? 'agent -'
49
50
  }
50
51
 
51
- function describe_lane(lane: LaneInfo): string {
52
+ // The plain `#N`, for a caller that could not read the repository the issue would link into.
53
+ const PLAIN_CITE: IssueCiter = issue_cite.citer(undefined, new Map())
54
+
55
+ // The issue leads as a citation (joshuafolkken/kit#2943): a lane status report copies this row, and a
56
+ // bare `#N` copied into it is what the Stop guard sends back.
57
+ function describe_lane(lane: LaneInfo, cite: IssueCiter = PLAIN_CITE): string {
52
58
  return [
53
- `#${lane.issue}`,
59
+ cite(lane.issue),
54
60
  `seat ${value_or_unknown(lane.seat)}`,
55
61
  `dev ${value_or_unknown(lane.development_port)}`,
56
62
  `preview ${value_or_unknown(lane.preview_port)}`,
@@ -63,10 +69,10 @@ function describe_lane(lane: LaneInfo): string {
63
69
  ].join(COLUMN_SEPARATOR)
64
70
  }
65
71
 
66
- function describe_lanes(lanes: ReadonlyArray<LaneInfo>): string {
72
+ function describe_lanes(lanes: ReadonlyArray<LaneInfo>, cite: IssueCiter = PLAIN_CITE): string {
67
73
  if (lanes.length === 0) return NO_LANES
68
74
 
69
- return lanes.map((lane) => describe_lane(lane)).join('\n')
75
+ return lanes.map((lane) => describe_lane(lane, cite)).join('\n')
70
76
  }
71
77
 
72
78
  function describe_ports(lane: LaneInfo): string {
@@ -17,7 +17,9 @@ async function wait_for_ship(issue: string, prior_launch: string | undefined): P
17
17
  if (read === undefined || read.launch_id === prior_launch) return 'none'
18
18
 
19
19
  while (read.result === 'running') {
20
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
20
21
  await sleep(POLL_MS)
22
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
21
23
  read = await current(issue)
22
24
  if (read === undefined) return 'abnormal'
23
25
  }
@@ -48,6 +48,7 @@ async function is_approved(lane_directory: string, nonce: string): Promise<boole
48
48
  const decision = read(lane_directory, nonce)?.decision
49
49
  if (decision !== undefined) return decision === 'approved'
50
50
 
51
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
51
52
  await new Promise((resolve) => setTimeout(resolve, APPROVAL_POLL_MS))
52
53
  }
53
54
 
@@ -58,6 +59,6 @@ function remove(lane_directory: string, nonce: string): void {
58
59
  stamp_file.remove_stamp(target(lane_directory, nonce))
59
60
  }
60
61
 
61
- const openai_lane_supervisor_decision = { approve, cancel, is_approved, remove, target }
62
+ const openai_lane_supervisor_decision = { approve, cancel, is_approved, remove }
62
63
 
63
64
  export { openai_lane_supervisor_decision }
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url'
4
4
  import { agent_argv } from '#scripts/agent/agent-argv'
5
5
  import type { AgentProfile } from '#scripts/agent/agent-role-profile'
6
6
  import { process_identity } from '#scripts/josh/process-identity'
7
+ import { process_owner_schema } from '#scripts/josh/process-owner'
7
8
  import { stamp_file } from '#scripts/josh/stamp-file'
8
9
  import { detached_launch, type LaunchArgv } from '#scripts/run/detached-launch'
9
10
  import { run_cut, type RunCut } from '#scripts/run/run-cut'
@@ -22,11 +23,12 @@ const CHILD_POLL_MS = 250
22
23
  const CLAIM_POLL_MS = 50
23
24
  const CLAIM_POLL_LIMIT = 40
24
25
  const SUPERVISOR_SCRIPT = fileURLToPath(new URL('openai-lane-supervisor-cli.ts', import.meta.url))
25
- const owner_schema = z.object({
26
+ // The shared owner pair, with the pid narrowed: this record is written only by a live supervisor, so a
27
+ // pid that is not a real one marks it as corrupt rather than as a dead owner.
28
+ const owner_schema = process_owner_schema.extend({
26
29
  issue: z.string(),
27
30
  nonce: z.string(),
28
31
  pid: z.number().int().positive(),
29
- process_start: z.string().optional(),
30
32
  })
31
33
  const state_schema = z.object({
32
34
  owner_nonce: z.string(),
@@ -81,6 +83,7 @@ async function wait_for_active(lane_directory: string): Promise<SupervisorOwner
81
83
  for (let attempt = 0; attempt < CLAIM_POLL_LIMIT; attempt += 1) {
82
84
  const owner = active(lane_directory)
83
85
  if (owner !== undefined) return owner
86
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
84
87
  await new Promise((resolve) => setTimeout(resolve, CLAIM_POLL_MS))
85
88
  }
86
89
 
@@ -298,6 +301,7 @@ async function wait_for_inherited_child(
298
301
  child_process_start: string | undefined,
299
302
  ): Promise<void> {
300
303
  while (process_identity.is_same_process(child_pid, child_process_start) !== false) {
304
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
301
305
  await new Promise((resolve) => setTimeout(resolve, CHILD_POLL_MS))
302
306
  }
303
307
  }
@@ -350,10 +354,8 @@ const openai_lane_supervisor = {
350
354
  approve: openai_lane_supervisor_decision.approve,
351
355
  cancel: openai_lane_supervisor_decision.cancel,
352
356
  claim,
353
- decision_path: openai_lane_supervisor_decision.target,
354
357
  marker_path,
355
358
  new_nonce: randomUUID,
356
- read,
357
359
  release,
358
360
  state_path,
359
361
  supervise,
@@ -138,9 +138,11 @@ async function serve(
138
138
 
139
139
  if (received?.session === session && received.nonce !== last_nonce) {
140
140
  last_nonce = received.nonce
141
+ // eslint-disable-next-line no-await-in-loop -- requests are served one at a time, in arrival order
141
142
  await process_request(lane, response, received, state)
142
143
  }
143
144
 
145
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
144
146
  await sleep(POLL_MS)
145
147
  }
146
148
  }
@@ -232,6 +234,7 @@ async function wait_response(target: string, session: string, nonce: string): Pr
232
234
  const verdict = response_verdict(read_record(target, response_schema), session, nonce)
233
235
  if (verdict !== 'wait') return verdict === 'success'
234
236
 
237
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
235
238
  await sleep(POLL_MS)
236
239
  }
237
240
 
@@ -70,6 +70,7 @@ async function drain<Item, Result>(
70
70
  const [index, item] = entry
71
71
 
72
72
  try {
73
+ // eslint-disable-next-line no-await-in-loop -- one consumer runs one task at a time; the pool's width is the concurrency
73
74
  pool.results.push({ index, value: await worker(item, index) })
74
75
  } catch (error) {
75
76
  pool.failures.push(error)
@@ -1,9 +1,10 @@
1
1
  // Reading JSON that may not be JSON (joshuafolkken/kit#1151).
2
2
  //
3
- // Four readers under `scripts/` parse text that a defect, a truncated write or an older format can
4
- // make unparseable — a transcript line, a settings file, an MCP declaration — and every one of them
5
- // answers "could not read this" rather than throwing, because a single bad line must not fail a
6
- // whole report. They had begun to carry a copy of the same try/catch each; this is the one copy.
3
+ // Readers under `scripts/` parse text that a defect, a truncated write or an older format can make
4
+ // unparseable — a transcript line, a settings file, an MCP declaration, a lock record, CLI output —
5
+ // and every one of them answers "could not read this" rather than throwing, because a single bad line
6
+ // must not fail a whole report. They had begun to carry a copy of the same try/catch each; this is the
7
+ // one copy, and `is_record` beside it is the one object test (joshuafolkken/kit#2986).
7
8
 
8
9
  function parse_or_undefined(text: string): unknown {
9
10
  try {
@@ -1,6 +1,6 @@
1
1
  // One bounded polling loop and one sleep, in one place.
2
2
  //
3
- // **Three modules had already written the same three-line `sleep`** — `git/git-pr-checks.ts`,
3
+ // **Three modules had already written the same three-line `sleep`** — `gh/git-pr-checks.ts`,
4
4
  // `eval/eval-run.ts` and `propagate/propagate-publish.ts` — and the release command's tag watch
5
5
  // (joshuafolkken/kit#1169) would have been the fourth. `CLAUDE.md` → "No clones — single-source"
6
6
  // reads an existing duplication as the signal to single-source it rather than as a license to add to
@@ -43,8 +43,10 @@ async function poll_until(is_done: () => Promise<boolean>, options: PollOptions)
43
43
  const wait = options.sleeper ?? sleep
44
44
 
45
45
  for (let attempt = 0; attempt < options.attempts; attempt += 1) {
46
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
46
47
  if (await is_done()) return true
47
48
 
49
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the state the previous one saw
48
50
  await wait_between(wait, options.interval_ms, attempt, options.attempts)
49
51
  }
50
52
 
@@ -0,0 +1,15 @@
1
+ import { FETCH_TIMEOUT_MS } from './timeouts'
2
+
3
+ // **The one place in `scripts/` that calls the global `fetch`** (joshuafolkken/kit#2981). Every request
4
+ // carries a time limit, so a stalled connection rejects instead of holding an unattended run forever;
5
+ // `timed-fetch-usage.test.ts` refuses a direct `fetch` call anywhere else. The signal also bounds the
6
+ // body read, because the response stream aborts with it.
7
+ async function timed_fetch(
8
+ url: string,
9
+ init: Omit<RequestInit, 'signal'> = {},
10
+ timeout_ms: number = FETCH_TIMEOUT_MS,
11
+ ): Promise<Response> {
12
+ return await fetch(url, { ...init, signal: AbortSignal.timeout(timeout_ms) })
13
+ }
14
+
15
+ export { timed_fetch }
@@ -0,0 +1,13 @@
1
+ // Shared time limits for the calls that leave the process — a network request or a spawned command.
2
+ // An unattended run (a lane, `followup`, a backlog drain) that waits on a request or a command with no
3
+ // limit stays stuck where it stopped and never comes back (joshuafolkken/kit#2981), so every such
4
+ // call takes one of these, and a value used in more than one place is named once here.
5
+
6
+ // One HTTP request to a registry, an API or a webhook.
7
+ const FETCH_TIMEOUT_MS = 10_000
8
+ // A short command that only answers a question (`gh --version`, `pnpm config get`).
9
+ const COMMAND_TIMEOUT_MS = 30_000
10
+ // A `pnpm install`, which can download the whole dependency tree on a cold store.
11
+ const INSTALL_TIMEOUT_MS = 600_000
12
+
13
+ export { COMMAND_TIMEOUT_MS, FETCH_TIMEOUT_MS, INSTALL_TIMEOUT_MS }
@@ -1,3 +1,4 @@
1
+ import { json_value } from '#scripts/lib/json-value'
1
2
  import { json_object_schema } from '#scripts/lib/schemas'
2
3
  import { load, loadAll, YAMLException } from 'js-yaml'
3
4
 
@@ -25,8 +26,9 @@ function parse_yaml(content: string): Record<string, unknown> {
25
26
  return json_object_schema.parse(raw)
26
27
  }
27
28
 
29
+ // A YAML mapping parses to the same plain object a JSON object does, so the test is `json_value`'s.
28
30
  function is_mapping_document(value: unknown): value is Record<string, unknown> {
29
- return typeof value === 'object' && value !== null && !Array.isArray(value)
31
+ return json_value.is_record(value)
30
32
  }
31
33
 
32
34
  // pnpm 11 writes pnpm-lock.yaml as a multi-document stream (the package-manager document, then the