@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,7 +1,7 @@
1
1
  import {
2
2
  lane_child_marker,
3
3
  run_issue_number
4
- } from "./chunk-HOYJU5RM.js";
4
+ } from "./chunk-E54KGFEQ.js";
5
5
  import {
6
6
  PLATFORM_TEMP_ROOT,
7
7
  cost_blocks,
@@ -13,7 +13,7 @@ import {
13
13
  time_density_hook,
14
14
  time_shell,
15
15
  time_transcript_line
16
- } from "./chunk-GLBDBJOK.js";
16
+ } from "./chunk-NKOG3XAV.js";
17
17
  import {
18
18
  ENV_FILE_NAME,
19
19
  LANE_SEAT_KEY,
@@ -32,7 +32,6 @@ function is_josh_command(segment, names) {
32
32
  return names.has(named.slice(time_shell.JOSH_PREFIX.length));
33
33
  }
34
34
  var shell_segments = {
35
- SEGMENT_SEPARATOR,
36
35
  is_josh_command,
37
36
  segments_of
38
37
  };
@@ -69,7 +68,7 @@ function bash_commands_of(line) {
69
68
  function prior_bash_commands(tail) {
70
69
  return tail.split("\n").flatMap((line) => bash_commands_of(line));
71
70
  }
72
- var tail_commands = { bash_commands_of, is_bash_use, prior_bash_commands };
71
+ var tail_commands = { is_bash_use, prior_bash_commands };
73
72
 
74
73
  // scripts/rules/filing-cap.ts
75
74
  var FILING_CAP = 10;
@@ -123,8 +122,6 @@ var filing_cap = {
123
122
  FILING_CAP,
124
123
  FILING_CAP_REASON,
125
124
  ROW,
126
- current_turn,
127
- decide,
128
125
  is_prompt_line,
129
126
  prior_filing_count,
130
127
  turn_filing_count
@@ -153,7 +150,7 @@ var git_utilities = {
153
150
  };
154
151
 
155
152
  // scripts/git/git-push-transport.ts
156
- import { execa } from "execa";
153
+ import { execa as execa2 } from "execa";
157
154
 
158
155
  // scripts/git/git-execa-error.ts
159
156
  function get_exit_code(error) {
@@ -166,25 +163,20 @@ function has_timed_out(error) {
166
163
  if (typeof error !== "object" || error === null || !("timedOut" in error)) return false;
167
164
  return error.timedOut === true;
168
165
  }
166
+ var TIMEOUT_EXIT_CODE = "timeout";
169
167
  function create_spawn_error(command, exit_code) {
170
168
  const exit_code_string = exit_code === void 0 ? "unknown" : String(exit_code);
171
169
  const error_message = `git ${command} exited with code ${exit_code_string}`;
172
170
  return new Error(error_message, { cause: { exit_code: exit_code_string } });
173
171
  }
174
172
 
175
- // scripts/git/git-push-transport.ts
176
- var MS_PER_SECOND = 1e3;
177
- var PUSH_TIMEOUT_SECONDS = 120;
178
- var PUSH_TIMEOUT_MS = PUSH_TIMEOUT_SECONDS * MS_PER_SECOND;
179
- var PUSH_TIMEOUT_LABEL = `${String(PUSH_TIMEOUT_SECONDS)}s`;
180
- var PUSH_TIMEOUT_MESSAGE = "git push timed out";
181
- var PUSH_TIMEOUT_EXIT_CODE = "timeout";
173
+ // scripts/git/git-ssh-keepalive.ts
174
+ import { execa } from "execa";
182
175
  var SSH_KEEPALIVE_OPTIONS = "-o ServerAliveInterval=15 -o ServerAliveCountMax=3";
183
176
  var SSH_COMMAND_VARIABLE = "GIT_SSH_COMMAND";
184
177
  var SSH_LEGACY_VARIABLE = "GIT_SSH";
185
178
  var KEEPALIVE_SSH_COMMAND = `ssh ${SSH_KEEPALIVE_OPTIONS}`;
186
179
  var SSH_COMMAND_CONFIG_KEY = "core.sshCommand";
187
- var PUSH_SUBCOMMAND = "push";
188
180
  function to_environment_value(name) {
189
181
  return process.env[name]?.trim() ?? "";
190
182
  }
@@ -202,19 +194,33 @@ async function has_configured_ssh_command() {
202
194
  return false;
203
195
  }
204
196
  }
205
- async function to_push_options() {
197
+ async function to_environment() {
206
198
  const should_keep_alive = !await has_configured_ssh_command();
199
+ return should_keep_alive ? { [SSH_COMMAND_VARIABLE]: KEEPALIVE_SSH_COMMAND } : {};
200
+ }
201
+ var git_ssh_keepalive = {
202
+ to_environment
203
+ };
204
+
205
+ // scripts/git/git-push-transport.ts
206
+ var MS_PER_SECOND = 1e3;
207
+ var PUSH_TIMEOUT_SECONDS = 120;
208
+ var PUSH_TIMEOUT_MS = PUSH_TIMEOUT_SECONDS * MS_PER_SECOND;
209
+ var PUSH_TIMEOUT_LABEL = `${String(PUSH_TIMEOUT_SECONDS)}s`;
210
+ var PUSH_TIMEOUT_MESSAGE = "git push timed out";
211
+ var PUSH_SUBCOMMAND = "push";
212
+ async function to_push_options() {
207
213
  return {
208
214
  stdio: "inherit",
209
215
  timeout: PUSH_TIMEOUT_MS,
210
- env: should_keep_alive ? { [SSH_COMMAND_VARIABLE]: KEEPALIVE_SSH_COMMAND } : {}
216
+ env: await git_ssh_keepalive.to_environment()
211
217
  };
212
218
  }
213
219
  async function did_push_time_out(arguments_list) {
214
220
  const git_command_bin = git_utilities.get_git_command_for_spawn();
215
221
  const options = await to_push_options();
216
222
  try {
217
- await execa(git_command_bin, [PUSH_SUBCOMMAND, ...arguments_list], options);
223
+ await execa2(git_command_bin, [PUSH_SUBCOMMAND, ...arguments_list], options);
218
224
  return false;
219
225
  } catch (error) {
220
226
  if (has_timed_out(error)) return true;
@@ -229,7 +235,7 @@ function to_retry_notice(arguments_list) {
229
235
  }
230
236
  function create_timeout_error(arguments_list) {
231
237
  const message = `${PUSH_TIMEOUT_MESSAGE} twice (${PUSH_TIMEOUT_LABEL} each). Re-run it by hand: ${to_manual_command(arguments_list)}`;
232
- return new Error(message, { cause: { exit_code: PUSH_TIMEOUT_EXIT_CODE } });
238
+ return new Error(message, { cause: { exit_code: TIMEOUT_EXIT_CODE } });
233
239
  }
234
240
  async function push(arguments_list) {
235
241
  if (!await did_push_time_out(arguments_list)) return;
@@ -242,23 +248,56 @@ var git_push_transport = {
242
248
  };
243
249
 
244
250
  // scripts/git/git-spawn.ts
245
- import { execa as execa2 } from "execa";
251
+ import { execa as execa3 } from "execa";
246
252
  async function read(arguments_) {
247
253
  const git_cmd = git_utilities.get_git_command_for_spawn();
248
- const { stdout } = await execa2(git_cmd, arguments_);
254
+ const { stdout } = await execa3(git_cmd, arguments_);
249
255
  return stdout.trimEnd();
250
256
  }
257
+ var REMOTE_TIMEOUT_MS = PUSH_TIMEOUT_MS;
258
+ var MS_PER_SECOND2 = 1e3;
259
+ function to_remote_error(command, error, timeout_ms) {
260
+ if (!has_timed_out(error)) return create_spawn_error(command, get_exit_code(error));
261
+ const seconds = String(Math.round(timeout_ms / MS_PER_SECOND2));
262
+ return new Error(`git ${command} timed out after ${seconds}s`, {
263
+ cause: { exit_code: TIMEOUT_EXIT_CODE }
264
+ });
265
+ }
266
+ async function spawn_remote(arguments_, options) {
267
+ const git_cmd = git_utilities.get_git_command_for_spawn();
268
+ try {
269
+ const { stdout } = await execa3(git_cmd, arguments_, options);
270
+ return typeof stdout === "string" ? stdout.trimEnd() : "";
271
+ } catch (error) {
272
+ throw to_remote_error(arguments_[0] ?? "", error, options.timeout);
273
+ }
274
+ }
275
+ async function read_remote(arguments_, timeout_ms = REMOTE_TIMEOUT_MS) {
276
+ return await spawn_remote(arguments_, {
277
+ timeout: timeout_ms,
278
+ env: await git_ssh_keepalive.to_environment()
279
+ });
280
+ }
281
+ async function with_output_remote(command, arguments_list) {
282
+ await spawn_remote([command, ...arguments_list], {
283
+ stdio: "inherit",
284
+ timeout: REMOTE_TIMEOUT_MS,
285
+ env: await git_ssh_keepalive.to_environment()
286
+ });
287
+ }
251
288
  async function with_output(command, arguments_list) {
252
289
  const git_command_bin = git_utilities.get_git_command_for_spawn();
253
290
  try {
254
- await execa2(git_command_bin, [command, ...arguments_list], { stdio: "inherit" });
291
+ await execa3(git_command_bin, [command, ...arguments_list], { stdio: "inherit" });
255
292
  } catch (error) {
256
293
  throw create_spawn_error(command, get_exit_code(error));
257
294
  }
258
295
  }
259
296
  var git_spawn = {
260
297
  read,
261
- with_output
298
+ read_remote,
299
+ with_output,
300
+ with_output_remote
262
301
  };
263
302
 
264
303
  // scripts/git/git-command.ts
@@ -433,7 +472,7 @@ async function push2() {
433
472
  }
434
473
  }
435
474
  async function pull_fast_forward() {
436
- await git_spawn.with_output("pull", ["--ff-only"]);
475
+ await git_spawn.with_output_remote("pull", ["--ff-only"]);
437
476
  }
438
477
  var SHORT_NAME_FORMAT = "--format=%(refname:short)";
439
478
  var REMOTES_FLAG = "--remotes";
@@ -559,19 +598,20 @@ function hold_path(git_directory) {
559
598
  return stamp_file.stamp_path(HOLD_PREFIX, git_directory);
560
599
  }
561
600
  async function worktree_directory() {
562
- const directories = await git_command.git_directories();
563
- return directories[0];
601
+ const directories2 = await git_command.git_directories();
602
+ return directories2[0];
564
603
  }
565
604
  async function is_linked_worktree() {
566
- const directories = await git_command.git_directories();
567
- return directories[0] !== void 0 && directories[0] !== directories[1];
605
+ const directories2 = await git_command.git_directories();
606
+ return directories2[0] !== void 0 && directories2[0] !== directories2[1];
568
607
  }
569
608
  var run_hold_schema = z.object({
570
609
  issue: z.string(),
571
610
  taken_at: z.string(),
572
611
  pid: z.number(),
573
612
  is_fullrun: z.boolean().optional(),
574
- is_halfrun_stop: z.boolean().optional()
613
+ is_halfrun_stop: z.boolean().optional(),
614
+ prrun_stop_head: z.string().optional()
575
615
  });
576
616
  function parse_hold(raw) {
577
617
  try {
@@ -606,8 +646,12 @@ function write_hold(target, issue, now = /* @__PURE__ */ new Date()) {
606
646
  function create_hold(target, issue, now = /* @__PURE__ */ new Date(), is_fullrun) {
607
647
  return stamp_file.create_stamp(target, build_hold(issue, now, is_fullrun));
608
648
  }
649
+ var HALFRUN_STOP_FIELDS = { is_halfrun_stop: true };
650
+ function create_stop_hold(target, issue, mark, now = /* @__PURE__ */ new Date()) {
651
+ return stamp_file.create_stamp(target, { ...build_hold(issue, now), ...mark });
652
+ }
609
653
  function create_halfrun_stop_hold(target, issue, now = /* @__PURE__ */ new Date()) {
610
- return stamp_file.create_stamp(target, { ...build_hold(issue, now), is_halfrun_stop: true });
654
+ return create_stop_hold(target, issue, HALFRUN_STOP_FIELDS, now);
611
655
  }
612
656
  async function is_tree_dirty(directory) {
613
657
  try {
@@ -663,8 +707,9 @@ var run_hold = {
663
707
  UNNUMBERED_ISSUE,
664
708
  classify,
665
709
  create_halfrun_stop_hold,
710
+ create_stop_hold,
711
+ HALFRUN_STOP_FIELDS,
666
712
  create_hold,
667
- describe_holder,
668
713
  foreign_release_message,
669
714
  forced_release_message,
670
715
  held_message,
@@ -673,7 +718,6 @@ var run_hold = {
673
718
  is_own_hold,
674
719
  is_tree_dirty,
675
720
  own_release_command,
676
- parse_hold,
677
721
  race_message,
678
722
  read_hold,
679
723
  release_hold,
@@ -685,9 +729,6 @@ var run_hold = {
685
729
  write_hold
686
730
  };
687
731
 
688
- // scripts/run/run-cut.ts
689
- import { execFileSync } from "node:child_process";
690
-
691
732
  // scripts/josh/agent-session-environment.ts
692
733
  var SESSION_ID_KEY = "CLAUDE_CODE_SESSION_ID";
693
734
  var PARENT_SESSION_KEYS = [
@@ -732,13 +773,13 @@ function is_declared(value) {
732
773
  return value !== void 0 && value !== NO_HOST;
733
774
  }
734
775
  function loopback_proxy_keys(source, is_removed = is_loopback_proxy) {
735
- const declared = PROXY_KEYS.filter((key) => is_declared(source[key]));
736
- const matched = declared.filter((key) => is_removed(source[key]));
776
+ const declared = PROXY_KEYS.filter((key2) => is_declared(source[key2]));
777
+ const matched = declared.filter((key2) => is_removed(source[key2]));
737
778
  const is_certificate_included = matched.length > 0 && matched.length === declared.length;
738
779
  return is_certificate_included ? [...matched, PROXY_CERTIFICATE_KEY] : matched;
739
780
  }
740
781
  function to_removed(keys) {
741
- return Object.fromEntries(keys.map((key) => [key, void 0]));
782
+ return Object.fromEntries(keys.map((key2) => [key2, void 0]));
742
783
  }
743
784
  function removed_environment(source = process.env) {
744
785
  return to_removed([...PARENT_SESSION_KEYS, ...loopback_proxy_keys(source)]);
@@ -840,55 +881,55 @@ function trimmed(value) {
840
881
  const result = value?.trim();
841
882
  return result === void 0 || result.length === EMPTY_LENGTH ? void 0 : result;
842
883
  }
843
- function override_value(role, field, environment) {
844
- const key = ENV_KEYS[role][field];
845
- const value = trimmed(environment[key]);
846
- if (value !== void 0) return { key, value };
847
- if (role !== WORKER) return { key, value: void 0 };
884
+ function override_value(role, field, environment2) {
885
+ const key2 = ENV_KEYS[role][field];
886
+ const value = trimmed(environment2[key2]);
887
+ if (value !== void 0) return { key: key2, value };
888
+ if (role !== WORKER) return { key: key2, value: void 0 };
848
889
  const legacy = LEGACY_WORKER_KEYS[field];
849
- const legacy_value = trimmed(environment[legacy]);
850
- return legacy_value === void 0 ? { key, value: void 0 } : { key: legacy, value: legacy_value };
890
+ const legacy_value = trimmed(environment2[legacy]);
891
+ return legacy_value === void 0 ? { key: key2, value: void 0 } : { key: legacy, value: legacy_value };
851
892
  }
852
- function model_override(role, environment, provider) {
853
- const key = ENV_KEYS[role].model;
854
- return provider === OPENAI_PROVIDER ? { key, value: void 0 } : override_value(role, "model", environment);
893
+ function model_override(role, environment2, provider) {
894
+ const key2 = ENV_KEYS[role].model;
895
+ return provider === OPENAI_PROVIDER ? { key: key2, value: void 0 } : override_value(role, "model", environment2);
855
896
  }
856
- function rejection(key, value, expected) {
857
- return { kind: "rejected", note: `${key}=${value} ${expected}, so the agent was not started` };
897
+ function rejection(key2, value, expected) {
898
+ return { kind: "rejected", note: `${key2}=${value} ${expected}, so the agent was not started` };
858
899
  }
859
900
  function resolved_effort(override, fallback) {
860
901
  const parsed = EFFORT_SCHEMA.safeParse(override.value ?? fallback);
861
902
  return parsed.success ? { kind: "effort", effort: parsed.data } : rejection(override.key, override.value ?? "", "is not an allowed effort");
862
903
  }
863
- function has_session(environment, keys) {
864
- return keys.some((key) => trimmed(environment[key]) !== void 0);
904
+ function has_session(environment2, keys) {
905
+ return keys.some((key2) => trimmed(environment2[key2]) !== void 0);
865
906
  }
866
907
  function session_rejection(is_conflicting) {
867
908
  const note = is_conflicting ? "both Codex and Claude Code sessions were detected" : "no Codex or Claude Code session was detected";
868
909
  return { kind: "rejected", note };
869
910
  }
870
911
  var HANDED_PROVIDER_KEY = "JOSH_AGENT_PROVIDER";
871
- function handed_provider(environment) {
872
- const value = trimmed(environment[HANDED_PROVIDER_KEY]);
912
+ function handed_provider(environment2) {
913
+ const value = trimmed(environment2[HANDED_PROVIDER_KEY]);
873
914
  if (value === void 0) return session_rejection(false);
874
915
  const parsed = PROVIDER_SCHEMA.safeParse(value);
875
916
  return parsed.success ? { kind: "provider", provider: parsed.data } : rejection(HANDED_PROVIDER_KEY, value, "is not an allowed provider");
876
917
  }
877
- function detected_provider(environment) {
878
- const has_codex = has_session(environment, [CODEX_SESSION_KEY]);
879
- const has_claude = has_session(environment, agent_session_environment.PARENT_SESSION_KEYS);
918
+ function detected_provider(environment2) {
919
+ const has_codex = has_session(environment2, [CODEX_SESSION_KEY]);
920
+ const has_claude = has_session(environment2, agent_session_environment.PARENT_SESSION_KEYS);
880
921
  if (has_codex === has_claude) return has_codex ? session_rejection(true) : void 0;
881
922
  return { kind: "provider", provider: has_codex ? OPENAI_PROVIDER : ANTHROPIC_PROVIDER };
882
923
  }
883
- function resolve_provider(environment = process.env) {
884
- return detected_provider(environment) ?? handed_provider(environment);
924
+ function resolve_provider(environment2 = process.env) {
925
+ return detected_provider(environment2) ?? handed_provider(environment2);
885
926
  }
886
- function has_completion_callback(environment = process.env) {
887
- const selected = resolve_provider(environment);
927
+ function has_completion_callback(environment2 = process.env) {
928
+ const selected = resolve_provider(environment2);
888
929
  return selected.kind !== "provider" || selected.provider !== OPENAI_PROVIDER;
889
930
  }
890
- function handoff_environment(environment = process.env) {
891
- const selected = resolve_provider(environment);
931
+ function handoff_environment(environment2 = process.env) {
932
+ const selected = resolve_provider(environment2);
892
933
  return selected.kind === "provider" ? { [HANDED_PROVIDER_KEY]: selected.provider } : {};
893
934
  }
894
935
  function validate(profile, model_key) {
@@ -902,19 +943,19 @@ function phase_effort(role, phase, fallback) {
902
943
  if (!parsed.success) return fallback;
903
944
  return PHASE_EFFORT[role]?.[parsed.data] ?? fallback;
904
945
  }
905
- function overridden_effort(profile, phase, environment) {
906
- const parsed = EFFORT_SCHEMA.safeParse(override_value(profile.role, "effort", environment).value);
946
+ function overridden_effort(profile, phase, environment2) {
947
+ const parsed = EFFORT_SCHEMA.safeParse(override_value(profile.role, "effort", environment2).value);
907
948
  return parsed.success ? parsed.data : phase_effort(profile.role, phase, profile.effort);
908
949
  }
909
- function with_phase_effort(profile, phase, environment = process.env) {
910
- return { ...profile, effort: overridden_effort(profile, phase, environment) };
950
+ function with_phase_effort(profile, phase, environment2 = process.env) {
951
+ return { ...profile, effort: overridden_effort(profile, phase, environment2) };
911
952
  }
912
- function resolve(role, environment = process.env, phase) {
913
- const selected = resolve_provider(environment);
953
+ function resolve(role, environment2 = process.env, phase) {
954
+ const selected = resolve_provider(environment2);
914
955
  if (selected.kind === "rejected") return selected;
915
956
  const defaults = PROVIDER_PROFILES[selected.provider][role];
916
- const model = model_override(role, environment, selected.provider);
917
- const effort = override_value(role, "effort", environment);
957
+ const model = model_override(role, environment2, selected.provider);
958
+ const effort = override_value(role, "effort", environment2);
918
959
  const resolved = resolved_effort(effort, phase_effort(role, phase, defaults.effort));
919
960
  if (resolved.kind === "rejected") return resolved;
920
961
  const profile = {
@@ -934,17 +975,11 @@ function parse(value) {
934
975
  }
935
976
  var agent_role_profile = {
936
977
  DEFAULT_PROFILES,
937
- EFFORT_SCHEMA,
938
- ENV_KEYS,
939
978
  HANDED_PROVIDER_KEY,
940
979
  IMPLEMENTATION_PHASE,
941
- LEGACY_WORKER_KEYS,
942
980
  MAX_VALUE_LENGTH,
943
981
  OPENAI_PROFILES,
944
- PHASE_EFFORT,
945
- PHASE_SCHEMA,
946
982
  PRE_GATE_PHASE,
947
- PROVIDER_SCHEMA,
948
983
  PROFILE_SCHEMA,
949
984
  REVIEWER,
950
985
  SCHEDULER,
@@ -954,7 +989,6 @@ var agent_role_profile = {
954
989
  has_completion_callback,
955
990
  is_safe_value,
956
991
  parse,
957
- phase_effort,
958
992
  resolve,
959
993
  resolve_provider,
960
994
  with_phase_effort
@@ -1061,7 +1095,6 @@ function decide2(input) {
1061
1095
  }
1062
1096
  var backlog_budget = {
1063
1097
  ANSWERS,
1064
- ANSWER_STOP_REASONS,
1065
1098
  BLOCKED_REASON,
1066
1099
  DEFAULT_IDLE_MINUTES,
1067
1100
  DEFAULT_IDLE_MS,
@@ -1076,7 +1109,6 @@ var backlog_budget = {
1076
1109
  UNREADABLE_REASON,
1077
1110
  WATCH_VERDICT,
1078
1111
  WHOLE_RUN_BUDGET_HOURS,
1079
- WHOLE_RUN_BUDGET_MINUTES,
1080
1112
  WHOLE_RUN_BUDGET_MS,
1081
1113
  WHOLE_RUN_REASON,
1082
1114
  decide: decide2,
@@ -1162,7 +1194,6 @@ var cost_pricing = {
1162
1194
  CACHE_WRITE_5M_MULTIPLIER,
1163
1195
  CACHE_WRITE_1H_MULTIPLIER,
1164
1196
  CACHE_READ_MULTIPLIER,
1165
- MODEL_PRICES,
1166
1197
  resolve_price,
1167
1198
  estimate_cost,
1168
1199
  estimate_composition,
@@ -1201,6 +1232,9 @@ var context_cut_payback = {
1201
1232
  var CONTEXT_CUT_THRESHOLD = context_cut_payback.break_even_context();
1202
1233
  var RECENT_REQUEST_WINDOW = 10;
1203
1234
 
1235
+ // scripts/git/git-common-directory.ts
1236
+ import { execFileSync } from "node:child_process";
1237
+
1204
1238
  // scripts/git/git-location-environment.ts
1205
1239
  var GIT_LOCATION_VARIABLES = [
1206
1240
  "GIT_ALTERNATE_OBJECT_DIRECTORIES",
@@ -1230,6 +1264,197 @@ function clear_git_location_variables() {
1230
1264
  }
1231
1265
  var git_location_environment = { clear_git_location_variables, location_free_environment };
1232
1266
 
1267
+ // scripts/git/git-common-directory.ts
1268
+ var OWN_GIT_DIRECTORY = 0;
1269
+ var COMMON_GIT_DIRECTORY = 1;
1270
+ var PROBE_TIMEOUT_MS = 5e3;
1271
+ var answers = /* @__PURE__ */ new Map();
1272
+ function probe_environment(cwd) {
1273
+ if (cwd === void 0) return process.env;
1274
+ return { ...process.env, ...git_location_environment.location_free_environment() };
1275
+ }
1276
+ function cache_key(cwd) {
1277
+ if (cwd !== void 0) return `explicit\0${cwd}`;
1278
+ const locations = GIT_LOCATION_VARIABLES.map((name) => process.env[name] ?? "");
1279
+ return ["inherited", process.cwd(), ...locations].join("\0");
1280
+ }
1281
+ function read_directories(cwd) {
1282
+ try {
1283
+ const output = execFileSync(
1284
+ git_utilities.get_git_command_for_spawn(),
1285
+ [...git_command.GIT_DIRECTORY_ARGUMENTS],
1286
+ {
1287
+ cwd,
1288
+ env: probe_environment(cwd),
1289
+ encoding: "utf8",
1290
+ stdio: ["ignore", "pipe", "ignore"],
1291
+ timeout: PROBE_TIMEOUT_MS
1292
+ }
1293
+ );
1294
+ return output.split("\n").filter((line) => line !== "");
1295
+ } catch {
1296
+ return [];
1297
+ }
1298
+ }
1299
+ function directories(cwd) {
1300
+ const key2 = cache_key(cwd);
1301
+ const known = answers.get(key2);
1302
+ if (known !== void 0) return known;
1303
+ const read2 = read_directories(cwd);
1304
+ if (read2.length > 0) answers.set(key2, read2);
1305
+ return read2;
1306
+ }
1307
+ function own(cwd) {
1308
+ return directories(cwd)[OWN_GIT_DIRECTORY];
1309
+ }
1310
+ function repository(cwd) {
1311
+ return directories(cwd)[COMMON_GIT_DIRECTORY];
1312
+ }
1313
+ function select_linked(found) {
1314
+ const own_directory = found[OWN_GIT_DIRECTORY];
1315
+ const common = found[COMMON_GIT_DIRECTORY];
1316
+ return own_directory !== void 0 && common !== void 0 && own_directory !== common ? common : void 0;
1317
+ }
1318
+ function resolve2(cwd) {
1319
+ return select_linked(directories(cwd));
1320
+ }
1321
+ var git_common_directory = { directories, own, repository, resolve: resolve2, select_linked };
1322
+
1323
+ // scripts/run/run-ship-stage.ts
1324
+ import { z as z3 } from "zod";
1325
+ var STAGE = {
1326
+ PREFLIGHT: "preflight",
1327
+ REVIEW: "review",
1328
+ GATE: "gate",
1329
+ COMMIT: "commit",
1330
+ ROUND_TWO: "round-2",
1331
+ FOLLOWUP: "followup",
1332
+ REPORT: "report"
1333
+ };
1334
+ var PHASE = {
1335
+ START: "start",
1336
+ DONE: "done",
1337
+ FAILED: "failed",
1338
+ SKIPPED: "skipped"
1339
+ };
1340
+ var RECORD_PREFIX = "josh-ship-stages-";
1341
+ var RECORD_SEPARATOR = "-";
1342
+ var SKIP_COMMIT_FLAG = "--skip-commit";
1343
+ var SKIP_PUSH_FLAG = "--skip-push";
1344
+ var record_schema = z3.object({ done: z3.array(z3.string()) });
1345
+ function record_path(repository2, issue) {
1346
+ return stamp_file.stamp_path(`${RECORD_PREFIX}${issue}${RECORD_SEPARATOR}`, repository2);
1347
+ }
1348
+ function parse_done(raw) {
1349
+ try {
1350
+ return new Set(record_schema.parse(JSON.parse(raw)).done);
1351
+ } catch {
1352
+ return /* @__PURE__ */ new Set();
1353
+ }
1354
+ }
1355
+ function read_done(target) {
1356
+ const raw = stamp_file.read_stamp_text(target);
1357
+ return raw === void 0 ? /* @__PURE__ */ new Set() : parse_done(raw);
1358
+ }
1359
+ function mark_done(target, stage) {
1360
+ const done = /* @__PURE__ */ new Set([...read_done(target), stage]);
1361
+ stamp_file.replace_stamp(target, { done: [...done] });
1362
+ }
1363
+ function clear(target) {
1364
+ stamp_file.remove_stamp(target);
1365
+ }
1366
+ function is_shipped(state) {
1367
+ return state.is_committed && state.is_pushed;
1368
+ }
1369
+ var DONE_BY = {
1370
+ // The preflight (joshuafolkken/kit#2946) asks what the commit stage would refuse on, so a commit is
1371
+ // its output as it is the gate's; a resumed commit stage still asks `git -y`'s own preflight.
1372
+ [STAGE.PREFLIGHT]: (_done, state) => state.is_merged || state.is_committed,
1373
+ // The `--review` round (joshuafolkken/kit#2427) precedes the commit, so a commit is its output as it
1374
+ // is the gate's. A record alone is not honored: before a commit the tree may have been edited since
1375
+ // the recorded round, and only a commit pins the tree that round read.
1376
+ [STAGE.REVIEW]: (_done, state) => state.is_merged || state.is_committed,
1377
+ [STAGE.GATE]: (_done, state) => state.is_merged || state.is_committed,
1378
+ [STAGE.COMMIT]: (done, state) => state.is_merged || done.has(STAGE.COMMIT) && is_shipped(state),
1379
+ // The round-2 pass (joshuafolkken/kit#2489) reads the pushed fix delta, so its record is honored only
1380
+ // while that push still stands — new work pushed on top reruns it.
1381
+ [STAGE.ROUND_TWO]: (done, state) => state.is_merged || done.has(STAGE.ROUND_TWO) && is_shipped(state),
1382
+ // A recorded followup still needs the shipped state: a record left by a merged ship whose report
1383
+ // failed must not pass over the followup of new, not-yet-pushed work on the same issue.
1384
+ [STAGE.FOLLOWUP]: (done, state) => state.is_merged || done.has(STAGE.FOLLOWUP) && is_shipped(state),
1385
+ [STAGE.REPORT]: (done) => done.has(STAGE.REPORT)
1386
+ };
1387
+ function is_done(stage, done, state, has_body = false) {
1388
+ if (has_body && stage === STAGE.COMMIT && !state.is_merged) return false;
1389
+ return DONE_BY[stage](done, state);
1390
+ }
1391
+ function commit_flags(state) {
1392
+ if (!state.is_committed) return [];
1393
+ return state.is_pushed ? [SKIP_COMMIT_FLAG, SKIP_PUSH_FLAG] : [SKIP_COMMIT_FLAG];
1394
+ }
1395
+ function event_text(issue, stage, phase) {
1396
+ return `#${issue} ${stage} ${phase}`;
1397
+ }
1398
+ var run_ship_stage = {
1399
+ PHASE,
1400
+ STAGE,
1401
+ clear,
1402
+ commit_flags,
1403
+ event_text,
1404
+ is_done,
1405
+ mark_done,
1406
+ read_done,
1407
+ record_path
1408
+ };
1409
+
1410
+ // scripts/run/run-ship-next.ts
1411
+ var { STAGE: STAGE2 } = run_ship_stage;
1412
+ var COMMAND = {
1413
+ LOG: "pnpm josh ship --log",
1414
+ DETACH: "pnpm josh ship --detach",
1415
+ DETACH_REVIEW: "pnpm josh ship --detach --review"
1416
+ };
1417
+ var ESCAPED_QUOTE = String.raw`'\''`;
1418
+ var BARE_WORD = /^[\w%+,./:=@-]+$/u;
1419
+ function quoted(title) {
1420
+ return `'${title.split("'").join(ESCAPED_QUOTE)}'`;
1421
+ }
1422
+ function shell_word(token) {
1423
+ return BARE_WORD.test(token) ? token : quoted(token);
1424
+ }
1425
+ function resume_of(args) {
1426
+ const cites = args.cites.flatMap((cite) => ["--cite", cite]);
1427
+ return {
1428
+ title: args.title,
1429
+ flags: [...args.notify, ...args.body, ...cites],
1430
+ is_review: args.is_review
1431
+ };
1432
+ }
1433
+ function ship_command(command, resume) {
1434
+ const words = [...resume.flags, resume.title].map((token) => shell_word(token));
1435
+ return [command, ...words].join(" ");
1436
+ }
1437
+ function detach_again(resume) {
1438
+ return `leave the fix uncommitted and run \`${ship_command(COMMAND.DETACH, resume)}\`.`;
1439
+ }
1440
+ function detach_as_started(resume) {
1441
+ const command = resume.is_review ? COMMAND.DETACH_REVIEW : COMMAND.DETACH;
1442
+ return `leave the fix uncommitted and run \`${ship_command(command, resume)}\`.`;
1443
+ }
1444
+ var AFTER_FIX = {
1445
+ [STAGE2.PREFLIGHT]: detach_as_started,
1446
+ [STAGE2.REVIEW]: detach_as_started,
1447
+ [STAGE2.GATE]: detach_as_started,
1448
+ [STAGE2.COMMIT]: detach_as_started,
1449
+ [STAGE2.ROUND_TWO]: detach_again,
1450
+ [STAGE2.FOLLOWUP]: detach_again,
1451
+ [STAGE2.REPORT]: detach_again
1452
+ };
1453
+ function next_step(issue, stage, resume) {
1454
+ return `Read the stopped report with \`${COMMAND.LOG} ${issue}\`, fix the ${stage} failure, then ${AFTER_FIX[stage](resume)}`;
1455
+ }
1456
+ var run_ship_next = { COMMAND, next_step, quoted, resume_of };
1457
+
1233
1458
  // scripts/lane/lane-child-invocation.ts
1234
1459
  var CHILD_INVOCATION = "fullrun";
1235
1460
  var RESUME_GUIDE = ".claude/skills/workflow-commands/pre-gate-cut.md";
@@ -1250,12 +1475,12 @@ function outage_resume_invocation(issue) {
1250
1475
  const preamble = `Resuming the lane child for issue #${issue} after an API disconnection \u2014 your session was restored with its full context, so do not re-read the workflow-commands entry documents (SKILL.md, fullrun.md). Continue the run from where it stopped: run \`pnpm josh run:step ${issue}\` to find the next action, redoing only the last step if it did not complete.`;
1251
1476
  return `${preamble} ${child_invocation(issue)}`;
1252
1477
  }
1253
- function ship_stop_invocation(issue) {
1254
- const preamble = `Resuming the lane child for issue #${issue} after its detached ship supervisor stopped at a failed stage \u2014 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.`;
1255
- return `${preamble} ${child_invocation(issue)}`;
1478
+ function ship_stop_invocation(issue, stop) {
1479
+ const opening = `Resuming the lane child for issue #${issue} after its detached ship supervisor stopped at a failed stage \u2014 do not re-read the workflow-commands entry documents (SKILL.md, fullrun.md).`;
1480
+ const next = stop === void 0 ? `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.` : run_ship_next.next_step(issue, stop.stage, stop);
1481
+ return `${opening} ${next} Do not re-plan or re-implement what is already on the branch. ${child_invocation(issue)}`;
1256
1482
  }
1257
1483
  var lane_child_invocation = {
1258
- CHILD_INVOCATION,
1259
1484
  child_invocation,
1260
1485
  outage_resume_invocation,
1261
1486
  process_pattern,
@@ -1264,16 +1489,16 @@ var lane_child_invocation = {
1264
1489
  };
1265
1490
 
1266
1491
  // scripts/run/run-cut.ts
1267
- import { z as z4 } from "zod";
1492
+ import { z as z5 } from "zod";
1268
1493
 
1269
1494
  // scripts/run/run-cut-handoff.ts
1270
1495
  import { readFileSync } from "node:fs";
1271
- import { z as z3 } from "zod";
1272
- var handoff_schema = z3.object({
1273
- instruction: z3.string(),
1274
- completed: z3.array(z3.string()),
1275
- remaining: z3.array(z3.string()),
1276
- untouched: z3.array(z3.string())
1496
+ import { z as z4 } from "zod";
1497
+ var handoff_schema = z4.object({
1498
+ instruction: z4.string(),
1499
+ completed: z4.array(z4.string()),
1500
+ remaining: z4.array(z4.string()),
1501
+ untouched: z4.array(z4.string())
1277
1502
  });
1278
1503
  var COMPLETED_LABEL = "Completed";
1279
1504
  var REMAINING_LABEL = "Remaining";
@@ -1312,10 +1537,10 @@ function load_handoff(handoff_path, max_bytes) {
1312
1537
  }
1313
1538
  return { kind: "ok", handoff };
1314
1539
  }
1315
- function describe_list(label, items) {
1316
- if (items.length === 0) return `${label}: ${EMPTY_LIST_NOTE}`;
1540
+ function describe_list(label2, items) {
1541
+ if (items.length === 0) return `${label2}: ${EMPTY_LIST_NOTE}`;
1317
1542
  const bullets = items.map((item) => ` - ${item}`).join("\n");
1318
- return `${label}:
1543
+ return `${label2}:
1319
1544
  ${bullets}`;
1320
1545
  }
1321
1546
  function describe_handoff(handoff) {
@@ -1342,22 +1567,20 @@ var { PRE_GATE_PHASE: PRE_GATE_PHASE2, IMPLEMENTATION_PHASE: IMPLEMENTATION_PHAS
1342
1567
  var IMPLEMENTATION_CONTEXT_THRESHOLD = CONTEXT_CUT_THRESHOLD;
1343
1568
  var CUT_MAX_AGE_MS = backlog_budget.WHOLE_RUN_BUDGET_MS;
1344
1569
  var CUT_MAX_AGE_HOURS = backlog_budget.WHOLE_RUN_BUDGET_HOURS;
1345
- var GIT_READ_TIMEOUT_MS = 5e3;
1346
- var WORKTREE_DIRECTORY_INDEX = 0;
1347
1570
  var END_COMMAND = "pnpm josh run:cut --end";
1348
1571
  var READ_COMMAND = "pnpm josh run:cut --json";
1349
1572
  var MAX_HANDOFF_BYTES = 8192;
1350
1573
  var NONE_READ = { kind: "none" };
1351
1574
  var UNREADABLE_READ2 = { kind: "unreadable" };
1352
- var run_cut_schema = z4.object({
1353
- invocation: z4.string(),
1354
- issue: z4.string(),
1355
- branch: z4.string(),
1356
- phase: z4.string(),
1357
- cut_at: z4.string(),
1358
- is_handed_off: z4.boolean().optional(),
1575
+ var run_cut_schema = z5.object({
1576
+ invocation: z5.string(),
1577
+ issue: z5.string(),
1578
+ branch: z5.string(),
1579
+ phase: z5.string(),
1580
+ cut_at: z5.string(),
1581
+ is_handed_off: z5.boolean().optional(),
1359
1582
  handoff: run_cut_handoff.handoff_schema.optional(),
1360
- is_merge_relaunched: z4.boolean().optional()
1583
+ is_merge_relaunched: z5.boolean().optional()
1361
1584
  });
1362
1585
  function cut_path(git_directory) {
1363
1586
  return stamp_file.stamp_path(CUT_PREFIX, git_directory);
@@ -1388,36 +1611,8 @@ function classify2(raw, now) {
1388
1611
  function read_cut(target, now = /* @__PURE__ */ new Date()) {
1389
1612
  return classify2(stamp_file.read_stamp_text(target), now);
1390
1613
  }
1391
- function git_directories_sync(cwd) {
1392
- try {
1393
- const output = execFileSync(
1394
- git_utilities.get_git_command_for_spawn(),
1395
- [...git_command.GIT_DIRECTORY_ARGUMENTS],
1396
- // git's own diagnostics are discarded: a checkout it cannot describe is already the
1397
- // `undefined` below, and a hook that let `fatal: not a git repository` through would print it
1398
- // in front of a tool call that is about to be allowed.
1399
- // A `PreToolUse` hook holds the tool call while it runs, so the read is bounded rather than
1400
- // left to whatever git does.
1401
- // `cwd` is set only by `lane_cut_sync`, which reads another lane's record from the parent.
1402
- // With a `cwd`, the git location variables are cleared so it is what git answers for: a hook
1403
- // exports `GIT_DIR`, which beats `cwd` and would name the hook's checkout instead
1404
- // (joshuafolkken/kit#2515). Without one the environment is kept, so this read resolves the same
1405
- // checkout as the asynchronous `git_directories` that writes the record.
1406
- {
1407
- cwd,
1408
- env: cwd === void 0 ? process.env : { ...process.env, ...git_location_environment.location_free_environment() },
1409
- encoding: "utf8",
1410
- stdio: ["ignore", "pipe", "ignore"],
1411
- timeout: GIT_READ_TIMEOUT_MS
1412
- }
1413
- );
1414
- return output.split("\n").filter((line) => line !== "");
1415
- } catch {
1416
- return [];
1417
- }
1418
- }
1419
1614
  function worktree_git_directory_sync() {
1420
- return git_directories_sync()[WORKTREE_DIRECTORY_INDEX];
1615
+ return git_common_directory.own();
1421
1616
  }
1422
1617
  function carried_cut_sync(now = /* @__PURE__ */ new Date(), directory = worktree_git_directory_sync()) {
1423
1618
  if (directory === void 0) return void 0;
@@ -1425,7 +1620,7 @@ function carried_cut_sync(now = /* @__PURE__ */ new Date(), directory = worktree
1425
1620
  return read2.kind === "carried" ? read2.cut : void 0;
1426
1621
  }
1427
1622
  function lane_cut_sync(lane_directory) {
1428
- const directory = git_directories_sync(lane_directory)[WORKTREE_DIRECTORY_INDEX];
1623
+ const directory = git_common_directory.own(lane_directory);
1429
1624
  if (directory === void 0) return void 0;
1430
1625
  const cut = carried_cut_sync(/* @__PURE__ */ new Date(), directory);
1431
1626
  return cut === void 0 ? void 0 : { target: cut_path(directory), cut };
@@ -1616,7 +1811,7 @@ var run_tail = {
1616
1811
 
1617
1812
  // scripts/rules/lane-background.ts
1618
1813
  var LONG_RUNNING_COMMANDS = new Set(
1619
- ["gate", "git", "followup"].map((name) => `${time_shell.JOSH_PREFIX}${name}`)
1814
+ ["gate", "git", "followup", "ship"].map((name) => `${time_shell.JOSH_PREFIX}${name}`)
1620
1815
  );
1621
1816
  var BACKGROUND_KEY2 = "run_in_background";
1622
1817
  function is_backgrounded2(input) {
@@ -1637,8 +1832,8 @@ function unfinished_ids(tail, launched_ids) {
1637
1832
  const lines = tail.split("\n").map((line) => time_transcript_line.parse_line(line));
1638
1833
  const finished = new Set(lines.map((line) => line?.finished_background ?? ""));
1639
1834
  const blocks = lines.flatMap((line) => line?.blocks ?? []);
1640
- const launched = blocks.flatMap((block) => launched_ids(block));
1641
- return launched.filter((id) => id !== "" && !finished.has(id));
1835
+ const launched2 = blocks.flatMap((block) => launched_ids(block));
1836
+ return launched2.filter((id) => id !== "" && !finished.has(id));
1642
1837
  }
1643
1838
  function pending_background_ids(tail) {
1644
1839
  return unfinished_ids(tail, (block) => [block.background_id, block.agent_id]);
@@ -1646,7 +1841,7 @@ function pending_background_ids(tail) {
1646
1841
  function pending_agent_ids(tail) {
1647
1842
  return unfinished_ids(tail, (block) => [block.agent_id]);
1648
1843
  }
1649
- var LANE_BACKGROUND_REASON = '\u26D4 lane background: this session is a dispatched lane child (`JOSH_LANE_CHILD`), a headless `claude -p` process whose background Bash tasks are killed when the turn ends \u2014 so a backgrounded `josh gate` / `josh git` / `josh followup` never re-invokes you; it dies at `exit code 143` (joshuafolkken/kit#2704, measured on the #2606 child: implementation, review and live verification done, the run parked instead of merged). Hand the gate-to-merge region to the detached supervisor instead: `pnpm josh ship --detach --review "<title> #<N>"` (add `--cite <N>`), then end the turn on `launched` / `busy` \u2014 `.claude/skills/workflow-commands/chain-rule.md` step 0 is the single source. A single check you must read before continuing runs in the foreground within the tool timeout; the commit-push step does not \u2014 in a child it too belongs to the detached ship, in either spelling. This rule fires on every occurrence, not once per run.';
1844
+ var LANE_BACKGROUND_REASON = '\u26D4 lane background: this session is a dispatched lane child (`JOSH_LANE_CHILD`), a headless `claude -p` process whose background Bash tasks are killed when the turn ends \u2014 so a backgrounded `josh gate` / `josh git` / `josh followup` / `josh ship` never re-invokes you; it dies at `exit code 143` (joshuafolkken/kit#2704, measured on the #2606 child: implementation, review and live verification done, the run parked instead of merged). Hand the gate-to-merge region to the detached supervisor instead: `pnpm josh ship --detach --review "<title> #<N>"` (add `--cite <N>`) issued in the foreground \u2014 its preflight runs in this process before the supervisor exists, so a backgrounded `ship` dies with the turn (joshuafolkken/kit#3027) \u2014 then end the turn on `launched` / `busy`. `.claude/skills/workflow-commands/chain-rule.md` step 0 is the single source. A single check you must read before continuing runs in the foreground within the tool timeout; the commit-push step does not \u2014 in a child it too belongs to the detached ship, in either spelling. This rule fires on every occurrence, not once per run.';
1650
1845
  var LANE_BACKGROUND_STOP_REASON = "\u26D4 lane background stop: this is a dispatched lane child (`JOSH_LANE_CHILD`) and a background task it launched has not finished \u2014 ending this headless turn kills it, and nothing re-invokes you afterwards (joshuafolkken/kit#2704). Do not send a notification and do not stop: wait for the task in the foreground and act on its result, or re-issue the work through `pnpm josh ship --detach`, whose supervisor outlives the turn (`.claude/skills/workflow-commands/chain-rule.md` step 0). Do not repeat your previous reply.";
1651
1846
  var ROW2 = {
1652
1847
  id: "lane-background",
@@ -1663,1713 +1858,2427 @@ var lane_background = {
1663
1858
  pending_background_ids
1664
1859
  };
1665
1860
 
1666
- // scripts/git/issue-labels.ts
1667
- var EPIC_LABEL = "epic";
1668
- var ENHANCEMENT_LABEL = "enhancement";
1669
- var BREAKING_CHANGE_LABEL = "breaking-change";
1670
- var IN_PROGRESS_LABEL = "in-progress";
1671
- var NEEDS_DECISION_LABEL = "needs-decision";
1672
- var RUN_SOLO_LABEL = "run:solo";
1673
- var RUN_LANE_LABEL = "run:lane";
1674
- var NEEDS_HUMAN_REVIEW_LABEL = "needs-human-review";
1675
- var ALREADY_DONE_LABEL = "already-done";
1676
- var INTERRUPT_ROUTE_LABEL = "route:interrupt";
1677
- var REVIEW_CAP_ROUTE_LABEL = "route:review-cap";
1678
- var SPLIT_ROUTE_LABEL = "route:split";
1679
- var TIER_A_ROUTE_LABEL = "route:tier-a";
1680
- var FILING_ROUTE_LABELS = [
1681
- {
1682
- name: INTERRUPT_ROUTE_LABEL,
1683
- // Not `5319e7`, which is the `epic` label's purple: in a listing an interrupt would render as
1684
- // an epic, and the two are read at a glance rather than by name.
1685
- color: "1d76db",
1686
- // Keyed to the tests rather than to severity. "A serious defect" is the self-assessment
1687
- // `wip-cap.md` bans as a criterion, and this string is what a person reads in `gh label list`.
1688
- description: "Filed past the WIP cap \u2014 meets one of the three interrupt tests (wip-cap.md)"
1689
- },
1690
- {
1691
- name: REVIEW_CAP_ROUTE_LABEL,
1692
- color: "eab308",
1693
- description: "Filed by the review round cap's carry-forward (prompts/review.md \u2192 Review round cap)"
1694
- },
1695
- {
1696
- name: SPLIT_ROUTE_LABEL,
1697
- color: "0e8a16",
1698
- description: "A child issue created by a split (split-assessment.md)"
1699
- },
1700
- {
1701
- name: TIER_A_ROUTE_LABEL,
1702
- color: "d93f0b",
1703
- description: "Filed Tier A during implementation \u2014 an upstream defect or a prerequisite"
1704
- }
1705
- ];
1706
- var DEPTH_0_LABEL = "depth:0";
1707
- var DEPTH_1_LABEL = "depth:1";
1708
- var DEPTH_2_LABEL = "depth:2";
1709
- var DEPTH_LABELS = [
1710
- {
1711
- name: DEPTH_0_LABEL,
1712
- color: "0e8a16",
1713
- description: "Depth 0 \u2014 what a consumer of this package touches (SKILL.md \xA72i)"
1714
- },
1715
- {
1716
- name: DEPTH_1_LABEL,
1717
- color: "fbc02d",
1718
- description: "Depth 1 \u2014 the run orchestration that executes an Issue (SKILL.md \xA72i)"
1719
- },
1720
- {
1721
- name: DEPTH_2_LABEL,
1722
- color: "c5def5",
1723
- description: "Depth 2 \u2014 what measures a run (SKILL.md \xA72i)"
1861
+ // scripts/josh/process-identity.ts
1862
+ import { execFileSync as execFileSync2, spawnSync } from "node:child_process";
1863
+ import { randomUUID } from "node:crypto";
1864
+ import { readFileSync as readFileSync2, rmSync } from "node:fs";
1865
+ import { createServer } from "node:net";
1866
+ import path from "node:path";
1867
+ var PS_COMMAND = "/bin/ps";
1868
+ var FORMAT_FLAG = "-o";
1869
+ var START_TIME_FORMAT = "lstart=";
1870
+ var PID_FLAG = "-p";
1871
+ var PROBE_ENVIRONMENT = { TZ: "UTC", LC_ALL: "C" };
1872
+ var PROBE_TIMEOUT_MS2 = 2e3;
1873
+ var LIVENESS_SIGNAL = 0;
1874
+ var LOWEST_REAL_PID = 1;
1875
+ var PROC_START_FIELD_INDEX = 19;
1876
+ var BEACON_SCHEME = "socket:";
1877
+ var BEACON_SUFFIX = ".sock";
1878
+ var BEACON_PREFIX = `josh-process-identity-${String(process.getuid?.() ?? "")}-`;
1879
+ var BEACON_ID_LENGTH = 36;
1880
+ var BEACON_ID_PATTERN = /^[\da-f-]+$/u;
1881
+ var WINDOWS_PLATFORM = "win32";
1882
+ var SOCKET_PROBE_SOURCE = "const net=require('node:net');const socket=net.createConnection(process.argv[1]);socket.once('connect',()=>process.exit(0));socket.once('error',()=>process.exit(1));socket.setTimeout(1000,()=>process.exit(1));";
1883
+ var beacons = /* @__PURE__ */ new Map();
1884
+ var exit_cleanup = { is_registered: false };
1885
+ function is_live_pid(pid) {
1886
+ if (pid < LOWEST_REAL_PID) return false;
1887
+ try {
1888
+ process.kill(pid, LIVENESS_SIGNAL);
1889
+ return true;
1890
+ } catch {
1891
+ return false;
1724
1892
  }
1725
- ];
1726
- var WORKFLOW_LABELS = [
1727
- {
1728
- name: EPIC_LABEL,
1729
- color: "5319e7",
1730
- description: "Tracks a batch of child issues from one split"
1731
- },
1732
- { name: IN_PROGRESS_LABEL, color: "0075ca", description: "Work is actively in progress" },
1733
- {
1734
- name: RUN_SOLO_LABEL,
1735
- color: "b60205",
1736
- description: "Runs alone in a backlogrun: nothing starts beside it"
1737
- },
1738
- {
1739
- name: RUN_LANE_LABEL,
1740
- color: "0e8a16",
1741
- description: "Judged safe to run beside others in a backlogrun"
1742
- },
1743
- ...FILING_ROUTE_LABELS,
1744
- ...DEPTH_LABELS
1745
- ];
1746
- var BUGFIX_LABEL = "bugfix";
1747
- var OTHER_CHANGE_LABEL = "other-change";
1748
- var IGNORE_FOR_RELEASE_LABEL = "ignore-for-release";
1749
- var RELEASE_CLASSIFICATION_NAMES = [
1750
- BREAKING_CHANGE_LABEL,
1751
- ENHANCEMENT_LABEL,
1752
- BUGFIX_LABEL,
1753
- OTHER_CHANGE_LABEL,
1754
- IGNORE_FOR_RELEASE_LABEL
1755
- ];
1756
- var RELEASE_CLASSIFICATION_METADATA = {
1757
- [BREAKING_CHANGE_LABEL]: { color: "d93f0b", description: "Release: a breaking change (major)" },
1758
- [ENHANCEMENT_LABEL]: { color: "84b6eb", description: "Release: a new feature (minor)" },
1759
- [BUGFIX_LABEL]: { color: "d73a4a", description: "Release: a bug fix (patch)" },
1760
- [OTHER_CHANGE_LABEL]: { color: "c2e0c6", description: "Release: any other change (patch)" },
1761
- [IGNORE_FOR_RELEASE_LABEL]: { color: "cfd3d7", description: "Release: left out of the notes" }
1762
- };
1763
- var RELEASE_CLASSIFICATION_LABELS = RELEASE_CLASSIFICATION_NAMES.map((name) => ({ name, ...RELEASE_CLASSIFICATION_METADATA[name] }));
1764
- var REPOSITORY_LABELS = [
1765
- ...WORKFLOW_LABELS,
1766
- ...RELEASE_CLASSIFICATION_LABELS
1767
- ];
1768
- function has_any_label(labels, wanted) {
1769
- return (labels ?? []).some((label) => wanted.has(label.name.toLowerCase()));
1770
- }
1771
- function label_name_of(labels, wanted) {
1772
- const target = wanted.toLowerCase();
1773
- return labels.find((label) => label.toLowerCase() === target);
1774
- }
1775
- function has_label_name(labels, wanted) {
1776
- return label_name_of(labels, wanted) !== void 0;
1777
1893
  }
1778
-
1779
- // scripts/rules/lane-park.ts
1780
- var NOTIFY_COMMANDS = /* @__PURE__ */ new Set(["notify"]);
1781
- var CONFIRMATION_TASK_TYPE = /(?:^|\s)--task-type[=\s]+["']?confirmation\b/u;
1782
- function is_confirmation_notify(command) {
1783
- return shell_segments.segments_of(command).some(
1784
- (segment) => shell_segments.is_josh_command(segment, NOTIFY_COMMANDS) && CONFIRMATION_TASK_TYPE.test(segment)
1785
- );
1894
+ function read_proc_start(pid) {
1895
+ try {
1896
+ const stat = readFileSync2(`/proc/${String(pid)}/stat`, "utf8");
1897
+ const fields = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/u);
1898
+ const start = fields[PROC_START_FIELD_INDEX];
1899
+ return start === void 0 ? void 0 : `proc:${start}`;
1900
+ } catch {
1901
+ return void 0;
1902
+ }
1786
1903
  }
1787
- var NOTIFY_INVOCATION = /josh\s+(?:notify|nf)\b/u;
1788
- function mentions_confirmation_notify(text) {
1789
- return NOTIFY_INVOCATION.test(text) && CONFIRMATION_TASK_TYPE.test(text);
1904
+ function read_ps_start(pid) {
1905
+ try {
1906
+ const printed = execFileSync2(
1907
+ PS_COMMAND,
1908
+ [FORMAT_FLAG, START_TIME_FORMAT, PID_FLAG, String(pid)],
1909
+ {
1910
+ encoding: "utf8",
1911
+ timeout: PROBE_TIMEOUT_MS2,
1912
+ stdio: ["ignore", "pipe", "ignore"],
1913
+ env: PROBE_ENVIRONMENT
1914
+ }
1915
+ ).trim();
1916
+ return printed.length > 0 ? printed : void 0;
1917
+ } catch {
1918
+ return void 0;
1919
+ }
1790
1920
  }
1791
- function is_unparked_stop(command, is_lane_child = lane_child_marker.is_child_of(process.cwd())) {
1792
- if (!is_confirmation_notify(command)) return false;
1793
- return is_lane_child;
1921
+ var START_PROBES = { read_proc_start, read_ps_start };
1922
+ function safe_probe(probe, pid) {
1923
+ try {
1924
+ return probe(pid);
1925
+ } catch {
1926
+ return void 0;
1927
+ }
1794
1928
  }
1795
- var APPLIES_NEEDS_DECISION = new RegExp(
1796
- String.raw`(?:labels\[\]=|--add-label[= ]'?)${NEEDS_DECISION_LABEL}\b`,
1797
- "u"
1798
- );
1799
- function records_the_park(command) {
1800
- return APPLIES_NEEDS_DECISION.test(command);
1929
+ function read_start(pid, probes = START_PROBES) {
1930
+ return safe_probe(probes.read_proc_start, pid) ?? safe_probe(probes.read_ps_start, pid);
1801
1931
  }
1802
- var LANE_PARK_REASON = '\u26D4 lane child park: this session is a dispatched lane child, and a stop that waits on a person must leave the question on the Issue before it sends this Telegram \u2014 otherwise the parent finds an OPEN Issue with `in-progress` and no question, and a person cannot tell what to answer (joshuafolkken/kit#2034 measured this twice in one run). The child is the only process that knows the question, the options and whether it stashed, so it records the park itself rather than leaving the parent to guess from a log. Before you notify and stop: apply `needs-decision` and post a comment carrying the question, the options, and whether work was stashed \u2014 `gh api repos/{owner}/{repo}/issues/<N>/labels -f \'labels[]=needs-decision\'` then `pnpm josh issue:comment <N> --body-file <path>`. If this stop is a `needs-human-review` or `already-done` one, that label is already on the Issue and there is nothing to add. Then reissue this notify \u2014 it fires once per run and cannot repeat on the call in hand. The procedure is `.claude/skills/workflow-commands/pre-gate-cut.md` \u2192 "A lane child records its park before it stops", and the park itself is `backlogrun-park.md` \u2192 "park and continue".';
1803
- var lane_park = {
1804
- LANE_PARK_REASON,
1805
- is_confirmation_notify,
1806
- is_unparked_stop,
1807
- mentions_confirmation_notify,
1808
- records_the_park
1809
- };
1810
-
1811
- // scripts/discovery/repo-discovery.ts
1812
- import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
1813
- import path from "node:path";
1814
-
1815
- // scripts/discovery/repo-origin.ts
1816
- var GITHUB_HOST = "github.com";
1817
- var SSH_ALIAS_PATTERN = /^github(?:-\w+)*$/u;
1818
- var GIT_SUFFIX = ".git";
1819
- var PATH_SEPARATOR2 = "/";
1820
- var USER_SEPARATOR = "@";
1821
- var HOST_SEPARATOR = ":";
1822
- var OWNER_INDEX = 0;
1823
- var REPO_INDEX = 1;
1824
- var REPO_PATH_SEGMENTS = 2;
1825
- var NOT_FOUND = -1;
1826
- var SECTION_PREFIX = "[";
1827
- var ORIGIN_SECTION = /^\[remote\s+"origin"\]$/u;
1828
- var URL_KEY = "url";
1829
- function is_github_hostname(host) {
1830
- return host.toLowerCase() === GITHUB_HOST;
1932
+ function close_beacon(token) {
1933
+ const server = beacons.get(token);
1934
+ if (server === void 0) return;
1935
+ beacons.delete(token);
1936
+ server.close();
1937
+ rmSync(token.slice(BEACON_SCHEME.length), { force: true });
1831
1938
  }
1832
- function is_github_ssh_host(host) {
1833
- const normalized = host.toLowerCase();
1834
- return is_github_hostname(normalized) || SSH_ALIAS_PATTERN.test(normalized);
1939
+ function close_beacons() {
1940
+ for (const token of beacons.keys()) close_beacon(token);
1835
1941
  }
1836
- function strip_git_suffix(name) {
1837
- return name.endsWith(GIT_SUFFIX) ? name.slice(0, -GIT_SUFFIX.length) : name;
1942
+ function ensure_exit_cleanup() {
1943
+ if (exit_cleanup.is_registered) return;
1944
+ exit_cleanup.is_registered = true;
1945
+ process.once("exit", close_beacons);
1838
1946
  }
1839
- function to_identity(repository_path) {
1840
- const segments = repository_path.split(PATH_SEPARATOR2).filter((segment) => segment !== "");
1841
- if (segments.length !== REPO_PATH_SEGMENTS) return void 0;
1842
- const owner = segments[OWNER_INDEX] ?? "";
1843
- const repo = strip_git_suffix(segments[REPO_INDEX] ?? "");
1844
- return repo === "" ? void 0 : { owner, repo };
1947
+ function ignore_beacon_error() {
1845
1948
  }
1846
- function parse_scp_like(url) {
1847
- const user_end = url.indexOf(USER_SEPARATOR);
1848
- const authority = user_end === NOT_FOUND ? url : url.slice(user_end + 1);
1849
- const host_end = authority.indexOf(HOST_SEPARATOR);
1850
- if (host_end === NOT_FOUND) return void 0;
1851
- if (!is_github_ssh_host(authority.slice(0, host_end))) return void 0;
1852
- return to_identity(authority.slice(host_end + 1));
1949
+ function bind_beacon(target) {
1950
+ return createServer().on("error", ignore_beacon_error).listen(target);
1853
1951
  }
1854
- function parse_url_like(url) {
1952
+ function try_bind_beacon(bind, target) {
1855
1953
  try {
1856
- const parsed = new URL(url);
1857
- return is_github_hostname(parsed.hostname) ? to_identity(parsed.pathname) : void 0;
1954
+ const server = bind(target);
1955
+ return server.listening ? server : void 0;
1858
1956
  } catch {
1859
1957
  return void 0;
1860
1958
  }
1861
1959
  }
1862
- function parse_origin_url(url) {
1863
- const trimmed2 = url.trim();
1864
- if (trimmed2 === "") return void 0;
1865
- return parse_url_like(trimmed2) ?? parse_scp_like(trimmed2);
1960
+ function open_beacon(bind = bind_beacon) {
1961
+ const target = path.join(PLATFORM_TEMP_ROOT, `${BEACON_PREFIX}${randomUUID()}${BEACON_SUFFIX}`);
1962
+ const token = `${BEACON_SCHEME}${target}`;
1963
+ const server = try_bind_beacon(bind, target);
1964
+ if (server === void 0) return void 0;
1965
+ server.unref();
1966
+ beacons.set(token, server);
1967
+ ensure_exit_cleanup();
1968
+ return token;
1866
1969
  }
1867
- function is_section_header(line) {
1868
- return line.startsWith(SECTION_PREFIX);
1970
+ function resolve_own_start(probes = START_PROBES, platform2 = process.platform) {
1971
+ const start = read_start(process.pid, probes);
1972
+ if (start !== void 0 || platform2 === WINDOWS_PLATFORM) return start;
1973
+ return open_beacon();
1869
1974
  }
1870
- function origin_section_lines(lines) {
1871
- const start = lines.findIndex((line) => ORIGIN_SECTION.test(line));
1872
- if (start === NOT_FOUND) return [];
1873
- const rest = lines.slice(start + 1);
1874
- const end = rest.findIndex((line) => is_section_header(line));
1875
- return end === NOT_FOUND ? rest : rest.slice(0, end);
1975
+ function is_valid_beacon_name(name) {
1976
+ if (!name.startsWith(BEACON_PREFIX) || !name.endsWith(BEACON_SUFFIX)) return false;
1977
+ const id = name.slice(BEACON_PREFIX.length, -BEACON_SUFFIX.length);
1978
+ return id.length === BEACON_ID_LENGTH && BEACON_ID_PATTERN.test(id);
1876
1979
  }
1877
- function entry_value(line, key) {
1878
- const separator = line.indexOf("=");
1879
- if (separator === NOT_FOUND) return void 0;
1880
- if (line.slice(0, separator).trim() !== key) return void 0;
1881
- return line.slice(separator + 1).trim();
1980
+ function beacon_target(token) {
1981
+ if (!token.startsWith(BEACON_SCHEME)) return void 0;
1982
+ const target = token.slice(BEACON_SCHEME.length);
1983
+ if (path.dirname(target) !== PLATFORM_TEMP_ROOT) return void 0;
1984
+ return is_valid_beacon_name(path.basename(target)) ? target : void 0;
1882
1985
  }
1883
- function parse_origin_from_config(content) {
1884
- const lines = content.split("\n").map((line) => line.trim());
1885
- return origin_section_lines(lines).map((line) => entry_value(line, URL_KEY)).find((url) => url !== void 0);
1986
+ function is_live_beacon(target) {
1987
+ return spawnSync(process.execPath, ["-e", SOCKET_PROBE_SOURCE, target], {
1988
+ stdio: "ignore",
1989
+ timeout: PROBE_TIMEOUT_MS2
1990
+ }).status === 0;
1886
1991
  }
1887
- function format_identity(identity) {
1888
- return `${identity.owner}${PATH_SEPARATOR2}${identity.repo}`.toLowerCase();
1992
+ function has_matching_start(pid, recorded_start, read2) {
1993
+ const observed = read2(pid);
1994
+ return observed === void 0 ? void 0 : observed === recorded_start;
1889
1995
  }
1890
- var repo_origin = {
1891
- GITHUB_HOST,
1892
- is_github_hostname,
1893
- is_github_ssh_host,
1894
- parse_origin_from_config,
1895
- parse_origin_url,
1896
- format_identity
1897
- };
1898
-
1899
- // scripts/discovery/repo-map-logic.ts
1900
- var OVERRIDE_ENTRY_SEPARATOR = ",";
1901
- var OVERRIDE_PAIR_SEPARATOR = "=";
1902
- var OVERRIDE_PAIR_PARTS = 2;
1903
- var KEY_INDEX = 0;
1904
- var PATH_INDEX = 1;
1905
- function is_same_owner(identity, current_owner2) {
1906
- return identity.owner.toLowerCase() === current_owner2.toLowerCase();
1996
+ function matches_recorded_start(pid, recorded_start, read2) {
1997
+ const target = beacon_target(recorded_start);
1998
+ return target === void 0 ? has_matching_start(pid, recorded_start, read2) : is_live_beacon(target);
1907
1999
  }
1908
- function add_entry(target, origin_url, repository_path, current_owner2) {
1909
- if (origin_url === void 0) return;
1910
- const identity = repo_origin.parse_origin_url(origin_url);
1911
- if (identity === void 0 || !is_same_owner(identity, current_owner2)) return;
1912
- target.set(repo_origin.format_identity(identity), repository_path);
2000
+ var own_probe = /* @__PURE__ */ new Map();
2001
+ function own_start() {
2002
+ if (!own_probe.has(process.pid)) own_probe.set(process.pid, resolve_own_start());
2003
+ return own_probe.get(process.pid);
1913
2004
  }
1914
- function parse_override_entry(entry) {
1915
- const parts = entry.split(OVERRIDE_PAIR_SEPARATOR).map((part) => part.trim());
1916
- if (parts.length !== OVERRIDE_PAIR_PARTS) return void 0;
1917
- const key = parts[KEY_INDEX] ?? "";
1918
- const repository_path = parts[PATH_INDEX] ?? "";
1919
- if (repository_path === "") return void 0;
1920
- return { path: repository_path, origin_url: `https://${repo_origin.GITHUB_HOST}/${key}` };
2005
+ function own_fields() {
2006
+ const start = own_start();
2007
+ return { pid: process.pid, ...start !== void 0 && { process_start: start } };
1921
2008
  }
1922
- function parse_overrides(raw) {
1923
- if (raw === void 0) return [];
1924
- return raw.split(OVERRIDE_ENTRY_SEPARATOR).map((entry) => parse_override_entry(entry)).filter((entry) => entry !== void 0);
2009
+ function is_same_process(pid, recorded_start, read2 = read_start) {
2010
+ if (pid === void 0 || !is_live_pid(pid)) return false;
2011
+ if (recorded_start === void 0) return void 0;
2012
+ return matches_recorded_start(pid, recorded_start, read2);
1925
2013
  }
1926
- function build_repository_map(discovered, current_owner2, override_value2) {
1927
- const result = /* @__PURE__ */ new Map();
1928
- for (const entry of discovered) add_entry(result, entry.origin_url, entry.path, current_owner2);
1929
- for (const entry of parse_overrides(override_value2)) {
1930
- add_entry(result, entry.origin_url, entry.path, current_owner2);
1931
- }
1932
- return result;
2014
+ function is_own_process(pid, recorded_start) {
2015
+ return pid === process.pid && recorded_start === own_start();
1933
2016
  }
1934
- var repo_map_logic = {
1935
- is_same_owner,
1936
- parse_overrides,
1937
- build_repository_map
2017
+ var process_identity = {
2018
+ close_beacon,
2019
+ is_live_pid,
2020
+ is_own_process,
2021
+ is_same_process,
2022
+ open_beacon,
2023
+ own_fields,
2024
+ own_start,
2025
+ read_start,
2026
+ resolve_own_start
1938
2027
  };
1939
2028
 
1940
- // scripts/discovery/repo-discovery.ts
1941
- var GIT_DIRECTORY = ".git";
1942
- var GIT_CONFIG = "config";
1943
- var GITDIR_PREFIX = "gitdir:";
1944
- var COMMON_DIRECTORY_FILE = "commondir";
1945
- var OVERRIDE_ENV_KEY = "JOSH_REPO_PATHS";
1946
- function resolve_common_directory(git_directory) {
1947
- const pointer = path.join(git_directory, COMMON_DIRECTORY_FILE);
1948
- if (!existsSync(pointer)) return git_directory;
1949
- try {
1950
- return path.resolve(git_directory, readFileSync2(pointer, "utf8").trim());
1951
- } catch {
1952
- return git_directory;
1953
- }
2029
+ // scripts/run/run-carry.ts
2030
+ import { z as z6 } from "zod";
2031
+
2032
+ // scripts/run/run-carry-streak.ts
2033
+ var NO_INCREMENT = 0;
2034
+ var OUTAGE_FOLD_WINDOW_MS = 12e4;
2035
+ function has_increment(value) {
2036
+ return (value ?? NO_INCREMENT) > NO_INCREMENT;
1954
2037
  }
1955
- function follow_git_file(repository_path, git_file) {
1956
- const pointer = readFileSync2(git_file, "utf8").trim();
1957
- if (!pointer.startsWith(GITDIR_PREFIX)) return void 0;
1958
- return path.resolve(repository_path, pointer.slice(GITDIR_PREFIX.length).trim());
2038
+ function next_failures(carry, change) {
2039
+ if (has_increment(change.merged)) return NO_INCREMENT;
2040
+ return carry.failures + (change.failures ?? NO_INCREMENT);
1959
2041
  }
1960
- function resolve_git_directory(repository_path) {
1961
- const candidate = path.join(repository_path, GIT_DIRECTORY);
1962
- if (!existsSync(candidate)) return void 0;
1963
- try {
1964
- if (statSync(candidate).isDirectory()) return candidate;
1965
- const linked = follow_git_file(repository_path, candidate);
1966
- return linked === void 0 ? void 0 : resolve_common_directory(linked);
1967
- } catch {
1968
- return void 0;
1969
- }
2042
+ function is_within_fold_window(last_outage_at, now) {
2043
+ if (last_outage_at === void 0) return false;
2044
+ const last = Date.parse(last_outage_at);
2045
+ if (Number.isNaN(last)) return false;
2046
+ return now.getTime() - last <= OUTAGE_FOLD_WINDOW_MS;
1970
2047
  }
1971
- function read_origin_url(repository_path) {
1972
- const git_directory = resolve_git_directory(repository_path);
1973
- if (git_directory === void 0) return void 0;
1974
- try {
1975
- return repo_origin.parse_origin_from_config(
1976
- readFileSync2(path.join(git_directory, GIT_CONFIG), "utf8")
1977
- );
1978
- } catch {
1979
- return void 0;
1980
- }
2048
+ function folded_outage(carry) {
2049
+ return { outages: carry.outages, last_outage_at: carry.last_outage_at };
1981
2050
  }
1982
- function sibling_directories(parent) {
1983
- try {
1984
- return readdirSync(parent, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path.join(parent, entry.name)).toSorted((left, right) => left.localeCompare(right));
1985
- } catch {
1986
- return [];
1987
- }
2051
+ function counted_outage(carry, change, now) {
2052
+ const outages = carry.outages + (change.outages ?? NO_INCREMENT);
2053
+ return { outages, last_outage_at: now.toISOString() };
1988
2054
  }
1989
- function scan_siblings(parent) {
1990
- return sibling_directories(parent).map((repository_path) => ({
1991
- path: repository_path,
1992
- origin_url: read_origin_url(repository_path)
1993
- }));
2055
+ function next_outage(carry, change, now) {
2056
+ if (has_increment(change.outages)) {
2057
+ return is_within_fold_window(carry.last_outage_at, now) ? folded_outage(carry) : counted_outage(carry, change, now);
2058
+ }
2059
+ const is_reachable = has_increment(change.merged) || has_increment(change.failures);
2060
+ return is_reachable ? { outages: NO_INCREMENT, last_outage_at: void 0 } : folded_outage(carry);
1994
2061
  }
1995
- function resolve_current_owner(repository_path) {
1996
- const origin_url = read_origin_url(repository_path);
1997
- if (origin_url === void 0) return void 0;
1998
- return repo_origin.parse_origin_url(origin_url)?.owner;
2062
+ function next_state(carry, change, now) {
2063
+ const outage = next_outage(carry, change, now);
2064
+ return {
2065
+ failures: next_failures(carry, change),
2066
+ outages: outage.outages,
2067
+ last_outage_at: outage.last_outage_at
2068
+ };
1999
2069
  }
2000
- function main_worktree(repository_path) {
2001
- const git_directory = resolve_git_directory(repository_path);
2002
- if (git_directory === void 0) return repository_path;
2003
- if (path.basename(git_directory) !== GIT_DIRECTORY) return repository_path;
2004
- return path.dirname(git_directory);
2070
+ var run_carry_streak = { next_state };
2071
+
2072
+ // scripts/run/run-carry-change.ts
2073
+ var NO_INCREMENT2 = 0;
2074
+ function next_done(carry, done) {
2075
+ if (done === void 0) return carry.done;
2076
+ const current = carry.done ?? [];
2077
+ return current.includes(done) ? current : [...current, done];
2005
2078
  }
2006
- function discover_repositories(repository_path, environment = process.env) {
2007
- const main2 = main_worktree(repository_path);
2008
- const current_owner2 = resolve_current_owner(main2);
2009
- if (current_owner2 === void 0) return /* @__PURE__ */ new Map();
2010
- return repo_map_logic.build_repository_map(
2011
- scan_siblings(path.dirname(main2)),
2012
- current_owner2,
2013
- environment[OVERRIDE_ENV_KEY]
2014
- );
2079
+ function next_merged_issues(carry, issue) {
2080
+ if (issue === void 0) return carry.merged_issues;
2081
+ return [...carry.merged_issues ?? [], issue];
2015
2082
  }
2016
- var repo_discovery = {
2017
- OVERRIDE_ENV_KEY,
2018
- main_worktree,
2019
- read_origin_url,
2020
- resolve_current_owner,
2021
- scan_siblings,
2022
- discover_repositories
2023
- };
2024
-
2025
- // scripts/discovery/repo-party.ts
2026
- var FIRST_PARTY = "first-party";
2027
- var THIRD_PARTY = "third-party";
2028
- var UNKNOWN = "unknown";
2029
- function classify3(session_owner, target) {
2030
- if (session_owner === void 0 || target === void 0) return UNKNOWN;
2031
- return session_owner.toLowerCase() === target.toLowerCase() ? FIRST_PARTY : THIRD_PARTY;
2083
+ function is_duplicate_merge(carry, change) {
2084
+ const issue = change.merged_issue;
2085
+ return issue !== void 0 && carry.merged_issues?.includes(issue) === true;
2032
2086
  }
2033
- function target_owner(owner_repo) {
2034
- return repo_origin.parse_origin_url(`https://${repo_origin.GITHUB_HOST}/${owner_repo}`)?.owner;
2087
+ function next_counts(carry, change) {
2088
+ const cuts = change.cuts ?? NO_INCREMENT2;
2089
+ return {
2090
+ merged: carry.merged + (change.merged ?? NO_INCREMENT2),
2091
+ filed: carry.filed + (change.filed ?? NO_INCREMENT2),
2092
+ cuts: carry.cuts + cuts,
2093
+ is_handed_off: cuts > NO_INCREMENT2
2094
+ };
2035
2095
  }
2036
- function current_owner(repository_path = process.cwd()) {
2037
- return repo_discovery.resolve_current_owner(repository_path);
2096
+ function apply_change(target, carry, change, now = /* @__PURE__ */ new Date()) {
2097
+ if (is_duplicate_merge(carry, change)) return carry;
2098
+ const streak = run_carry_streak.next_state(carry, change, now);
2099
+ const next = {
2100
+ ...carry,
2101
+ ...next_counts(carry, change),
2102
+ merged_issues: next_merged_issues(carry, change.merged_issue),
2103
+ failures: streak.failures,
2104
+ outages: streak.outages,
2105
+ last_outage_at: streak.last_outage_at,
2106
+ done: next_done(carry, change.done),
2107
+ retrospective: change.retrospective === true || carry.retrospective
2108
+ };
2109
+ stamp_file.write_stamp(target, next);
2110
+ return next;
2038
2111
  }
2039
- var repo_party = { FIRST_PARTY, THIRD_PARTY, UNKNOWN, classify: classify3, current_owner, target_owner };
2112
+ var run_carry_change = { apply_change };
2040
2113
 
2041
- // scripts/lane/lane-registry.ts
2042
- import { existsSync as existsSync2 } from "node:fs";
2043
- import path3 from "node:path";
2114
+ // scripts/backlog/backlog-budget-cli.ts
2115
+ import { fileURLToPath } from "node:url";
2044
2116
 
2045
- // scripts/git/git-ls-remote.ts
2046
- var LS_REMOTE_COMMAND = "ls-remote";
2047
- var HEADS_FLAG = "--heads";
2048
- var ORIGIN_REMOTE = "origin";
2049
- var HEADS_REF_PREFIX = "refs/heads/";
2050
- function ls_remote_branch_arguments(branch_name) {
2051
- return [LS_REMOTE_COMMAND, HEADS_FLAG, ORIGIN_REMOTE, `${HEADS_REF_PREFIX}${branch_name}`];
2117
+ // scripts/git/changed-paths.ts
2118
+ function to_paths(raw) {
2119
+ return raw.split("\n").map((line) => line.trim()).filter((line) => line !== "");
2052
2120
  }
2121
+ async function read_changed_paths(is_staged) {
2122
+ if (is_staged) return to_paths(await git_command.diff_cached_names());
2123
+ const tracked = to_paths(await git_command.diff_main_names());
2124
+ const untracked = to_paths(await git_command.untracked_names());
2125
+ return [...tracked, ...untracked];
2126
+ }
2127
+ var changed_paths = { read_changed_paths, to_paths };
2053
2128
 
2054
- // scripts/git/repository-lock.ts
2055
- import { randomUUID as randomUUID2 } from "node:crypto";
2056
- import { linkSync, renameSync, rmSync as rmSync2 } from "node:fs";
2057
- import { setTimeout as sleep } from "node:timers/promises";
2058
-
2059
- // scripts/josh/process-identity.ts
2060
- import { execFileSync as execFileSync2, spawnSync } from "node:child_process";
2061
- import { randomUUID } from "node:crypto";
2062
- import { readFileSync as readFileSync3, rmSync } from "node:fs";
2063
- import { createServer } from "node:net";
2064
- import path2 from "node:path";
2065
- var PS_COMMAND = "/bin/ps";
2066
- var FORMAT_FLAG = "-o";
2067
- var START_TIME_FORMAT = "lstart=";
2068
- var PID_FLAG = "-p";
2069
- var PROBE_ENVIRONMENT = { TZ: "UTC", LC_ALL: "C" };
2070
- var PROBE_TIMEOUT_MS = 2e3;
2071
- var LIVENESS_SIGNAL = 0;
2072
- var LOWEST_REAL_PID = 1;
2073
- var PROC_START_FIELD_INDEX = 19;
2074
- var BEACON_SCHEME = "socket:";
2075
- var BEACON_SUFFIX = ".sock";
2076
- var BEACON_PREFIX = `josh-process-identity-${String(process.getuid?.() ?? "")}-`;
2077
- var BEACON_ID_LENGTH = 36;
2078
- var BEACON_ID_PATTERN = /^[\da-f-]+$/u;
2079
- var WINDOWS_PLATFORM = "win32";
2080
- var SOCKET_PROBE_SOURCE = "const net=require('node:net');const socket=net.createConnection(process.argv[1]);socket.once('connect',()=>process.exit(0));socket.once('error',()=>process.exit(1));socket.setTimeout(1000,()=>process.exit(1));";
2081
- var beacons = /* @__PURE__ */ new Map();
2082
- var exit_cleanup = { is_registered: false };
2083
- function is_live_pid(pid) {
2084
- if (pid < LOWEST_REAL_PID) return false;
2085
- try {
2086
- process.kill(pid, LIVENESS_SIGNAL);
2087
- return true;
2088
- } catch {
2089
- return false;
2090
- }
2129
+ // scripts/josh/path-decision.ts
2130
+ var STAGED_FLAG = "--staged";
2131
+ var JSON_FLAG = "--json";
2132
+ var KNOWN_FLAGS = [STAGED_FLAG, JSON_FLAG];
2133
+ var FAILURE_EXIT_CODE = 1;
2134
+ var MAX_LISTED_PATHS = 5;
2135
+ function has_unknown_flag(argv, known) {
2136
+ return argv.some((argument) => !known.includes(argument));
2091
2137
  }
2092
- function read_proc_start(pid) {
2093
- try {
2094
- const stat = readFileSync3(`/proc/${String(pid)}/stat`, "utf8");
2095
- const fields = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/u);
2096
- const start = fields[PROC_START_FIELD_INDEX];
2097
- return start === void 0 ? void 0 : `proc:${start}`;
2098
- } catch {
2099
- return void 0;
2138
+ function parse_options(argv) {
2139
+ if (has_unknown_flag(argv, KNOWN_FLAGS)) return void 0;
2140
+ return { is_staged: argv.includes(STAGED_FLAG), is_json: argv.includes(JSON_FLAG) };
2141
+ }
2142
+ function format_path_list(paths) {
2143
+ const listed = paths.slice(0, MAX_LISTED_PATHS).join(", ");
2144
+ if (paths.length <= MAX_LISTED_PATHS) return listed;
2145
+ return `${listed}, +${String(paths.length - MAX_LISTED_PATHS)} more`;
2146
+ }
2147
+ function print_decision(key2, answer, reason, is_json) {
2148
+ if (is_json) {
2149
+ console.info(JSON.stringify({ [key2]: answer, reason }));
2150
+ return;
2100
2151
  }
2152
+ console.info(answer);
2153
+ console.error(reason);
2101
2154
  }
2102
- function read_ps_start(pid) {
2103
- try {
2104
- const printed = execFileSync2(
2105
- PS_COMMAND,
2106
- [FORMAT_FLAG, START_TIME_FORMAT, PID_FLAG, String(pid)],
2107
- {
2108
- encoding: "utf8",
2109
- timeout: PROBE_TIMEOUT_MS,
2110
- stdio: ["ignore", "pipe", "ignore"],
2111
- env: PROBE_ENVIRONMENT
2112
- }
2113
- ).trim();
2114
- return printed.length > 0 ? printed : void 0;
2115
- } catch {
2116
- return void 0;
2155
+ async function run_path_decision(argv, command) {
2156
+ const options = parse_options(argv);
2157
+ if (options === void 0) {
2158
+ console.error(command.usage);
2159
+ return FAILURE_EXIT_CODE;
2117
2160
  }
2161
+ const paths = await changed_paths.read_changed_paths(options.is_staged);
2162
+ const answer = command.decide(paths);
2163
+ print_decision(command.key, answer, command.explain(paths, answer), options.is_json);
2164
+ return 0;
2118
2165
  }
2119
- var START_PROBES = { read_proc_start, read_ps_start };
2120
- function safe_probe(probe, pid) {
2166
+ var path_decision = {
2167
+ format_path_list,
2168
+ has_unknown_flag,
2169
+ JSON_FLAG,
2170
+ parse_options,
2171
+ print_decision,
2172
+ run_path_decision
2173
+ };
2174
+
2175
+ // scripts/lib/cli-flags.ts
2176
+ import { parseArgs } from "node:util";
2177
+ function refuse_unknown_flags(argv, known_flags, command) {
2178
+ const unknown = argv.filter((argument) => !known_flags.includes(argument));
2179
+ if (unknown.length === 0) return void 0;
2180
+ const usage = `Usage: josh ${command} [${known_flags.join("] [")}]`;
2181
+ return `Unknown argument(s): ${unknown.join(" ")}
2182
+ ${usage}`;
2183
+ }
2184
+ function parse_or_undefined(config) {
2121
2185
  try {
2122
- return probe(pid);
2186
+ return parseArgs(config);
2123
2187
  } catch {
2124
2188
  return void 0;
2125
2189
  }
2126
2190
  }
2127
- function read_start(pid, probes = START_PROBES) {
2128
- return safe_probe(probes.read_proc_start, pid) ?? safe_probe(probes.read_ps_start, pid);
2129
- }
2130
- function close_beacon(token) {
2131
- const server = beacons.get(token);
2132
- if (server === void 0) return;
2133
- beacons.delete(token);
2134
- server.close();
2135
- rmSync(token.slice(BEACON_SCHEME.length), { force: true });
2136
- }
2137
- function close_beacons() {
2138
- for (const token of beacons.keys()) close_beacon(token);
2139
- }
2140
- function ensure_exit_cleanup() {
2141
- if (exit_cleanup.is_registered) return;
2142
- exit_cleanup.is_registered = true;
2143
- process.once("exit", close_beacons);
2191
+ var cli_flags = { refuse_unknown_flags, parse_or_undefined };
2192
+
2193
+ // scripts/backlog/backlog-budget-cli.ts
2194
+ var ARGV_OFFSET = 2;
2195
+ var SUCCESS_EXIT_CODE = 0;
2196
+ var FAILURE_EXIT_CODE2 = 1;
2197
+ var JSON_KEY = "budget";
2198
+ var USAGE = `Usage: josh backlog:budget --answer <${backlog_budget.ANSWERS.join("|")}> --started <ISO-8601> [--active <ISO-8601>, required unless the watch is off] [--merged <count>] [--running <count>] [--max <count>] [--idle <minutes>, 0 to turn the watch off] [--json]`;
2199
+ var OPTIONS = {
2200
+ active: { type: "string" },
2201
+ answer: { type: "string" },
2202
+ idle: { type: "string" },
2203
+ json: { type: "boolean" },
2204
+ max: { type: "string" },
2205
+ merged: { type: "string" },
2206
+ running: { type: "string" },
2207
+ started: { type: "string" }
2208
+ };
2209
+ function read_arguments(argv) {
2210
+ return cli_flags.parse_or_undefined({ args: [...argv], options: OPTIONS, strict: true })?.values;
2144
2211
  }
2145
- function ignore_beacon_error() {
2212
+ function text_of(value) {
2213
+ return typeof value === "string" ? value : void 0;
2146
2214
  }
2147
- function bind_beacon(target) {
2148
- return createServer().on("error", ignore_beacon_error).listen(target);
2215
+ function to_answer(raw) {
2216
+ return backlog_budget.ANSWERS.find((answer) => answer === raw);
2149
2217
  }
2150
- function try_bind_beacon(bind, target) {
2151
- try {
2152
- const server = bind(target);
2153
- return server.listening ? server : void 0;
2154
- } catch {
2155
- return void 0;
2156
- }
2218
+ function to_time_ms(raw) {
2219
+ if (raw === void 0) return void 0;
2220
+ const parsed = Date.parse(raw);
2221
+ return Number.isNaN(parsed) ? void 0 : parsed;
2157
2222
  }
2158
- function open_beacon(bind = bind_beacon) {
2159
- const target = path2.join(PLATFORM_TEMP_ROOT, `${BEACON_PREFIX}${randomUUID()}${BEACON_SUFFIX}`);
2160
- const token = `${BEACON_SCHEME}${target}`;
2161
- const server = try_bind_beacon(bind, target);
2162
- if (server === void 0) return void 0;
2163
- server.unref();
2164
- beacons.set(token, server);
2165
- ensure_exit_cleanup();
2166
- return token;
2223
+ var COUNT_PATTERN = /^\d+$/u;
2224
+ function to_count(raw) {
2225
+ if (raw === void 0 || !COUNT_PATTERN.test(raw)) return void 0;
2226
+ const parsed = Number(raw);
2227
+ return Number.isSafeInteger(parsed) ? parsed : void 0;
2167
2228
  }
2168
- function resolve_own_start(probes = START_PROBES, platform2 = process.platform) {
2169
- const start = read_start(process.pid, probes);
2170
- if (start !== void 0 || platform2 === WINDOWS_PLATFORM) return start;
2171
- return open_beacon();
2229
+ function is_readable(raw, parsed) {
2230
+ return raw === void 0 || parsed !== void 0;
2172
2231
  }
2173
- function is_valid_beacon_name(name) {
2174
- if (!name.startsWith(BEACON_PREFIX) || !name.endsWith(BEACON_SUFFIX)) return false;
2175
- const id = name.slice(BEACON_PREFIX.length, -BEACON_SUFFIX.length);
2176
- return id.length === BEACON_ID_LENGTH && BEACON_ID_PATTERN.test(id);
2232
+ var COUNT_NAMES = ["idle", "max", "merged", "running"];
2233
+ function counts_of(values) {
2234
+ return {
2235
+ idle: to_count(text_of(values.idle)),
2236
+ max: to_count(text_of(values.max)),
2237
+ merged: to_count(text_of(values.merged)),
2238
+ running: to_count(text_of(values.running))
2239
+ };
2177
2240
  }
2178
- function beacon_target(token) {
2179
- if (!token.startsWith(BEACON_SCHEME)) return void 0;
2180
- const target = token.slice(BEACON_SCHEME.length);
2181
- if (path2.dirname(target) !== PLATFORM_TEMP_ROOT) return void 0;
2182
- return is_valid_beacon_name(path2.basename(target)) ? target : void 0;
2241
+ function are_counts_readable(values, counts) {
2242
+ return COUNT_NAMES.every((name) => is_readable(text_of(values[name]), counts[name]));
2183
2243
  }
2184
- function is_live_beacon(target) {
2185
- return spawnSync(process.execPath, ["-e", SOCKET_PROBE_SOURCE, target], {
2186
- stdio: "ignore",
2187
- timeout: PROBE_TIMEOUT_MS
2188
- }).status === 0;
2244
+ var NO_IDLE_MINUTES = 0;
2245
+ function to_idle_ms(idle_minutes) {
2246
+ if (idle_minutes === void 0) return backlog_budget.DEFAULT_IDLE_MS;
2247
+ if (idle_minutes === NO_IDLE_MINUTES) return void 0;
2248
+ return idle_minutes * backlog_budget.MS_PER_MINUTE;
2189
2249
  }
2190
- function has_matching_start(pid, recorded_start, read2) {
2191
- const observed = read2(pid);
2192
- return observed === void 0 ? void 0 : observed === recorded_start;
2250
+ function is_watch_readable(values) {
2251
+ if (to_idle_ms(to_count(text_of(values.idle))) === void 0) return true;
2252
+ return text_of(values.active) !== void 0;
2193
2253
  }
2194
- function matches_recorded_start(pid, recorded_start, read2) {
2195
- const target = beacon_target(recorded_start);
2196
- return target === void 0 ? has_matching_start(pid, recorded_start, read2) : is_live_beacon(target);
2254
+ function are_values_readable(values) {
2255
+ return is_readable(text_of(values.active), to_time_ms(text_of(values.active))) && is_watch_readable(values) && are_counts_readable(values, counts_of(values));
2197
2256
  }
2198
- var own_probe = /* @__PURE__ */ new Map();
2199
- function own_start() {
2200
- if (!own_probe.has(process.pid)) own_probe.set(process.pid, resolve_own_start());
2201
- return own_probe.get(process.pid);
2257
+ function input_of(values, base) {
2258
+ const counts = counts_of(values);
2259
+ return {
2260
+ ...base,
2261
+ active_at_ms: to_time_ms(text_of(values.active)) ?? base.started_at_ms,
2262
+ merged: counts.merged ?? 0,
2263
+ running: counts.running ?? 0,
2264
+ max_issues: counts.max,
2265
+ idle_budget_ms: to_idle_ms(counts.idle)
2266
+ };
2202
2267
  }
2203
- function own_fields() {
2204
- const start = own_start();
2205
- return { pid: process.pid, ...start !== void 0 && { process_start: start } };
2268
+ function build_input(values, now_ms) {
2269
+ const answer = to_answer(text_of(values.answer));
2270
+ if (answer === void 0) return void 0;
2271
+ const started_at_ms = to_time_ms(text_of(values.started));
2272
+ if (started_at_ms === void 0) return void 0;
2273
+ if (!are_values_readable(values)) return void 0;
2274
+ return input_of(values, { answer, now_ms, started_at_ms });
2206
2275
  }
2207
- function is_same_process(pid, recorded_start, read2 = read_start) {
2208
- if (pid === void 0 || !is_live_pid(pid)) return false;
2209
- if (recorded_start === void 0) return void 0;
2210
- return matches_recorded_start(pid, recorded_start, read2);
2276
+ function refuse() {
2277
+ console.error(USAGE);
2278
+ return FAILURE_EXIT_CODE2;
2211
2279
  }
2212
- function is_own_process(pid, recorded_start) {
2213
- return pid === process.pid && recorded_start === own_start();
2280
+ function run(argv, now_ms = Date.now()) {
2281
+ const values = read_arguments(argv);
2282
+ if (values === void 0) return refuse();
2283
+ const input = build_input(values, now_ms);
2284
+ if (input === void 0) return refuse();
2285
+ const { verdict, reason } = backlog_budget.decide(input);
2286
+ if (values.json === true) {
2287
+ console.info(
2288
+ JSON.stringify({ [JSON_KEY]: verdict, reason, is_finish: backlog_budget.is_finish(input) })
2289
+ );
2290
+ return SUCCESS_EXIT_CODE;
2291
+ }
2292
+ path_decision.print_decision(JSON_KEY, verdict, reason, false);
2293
+ return SUCCESS_EXIT_CODE;
2214
2294
  }
2215
- var process_identity = {
2216
- close_beacon,
2217
- is_live_pid,
2218
- is_own_process,
2219
- is_same_process,
2220
- open_beacon,
2221
- own_fields,
2222
- own_start,
2223
- read_start,
2224
- resolve_own_start
2295
+ function main(argv) {
2296
+ process.exitCode = run(argv);
2297
+ }
2298
+ var backlog_budget_cli = {
2299
+ JSON_KEY,
2300
+ OPTIONS,
2301
+ SUCCESS_EXIT_CODE,
2302
+ USAGE,
2303
+ build_input,
2304
+ main,
2305
+ read_arguments,
2306
+ run,
2307
+ to_answer,
2308
+ to_count,
2309
+ to_time_ms
2225
2310
  };
2311
+ if (process.argv[1] === fileURLToPath(import.meta.url)) main(process.argv.slice(ARGV_OFFSET));
2226
2312
 
2227
- // scripts/git/git-common-directory.ts
2228
- import { execFileSync as execFileSync3 } from "node:child_process";
2229
- var OWN_GIT_DIRECTORY = 0;
2230
- var COMMON_GIT_DIRECTORY = 1;
2231
- var PROBE_TIMEOUT_MS2 = 2e3;
2232
- function read_directories(cwd) {
2233
- const git = git_utilities.get_git_command_for_spawn();
2234
- const output = execFileSync3(git, [...git_command.GIT_DIRECTORY_ARGUMENTS], {
2235
- cwd,
2236
- encoding: "utf8",
2237
- stdio: ["ignore", "pipe", "ignore"],
2238
- timeout: PROBE_TIMEOUT_MS2
2239
- });
2240
- return output.trim().split("\n");
2313
+ // scripts/run/run-invocation.ts
2314
+ var BACKLOG_COMMAND = "backlogrun";
2315
+ var KNOWN_FLAGS2 = ["--max", "--idle"];
2316
+ var ONLY_FLAG = "--only";
2317
+ var ISSUE_PREFIX = "#";
2318
+ var TOKEN_SEPARATOR = " ";
2319
+ var FIRST_TOKEN = 0;
2320
+ var FIRST_ARGUMENT_INDEX = 1;
2321
+ var VALUE_OFFSET = 1;
2322
+ var FLAG_PAIR_LENGTH = 2;
2323
+ var ONLY_STEP = 1;
2324
+ var EMPTY_LENGTH2 = 0;
2325
+ var NOT_FOUND = -1;
2326
+ function tokens_of(invocation) {
2327
+ return invocation.split(TOKEN_SEPARATOR).filter((token) => token.length > EMPTY_LENGTH2);
2241
2328
  }
2242
- function select_linked(directories) {
2243
- const own = directories[OWN_GIT_DIRECTORY];
2244
- const common = directories[COMMON_GIT_DIRECTORY];
2245
- return own !== void 0 && common !== void 0 && own !== common ? common : void 0;
2329
+ function flag_token(token, raw) {
2330
+ const flag = KNOWN_FLAGS2.find((known) => known === token);
2331
+ const value = backlog_budget_cli.to_count(raw);
2332
+ if (flag === void 0 || value === void 0) return void 0;
2333
+ return `${flag} ${String(value)}`;
2246
2334
  }
2247
- function resolve2(cwd) {
2248
- try {
2249
- return select_linked(read_directories(cwd));
2250
- } catch {
2251
- return void 0;
2252
- }
2335
+ function read_flag(tokens, index) {
2336
+ const token = tokens[index] ?? "";
2337
+ if (token === ONLY_FLAG) return { part: ONLY_FLAG, advance: ONLY_STEP };
2338
+ const part = flag_token(token, tokens[index + VALUE_OFFSET]);
2339
+ return part === void 0 ? void 0 : { part, advance: FLAG_PAIR_LENGTH };
2253
2340
  }
2254
- function repository(cwd) {
2255
- try {
2256
- return read_directories(cwd)[COMMON_GIT_DIRECTORY];
2257
- } catch {
2258
- return void 0;
2341
+ function flag_tokens(tokens, start) {
2342
+ const rendered = [];
2343
+ let index = start;
2344
+ while (index < tokens.length) {
2345
+ const read2 = read_flag(tokens, index);
2346
+ if (read2 === void 0) return void 0;
2347
+ rendered.push(read2.part);
2348
+ index += read2.advance;
2259
2349
  }
2350
+ return rendered;
2260
2351
  }
2261
- var git_common_directory = { repository, resolve: resolve2, select_linked };
2262
-
2263
- // scripts/git/repository-lock.ts
2264
- var POLL_INTERVAL_MS = 200;
2265
- function lock_path(prefix, cwd = process.cwd()) {
2266
- return stamp_file.stamp_path(prefix, git_common_directory.repository(cwd) ?? cwd);
2352
+ function issue_of(token) {
2353
+ if (!token.startsWith(ISSUE_PREFIX)) return void 0;
2354
+ const digits = token.slice(ISSUE_PREFIX.length);
2355
+ if (!run_issue_number.ISSUE_NUMBER_PATTERN.test(digits)) return void 0;
2356
+ const issue = Number(digits);
2357
+ return Number.isSafeInteger(issue) ? issue : void 0;
2267
2358
  }
2268
- function parse_owner(raw) {
2269
- if (raw === void 0) return void 0;
2270
- try {
2271
- return JSON.parse(raw);
2272
- } catch {
2273
- return void 0;
2359
+ function take_leading_prefixed(args) {
2360
+ const stop = args.findIndex((token) => !token.startsWith(ISSUE_PREFIX));
2361
+ return args.slice(FIRST_TOKEN, stop === NOT_FOUND ? args.length : stop);
2362
+ }
2363
+ function leading_issues(tokens) {
2364
+ const issues = [];
2365
+ const prefixed = take_leading_prefixed(tokens.slice(FIRST_ARGUMENT_INDEX));
2366
+ for (const token of prefixed) {
2367
+ const issue = issue_of(token);
2368
+ if (issue === void 0) return void 0;
2369
+ issues.push(issue);
2274
2370
  }
2371
+ return issues;
2275
2372
  }
2276
- function read_owner(target) {
2277
- return parse_owner(stamp_file.read_stamp_text(target));
2373
+ function issue_token(issue) {
2374
+ return `${ISSUE_PREFIX}${String(issue)}`;
2278
2375
  }
2279
- function is_owner_gone(owner) {
2280
- return process_identity.is_same_process(owner.pid, owner.process_start) === false;
2376
+ function joined(command, rendered) {
2377
+ return [command, ...rendered].join(TOKEN_SEPARATOR);
2281
2378
  }
2282
- function restore(aside, target) {
2283
- try {
2284
- linkSync(aside, target);
2285
- } catch {
2286
- }
2379
+ function rebuilt_backlog(tokens) {
2380
+ const issues = leading_issues(tokens);
2381
+ if (issues === void 0) return void 0;
2382
+ const flags = flag_tokens(tokens, FIRST_ARGUMENT_INDEX + issues.length);
2383
+ if (flags === void 0) return void 0;
2384
+ return joined(BACKLOG_COMMAND, [...issues.map((issue) => issue_token(issue)), ...flags]);
2287
2385
  }
2288
- function stale_record(target, is_gone2) {
2289
- const raw = stamp_file.read_stamp_text(target);
2290
- const owner = parse_owner(raw);
2291
- return owner !== void 0 && is_gone2(owner) ? raw : void 0;
2386
+ function rebuild(invocation) {
2387
+ const tokens = tokens_of(invocation);
2388
+ if (tokens[FIRST_TOKEN] !== BACKLOG_COMMAND) return void 0;
2389
+ return rebuilt_backlog(tokens);
2292
2390
  }
2293
- function move_aside(target) {
2294
- const aside = `${target}.${String(process.pid)}.${randomUUID2()}`;
2391
+ function issue_numbers(invocation) {
2392
+ const tokens = tokens_of(invocation);
2393
+ if (tokens[FIRST_TOKEN] !== BACKLOG_COMMAND) return void 0;
2394
+ const issues = leading_issues(tokens);
2395
+ if (issues === void 0 || issues.length === EMPTY_LENGTH2) return void 0;
2396
+ return issues;
2397
+ }
2398
+ function has_only(invocation) {
2399
+ const tokens = tokens_of(invocation);
2400
+ if (tokens[FIRST_TOKEN] !== BACKLOG_COMMAND) return false;
2401
+ return tokens.slice(FIRST_ARGUMENT_INDEX).includes(ONLY_FLAG);
2402
+ }
2403
+ var run_invocation = {
2404
+ has_only,
2405
+ issue_numbers,
2406
+ rebuild
2407
+ };
2408
+
2409
+ // scripts/run/run-carry.ts
2410
+ var CARRY_PREFIX = "josh-run-carry-";
2411
+ var REPOSITORY_DIRECTORY_INDEX = 1;
2412
+ var CARRY_MAX_AGE_HOURS = backlog_budget.WHOLE_RUN_BUDGET_HOURS;
2413
+ var CARRY_MAX_AGE_MS = backlog_budget.WHOLE_RUN_BUDGET_MS;
2414
+ var NO_INCREMENT3 = 0;
2415
+ var MAX_CUTS = 6;
2416
+ var END_COMMAND2 = "pnpm josh run:carry --end";
2417
+ var RESUME_COMMAND = "pnpm josh run:carry --resume";
2418
+ var READ_COMMAND2 = "pnpm josh run:carry --json";
2419
+ var OWNER_ARGUMENT = '--owner "$PPID"';
2420
+ var NONE_READ2 = { kind: "none" };
2421
+ var UNREADABLE_READ3 = { kind: "unreadable" };
2422
+ var NO_OWNER = {};
2423
+ var run_carry_schema = z6.object({
2424
+ invocation: z6.string(),
2425
+ started_at: z6.string(),
2426
+ merged: z6.number(),
2427
+ merged_issues: z6.array(z6.number()).optional(),
2428
+ filed: z6.number(),
2429
+ cuts: z6.number(),
2430
+ // Defaulted, so a record written before this field existed parses with a zero streak rather than
2431
+ // failing to read — the same backward-compatibility the optional owner fields below carry.
2432
+ failures: z6.number().default(0),
2433
+ // Defaulted for the same backward-compatibility as `failures` (joshuafolkken/kit#2240).
2434
+ outages: z6.number().default(0),
2435
+ // Optional, so a record written before the outage fold existed still parses (joshuafolkken/kit#2317).
2436
+ last_outage_at: z6.string().optional(),
2437
+ // Optional, so a record written by the previous shape still parses. Read as "no owner declared",
2438
+ // which is the not-provably-live answer rather than a live one.
2439
+ owner_pid: z6.number().optional(),
2440
+ owner_start: z6.string().optional(),
2441
+ is_handed_off: z6.boolean().optional(),
2442
+ done: z6.array(z6.number()).optional(),
2443
+ // Optional, so a record written before the retrospective existed still parses (joshuafolkken/kit#2328).
2444
+ retrospective: z6.boolean().optional()
2445
+ });
2446
+ function carry_path(git_directory) {
2447
+ return stamp_file.stamp_path(CARRY_PREFIX, git_directory);
2448
+ }
2449
+ async function repository_directory() {
2450
+ const directories2 = await git_command.git_directories();
2451
+ return directories2[REPOSITORY_DIRECTORY_INDEX];
2452
+ }
2453
+ function parse_carry(raw) {
2295
2454
  try {
2296
- renameSync(target, aside);
2297
- return aside;
2455
+ const parsed = run_carry_schema.safeParse(JSON.parse(raw));
2456
+ return parsed.success ? parsed.data : void 0;
2298
2457
  } catch {
2299
2458
  return void 0;
2300
2459
  }
2301
2460
  }
2302
- function clear_stale(target, is_gone2 = is_owner_gone) {
2303
- const raw = stale_record(target, is_gone2);
2304
- if (raw === void 0) return;
2305
- const aside = move_aside(target);
2306
- if (aside === void 0) return;
2307
- if (stamp_file.read_stamp_text(aside) !== raw) restore(aside, target);
2308
- rmSync2(aside, { force: true });
2461
+ function is_expired2(carry, now) {
2462
+ const started = Date.parse(carry.started_at);
2463
+ if (Number.isNaN(started)) return true;
2464
+ return now.getTime() - started > CARRY_MAX_AGE_MS;
2309
2465
  }
2310
- function claim(target) {
2311
- clear_stale(target);
2312
- return stamp_file.create_stamp(target, process_identity.own_fields());
2466
+ function classify3(raw, now) {
2467
+ if (raw === void 0) return NONE_READ2;
2468
+ const carry = parse_carry(raw);
2469
+ if (carry === void 0) return UNREADABLE_READ3;
2470
+ return { kind: is_expired2(carry, now) ? "expired" : "carried", carry };
2313
2471
  }
2314
- function release(target) {
2315
- const owner = read_owner(target);
2316
- if (owner !== void 0 && process_identity.is_own_process(owner.pid, owner.process_start)) {
2317
- stamp_file.remove_stamp(target);
2318
- }
2472
+ function read_carry(target, now = /* @__PURE__ */ new Date()) {
2473
+ return classify3(stamp_file.read_stamp_text(target), now);
2319
2474
  }
2320
- async function acquire(target, max_wait_ms) {
2321
- const deadline = Date.now() + max_wait_ms;
2322
- while (!claim(target)) {
2323
- if (Date.now() >= deadline) return false;
2324
- await sleep(POLL_INTERVAL_MS);
2325
- }
2326
- return true;
2475
+ function owner_of(pid, read2 = process_identity.read_start) {
2476
+ return { pid, start: read2(pid) };
2327
2477
  }
2328
- async function with_lock(work, target, max_wait_ms) {
2329
- if (!await acquire(target, max_wait_ms)) return void 0;
2330
- try {
2331
- return await work();
2332
- } finally {
2333
- release(target);
2334
- }
2478
+ function fresh_carry(invocation, owner, now) {
2479
+ return {
2480
+ invocation,
2481
+ started_at: now.toISOString(),
2482
+ merged: NO_INCREMENT3,
2483
+ filed: NO_INCREMENT3,
2484
+ cuts: NO_INCREMENT3,
2485
+ failures: NO_INCREMENT3,
2486
+ outages: NO_INCREMENT3,
2487
+ owner_pid: owner.pid,
2488
+ owner_start: owner.start
2489
+ };
2335
2490
  }
2336
- var repository_lock = { clear_stale, lock_path, with_lock };
2337
-
2338
- // scripts/git/git-worktree.ts
2339
- var WORKTREE = "worktree";
2340
- var WORKTREE_LOCK_PREFIX = "josh-worktree-lock-";
2341
- var WORKTREE_LOCK_MAX_WAIT_MS = 12e4;
2342
- async function locked(arguments_) {
2343
- const target = repository_lock.lock_path(WORKTREE_LOCK_PREFIX);
2344
- const output = await repository_lock.with_lock(
2345
- async () => await git_spawn.read(arguments_),
2346
- target,
2347
- WORKTREE_LOCK_MAX_WAIT_MS
2348
- );
2349
- if (output === void 0) {
2350
- throw new Error(
2351
- `git ${arguments_.join(" ")}: timed out waiting for the work-tree lock ${target}`
2352
- );
2353
- }
2354
- return output;
2491
+ function begin_carry(target, invocation, owner = NO_OWNER, now = /* @__PURE__ */ new Date()) {
2492
+ const carry = fresh_carry(invocation, owner, now);
2493
+ return stamp_file.create_stamp(target, carry) ? carry : void 0;
2355
2494
  }
2356
- async function worktree_list() {
2357
- return await git_spawn.read([WORKTREE, "list", PORCELAIN_FLAG]);
2495
+ function replace_carry(target, invocation, owner = NO_OWNER, now = /* @__PURE__ */ new Date()) {
2496
+ stamp_file.remove_stamp(target);
2497
+ return begin_carry(target, invocation, owner, now);
2358
2498
  }
2359
- async function worktree_add(directory, branch_name, start_point) {
2360
- const flags = start_point === void 0 ? ["add"] : ["add", "--no-track", "-b", branch_name];
2361
- return await locked([WORKTREE, ...flags, directory, start_point ?? branch_name]);
2499
+ function adopt_carry(target, carry, owner = NO_OWNER) {
2500
+ const next = {
2501
+ ...carry,
2502
+ owner_pid: owner.pid,
2503
+ owner_start: owner.start,
2504
+ is_handed_off: false
2505
+ };
2506
+ stamp_file.remove_stamp(target);
2507
+ return stamp_file.create_stamp(target, next) ? next : void 0;
2362
2508
  }
2363
- async function worktree_add_detached(directory, commit2) {
2364
- return await locked([WORKTREE, "add", "--detach", directory, commit2]);
2509
+ function is_owner_live(carry) {
2510
+ return process_identity.is_same_process(carry.owner_pid, carry.owner_start) !== false;
2365
2511
  }
2366
- async function worktree_remove(directory) {
2367
- return await locked([WORKTREE, "remove", "--force", directory]);
2512
+ function is_owned_by(carry, owner) {
2513
+ return carry.owner_pid === owner.pid && carry.owner_start === owner.start;
2368
2514
  }
2369
- async function worktree_prune() {
2370
- return await locked([WORKTREE, "prune"]);
2515
+ function is_foreign_live_owner(carry, owner) {
2516
+ return is_owner_live(carry) && !is_owned_by(carry, owner);
2371
2517
  }
2372
- async function ls_remote_branch(branch_name) {
2373
- return await git_spawn.read(ls_remote_branch_arguments(branch_name));
2518
+ function is_handed_off_to(carry, invocation) {
2519
+ return carry.is_handed_off === true && carry.invocation === invocation;
2374
2520
  }
2375
- async function branch_delete(branch_name) {
2376
- return await git_spawn.read(["branch", "-D", branch_name]);
2521
+ function classify_claim(carry, request) {
2522
+ if (is_handed_off_to(carry, request.invocation)) return "resume";
2523
+ if (is_foreign_live_owner(carry, request.owner)) return "busy";
2524
+ if (carry.invocation !== request.invocation) return "mismatch";
2525
+ return request.is_adoption ? "resume" : "standing";
2377
2526
  }
2378
- var git_worktree = {
2379
- branch_delete,
2380
- ls_remote_branch,
2381
- worktree_add,
2382
- worktree_add_detached,
2383
- worktree_list,
2384
- worktree_prune,
2385
- worktree_remove
2386
- };
2387
-
2388
- // scripts/lane/lane-environment.ts
2389
- var LANE_OUTPUT_KEY = "JOSH_LANE_OUTPUT";
2390
- var LANE_PROVIDER_KEY = "JOSH_LANE_AGENT_PROVIDER";
2391
- var LANE_ROLE_KEY = "JOSH_LANE_AGENT_ROLE";
2392
- var LANE_MODEL_KEY = "JOSH_LANE_AGENT_MODEL";
2393
- var LANE_EFFORT_KEY = "JOSH_LANE_AGENT_EFFORT";
2394
- var ASSIGNMENT_SEPARATOR = "=";
2395
- var EXPORT_PREFIX = "export ";
2396
- var QUOTE_CHARACTERS = /* @__PURE__ */ new Set(['"', "'"]);
2397
- var MIN_QUOTED_LENGTH = 2;
2398
- var FIRST_CHARACTER_END = 1;
2399
- var LAST_CHARACTER_START = -1;
2400
- function strip_export(line) {
2401
- const trimmed2 = line.trimStart();
2402
- return trimmed2.startsWith(EXPORT_PREFIX) ? trimmed2.slice(EXPORT_PREFIX.length).trimStart() : trimmed2;
2527
+ function is_count_refused(carry, owner) {
2528
+ return carry.is_handed_off === true || is_foreign_live_owner(carry, owner);
2403
2529
  }
2404
- function is_key_line(line, key) {
2405
- const body = strip_export(line);
2406
- if (!body.startsWith(key)) return false;
2407
- return body.slice(key.length).trimStart().startsWith(ASSIGNMENT_SEPARATOR);
2530
+ function is_at_cut_cap(carry) {
2531
+ return carry.cuts >= MAX_CUTS;
2408
2532
  }
2409
- function is_seed_line(line) {
2410
- return is_key_line(line, PORT_SEED_KEY);
2533
+ function end_carry(target) {
2534
+ stamp_file.remove_stamp(target);
2411
2535
  }
2412
- function is_seat_line(line) {
2413
- return is_key_line(line, LANE_SEAT_KEY);
2536
+ function retrospective_done_of(read2) {
2537
+ if (read2.kind !== "carried" && read2.kind !== "expired") return false;
2538
+ return read2.carry.retrospective === true;
2414
2539
  }
2415
- function is_output_line(line) {
2416
- return is_key_line(line, LANE_OUTPUT_KEY);
2540
+ function remaining_of(carry) {
2541
+ if (carry === void 0) return void 0;
2542
+ const declared = run_invocation.issue_numbers(carry.invocation);
2543
+ if (declared === void 0) return void 0;
2544
+ const done = carry.done ?? [];
2545
+ return declared.filter((issue) => !done.includes(issue));
2417
2546
  }
2418
- function split_lines(content) {
2419
- const trimmed2 = content.trimEnd();
2420
- return trimmed2.length === 0 ? [] : trimmed2.split("\n");
2547
+ function done_note(carry) {
2548
+ const done = carry.done ?? [];
2549
+ return done.length === 0 ? "" : `, issues ${done.join(", ")} done`;
2421
2550
  }
2422
- function is_quoted(raw) {
2423
- const first = raw.slice(0, FIRST_CHARACTER_END);
2424
- if (!QUOTE_CHARACTERS.has(first)) return false;
2425
- return raw.length >= MIN_QUOTED_LENGTH && raw.endsWith(first);
2551
+ function describe_carry(carry) {
2552
+ return `${carry.invocation} started ${carry.started_at}; ${String(carry.merged)} merged, ${String(carry.filed)} filed, ${String(carry.failures)} failed in a row, ${String(carry.outages)} outage(s) in a row, ${String(carry.cuts)} cut(s) crossed${done_note(carry)}`;
2426
2553
  }
2427
- function unquote(raw) {
2428
- return is_quoted(raw) ? raw.slice(FIRST_CHARACTER_END, LAST_CHARACTER_START) : raw;
2554
+ function expired_message(carry) {
2555
+ return `The ${String(CARRY_MAX_AGE_HOURS)}-hour whole-run bound is spent: ${describe_carry(carry)}. Clear it with \`${END_COMMAND2}\`.`;
2429
2556
  }
2430
- function assignment_value(line) {
2431
- return unquote(line.slice(line.indexOf(ASSIGNMENT_SEPARATOR) + FIRST_CHARACTER_END).trim());
2557
+ function mismatch_message(carry, invocation) {
2558
+ return `A different invocation is carried here \u2014 ${describe_carry(carry)}. This one is \`${invocation}\`. End the standing record with \`${END_COMMAND2}\` once you know its run is over, then begin again.`;
2432
2559
  }
2433
- function read_assignment(content, key) {
2434
- const line = split_lines(content).findLast((entry) => is_key_line(entry, key));
2435
- return line === void 0 ? void 0 : assignment_value(line);
2560
+ function busy_message2(carry) {
2561
+ return `A live run holds this budget \u2014 ${describe_carry(carry)}, owner pid ${String(carry.owner_pid)} still running. Nothing was established. Wait for that run, or end its record with \`${END_COMMAND2}\` once you know it is over.`;
2436
2562
  }
2437
- function read_root_seed(content) {
2438
- return ports.resolve_seed({ [PORT_SEED_KEY]: read_assignment(content, PORT_SEED_KEY) });
2563
+ function standing_message(carry) {
2564
+ return `A run record is standing here that no cut handed off \u2014 ${describe_carry(carry)}. Nothing was established. Carry that budget with \`${RESUME_COMMAND} "${carry.invocation}" ${OWNER_ARGUMENT}\`, or discard it with \`${END_COMMAND2}\` and begin again.`;
2439
2565
  }
2440
- function read_lane_seat(content) {
2441
- const raw = read_assignment(content, LANE_SEAT_KEY);
2442
- return raw === void 0 ? void 0 : ports.resolve_lane({ [LANE_SEAT_KEY]: raw });
2566
+ function count_refused_message(carry) {
2567
+ if (carry.is_handed_off === true) {
2568
+ return `This budget was handed off at a cut and is waiting for its successor \u2014 ${describe_carry(carry)}. Nothing was counted; the cutting session must not advance a handed-off budget.`;
2569
+ }
2570
+ return `A live run owns this budget \u2014 ${describe_carry(carry)}, owner pid ${String(carry.owner_pid)} still running. Nothing was counted; only its owner advances the budget.`;
2443
2571
  }
2444
- function read_lane_ports(content) {
2445
- const environment = {
2446
- [PORT_SEED_KEY]: read_assignment(content, PORT_SEED_KEY),
2447
- [LANE_SEAT_KEY]: read_assignment(content, LANE_SEAT_KEY)
2448
- };
2572
+ function lost_message() {
2573
+ return `Another process established a run record here first, so nothing was established. Read it with \`${READ_COMMAND2}\`.`;
2574
+ }
2575
+ function nothing_to_resume_message() {
2576
+ return `Nothing is carried here, so there is no budget to resume. Begin one with \`pnpm josh run:carry --begin "<invocation>"\`.`;
2577
+ }
2578
+ function unreadable_message3() {
2579
+ return `A run record is here but could not be read; clear it with \`${END_COMMAND2}\` once you know no run is using it.`;
2580
+ }
2581
+ function unknown_message3() {
2582
+ return "This repository\u2019s git directory could not be read, so no run record was established.";
2583
+ }
2584
+ var run_carry = {
2585
+ CARRY_MAX_AGE_HOURS,
2586
+ CARRY_MAX_AGE_MS,
2587
+ END_COMMAND: END_COMMAND2,
2588
+ MAX_CUTS,
2589
+ NO_OWNER,
2590
+ RESUME_COMMAND,
2591
+ adopt_carry,
2592
+ apply_change: run_carry_change.apply_change,
2593
+ begin_carry,
2594
+ busy_message: busy_message2,
2595
+ carry_path,
2596
+ classify: classify3,
2597
+ classify_claim,
2598
+ count_refused_message,
2599
+ is_at_cut_cap,
2600
+ describe_carry,
2601
+ end_carry,
2602
+ expired_message,
2603
+ mismatch_message,
2604
+ fresh_carry,
2605
+ is_count_refused,
2606
+ is_expired: is_expired2,
2607
+ is_foreign_live_owner,
2608
+ is_owner_live,
2609
+ lost_message,
2610
+ nothing_to_resume_message,
2611
+ owner_of,
2612
+ parse_carry,
2613
+ read_carry,
2614
+ remaining_of,
2615
+ replace_carry,
2616
+ repository_directory,
2617
+ retrospective_done_of,
2618
+ standing_message,
2619
+ unknown_message: unknown_message3,
2620
+ unreadable_message: unreadable_message3
2621
+ };
2622
+
2623
+ // scripts/run/run-ship-detach.ts
2624
+ import { randomUUID as randomUUID2 } from "node:crypto";
2625
+ import { setTimeout as sleep } from "node:timers/promises";
2626
+ import { z as z8 } from "zod";
2627
+
2628
+ // scripts/run/detached-launch.ts
2629
+ import { spawn } from "node:child_process";
2630
+ import { closeSync, constants, openSync, writeSync } from "node:fs";
2631
+
2632
+ // scripts/agent/agent-launch-environment.ts
2633
+ import { existsSync, lstatSync, mkdirSync, readlinkSync, symlinkSync, unlinkSync } from "node:fs";
2634
+ import path3 from "node:path";
2635
+
2636
+ // scripts/gh/gh-cli-token.ts
2637
+ import { execaSync } from "execa";
2638
+ var GH_CLI_TIMEOUT_MS = 5e3;
2639
+ function get() {
2640
+ try {
2641
+ const { stdout } = execaSync("gh", ["auth", "token"], { timeout: GH_CLI_TIMEOUT_MS });
2642
+ const token = stdout.trim();
2643
+ return token.length > 0 ? token : void 0;
2644
+ } catch {
2645
+ return void 0;
2646
+ }
2647
+ }
2648
+ var gh_cli_token = { get };
2649
+
2650
+ // scripts/agent/codex-home-source.ts
2651
+ import os from "node:os";
2652
+ import path2 from "node:path";
2653
+ function default_home() {
2654
+ return path2.join(os.homedir(), ".codex");
2655
+ }
2656
+ var codex_home_source = { default_home };
2657
+
2658
+ // scripts/agent/agent-launch-environment.ts
2659
+ var OPENAI_PROVIDER2 = "openai";
2660
+ var REVIEWER_HOME = ["node_modules", ".cache", "josh", "openai", "reviewer-home"];
2661
+ var HOME_FILES = ["auth.json", "config.toml"];
2662
+ var PRIVATE_DIRECTORY_MODE = 448;
2663
+ function openai_environment() {
2664
+ const token = gh_cli_token.get();
2665
+ return token === void 0 ? { TMPDIR: PLATFORM_TEMP_ROOT } : { GH_TOKEN: token, TMPDIR: PLATFORM_TEMP_ROOT };
2666
+ }
2667
+ function remove_old_home_link(source, target) {
2668
+ const current = lstatSync(target, { throwIfNoEntry: false });
2669
+ if (current === void 0) return false;
2670
+ if (!current.isSymbolicLink()) throw new Error(`Reviewer home file is not a link: ${target}`);
2671
+ if (existsSync(source) && readlinkSync(target) === source) return true;
2672
+ unlinkSync(target);
2673
+ return false;
2674
+ }
2675
+ function link_home_file(source_home, target_home, name) {
2676
+ const source = path3.join(source_home, name);
2677
+ const target = path3.join(target_home, name);
2678
+ if (!remove_old_home_link(source, target) && existsSync(source)) symlinkSync(source, target);
2679
+ }
2680
+ function reviewer_home(cwd, environment2) {
2681
+ const source = environment2["CODEX_HOME"] ?? process.env["CODEX_HOME"] ?? codex_home_source.default_home();
2682
+ const target = path3.resolve(cwd, ...REVIEWER_HOME);
2683
+ mkdirSync(target, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });
2684
+ for (const name of HOME_FILES) link_home_file(source, target, name);
2685
+ return target;
2686
+ }
2687
+ function build(cwd, profile, environment2 = {}) {
2688
+ if (profile?.provider !== OPENAI_PROVIDER2) return environment2;
2689
+ const base = { ...openai_environment(), ...environment2 };
2690
+ return profile.role === agent_role_profile.REVIEWER ? { ...base, CODEX_HOME: reviewer_home(cwd, environment2) } : base;
2691
+ }
2692
+ var agent_launch_environment = { build };
2693
+
2694
+ // scripts/run/detached-launch.ts
2695
+ var NO_PID_NOTE = "the process started without a pid";
2696
+ var UNSAFE_NOTE = "the wake command or its arguments contain characters that are not safe to execute";
2697
+ var { is_safe_value: is_safe_value2 } = agent_role_profile;
2698
+ function is_safe_argv(argv) {
2699
+ return is_safe_value2(argv.command) && argv.args.every((value) => is_safe_value2(value));
2700
+ }
2701
+ function note_of(error) {
2702
+ return error instanceof Error ? error.message : JSON.stringify(error);
2703
+ }
2704
+ function launched(pid) {
2705
+ return { kind: "launched", pid };
2706
+ }
2707
+ var LOG_FLAGS = constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW;
2708
+ var LOG_MODE = 384;
2709
+ function profile_suffix(profile) {
2710
+ return profile === void 0 ? "" : ` \xB7 ${agent_role_profile.describe(profile)}`;
2711
+ }
2712
+ function log_header(argv, profile) {
2713
+ const stamp = (/* @__PURE__ */ new Date()).toISOString();
2714
+ return `
2715
+ === ${stamp} \xB7 started by process ${String(process.pid)} \xB7 ${argv.command}${profile_suffix(profile)} ===
2716
+ `;
2717
+ }
2718
+ function opened_log(log_path2, on_error) {
2719
+ const descriptor = openSync(log_path2, LOG_FLAGS, LOG_MODE);
2720
+ if (!stamp_file.is_own_regular_file(log_path2)) {
2721
+ closeSync(descriptor);
2722
+ on_error(`${log_path2} is not this account's own file, so nothing is kept there`);
2723
+ return void 0;
2724
+ }
2725
+ return descriptor;
2726
+ }
2727
+ function stamped(descriptor, argv, profile, on_error) {
2728
+ if (descriptor === void 0) return;
2729
+ try {
2730
+ writeSync(descriptor, log_header(argv, profile));
2731
+ } catch (error) {
2732
+ on_error(`the session log could not be written: ${note_of(error)}`);
2733
+ }
2734
+ }
2735
+ function open_log(log_path2, on_error) {
2736
+ if (log_path2 === void 0) return void 0;
2737
+ try {
2738
+ return opened_log(log_path2, on_error);
2739
+ } catch (error) {
2740
+ on_error(`the session log at ${log_path2} could not be opened: ${note_of(error)}`);
2741
+ return void 0;
2742
+ }
2743
+ }
2744
+ function ensure_log(log_path2, on_error) {
2745
+ const descriptor = open_log(log_path2, on_error);
2746
+ if (descriptor !== void 0) closeSync(descriptor);
2747
+ }
2748
+ function spawn_options(cwd, log, environment2 = {}, is_detached = true) {
2449
2749
  return {
2450
- development: ports.resolve_development_port(environment),
2451
- preview: ports.resolve_preview_port(environment)
2750
+ cwd,
2751
+ detached: is_detached,
2752
+ stdio: log === void 0 ? "ignore" : ["ignore", log, log],
2753
+ env: { ...process.env, ...agent_session_environment.removed_environment(), ...environment2 }
2452
2754
  };
2453
2755
  }
2454
- function read_lane_output(content) {
2455
- const line = split_lines(content).findLast((entry) => is_output_line(entry));
2456
- const raw = line === void 0 ? void 0 : assignment_value(line);
2457
- return raw === void 0 || raw.length === 0 ? void 0 : raw;
2756
+ function notify_spawn(pid, on_spawn, on_error) {
2757
+ if (pid === void 0) return;
2758
+ try {
2759
+ on_spawn(pid);
2760
+ } catch (error) {
2761
+ on_error(`the child state could not be recorded: ${note_of(error)}`);
2762
+ }
2458
2763
  }
2459
- function read_lane_profile(content) {
2460
- return agent_role_profile.parse({
2461
- provider: read_assignment(content, LANE_PROVIDER_KEY),
2462
- role: read_assignment(content, LANE_ROLE_KEY),
2463
- model: read_assignment(content, LANE_MODEL_KEY),
2464
- effort: read_assignment(content, LANE_EFFORT_KEY)
2764
+ async function attached_spawn(request, log, on_error, on_spawn) {
2765
+ return await new Promise((resolve3) => {
2766
+ const environment2 = agent_launch_environment.build(request.cwd, request.profile, request.env);
2767
+ const child = spawn(
2768
+ request.argv.command,
2769
+ [...request.argv.args],
2770
+ spawn_options(request.cwd, log, environment2, false)
2771
+ );
2772
+ child.once("error", (error) => {
2773
+ on_error(note_of(error));
2774
+ resolve3({ kind: "failed", note: note_of(error) });
2775
+ });
2776
+ child.once("exit", (exit_code) => {
2777
+ resolve3({ kind: "completed", pid: child.pid ?? 0, exit_code });
2778
+ });
2779
+ notify_spawn(child.pid, on_spawn, on_error);
2465
2780
  });
2466
2781
  }
2467
- function replace_assignment(lines, keep, assignment, key) {
2468
- return lines.flatMap((entry, index) => {
2469
- if (index === keep) return [assignment];
2470
- return is_key_line(entry, key) ? [] : [entry];
2471
- });
2782
+ function ignore_spawn(_pid) {
2472
2783
  }
2473
- function upsert_assignment(content, key, value) {
2474
- const assignment = `${key}${ASSIGNMENT_SEPARATOR}${value}`;
2475
- const lines = split_lines(content);
2476
- const last = lines.findLastIndex((entry) => is_key_line(entry, key));
2477
- const body = last === -1 ? [...lines, assignment] : replace_assignment(lines, last, assignment, key);
2478
- return `${body.join("\n")}
2479
- `;
2784
+ function spawned(request, log, on_error) {
2785
+ const { command, args } = request.argv;
2786
+ const environment2 = agent_launch_environment.build(request.cwd, request.profile, request.env);
2787
+ const child = spawn(command, [...args], spawn_options(request.cwd, log, environment2));
2788
+ child.on("error", (error) => {
2789
+ on_error(note_of(error));
2790
+ });
2791
+ child.unref();
2792
+ return child.pid === void 0 ? { kind: "failed", note: NO_PID_NOTE } : launched(child.pid);
2480
2793
  }
2481
- function lane_file_content(root_content, seat) {
2482
- return upsert_assignment(root_content, LANE_SEAT_KEY, String(seat));
2794
+ function launch(request, on_error) {
2795
+ if (!is_safe_argv(request.argv)) return { kind: "failed", note: UNSAFE_NOTE };
2796
+ const log = open_log(request.log_path, on_error);
2797
+ try {
2798
+ stamped(log, request.argv, request.profile, on_error);
2799
+ return spawned(request, log, on_error);
2800
+ } catch (error) {
2801
+ return { kind: "failed", note: note_of(error) };
2802
+ } finally {
2803
+ if (log !== void 0) closeSync(log);
2804
+ }
2483
2805
  }
2484
- function with_lane_output(content, output, profile) {
2485
- const recorded = upsert_assignment(content, LANE_OUTPUT_KEY, `"${output}"`);
2486
- if (profile === void 0) return recorded;
2487
- const with_provider = upsert_assignment(recorded, LANE_PROVIDER_KEY, profile.provider);
2488
- const with_role = upsert_assignment(with_provider, LANE_ROLE_KEY, profile.role);
2489
- const with_model = upsert_assignment(with_role, LANE_MODEL_KEY, profile.model);
2490
- return upsert_assignment(with_model, LANE_EFFORT_KEY, profile.effort);
2806
+ async function launch_attached(request, on_error, on_spawn = ignore_spawn) {
2807
+ if (!is_safe_argv(request.argv)) return { kind: "failed", note: UNSAFE_NOTE };
2808
+ const log = open_log(request.log_path, on_error);
2809
+ try {
2810
+ stamped(log, request.argv, request.profile, on_error);
2811
+ return await attached_spawn(request, log, on_error, on_spawn);
2812
+ } catch (error) {
2813
+ return { kind: "failed", note: note_of(error) };
2814
+ } finally {
2815
+ if (log !== void 0) closeSync(log);
2816
+ }
2491
2817
  }
2492
- var lane_environment = {
2493
- LANE_OUTPUT_KEY,
2494
- LANE_PROVIDER_KEY,
2495
- LANE_ROLE_KEY,
2496
- is_output_line,
2497
- is_seat_line,
2498
- is_seed_line,
2499
- lane_file_content,
2500
- read_lane_output,
2501
- read_lane_profile,
2502
- read_lane_ports,
2503
- read_lane_seat,
2504
- read_root_seed,
2505
- with_lane_output
2818
+ var detached_launch = {
2819
+ UNSAFE_NOTE,
2820
+ ensure_log,
2821
+ is_safe_argv,
2822
+ is_safe_value: is_safe_value2,
2823
+ launch,
2824
+ launch_attached,
2825
+ log_header,
2826
+ note_of
2506
2827
  };
2507
2828
 
2508
- // scripts/lane/lane-registry.ts
2509
- var WORKTREE_PREFIX = "worktree ";
2510
- var BRANCH_PREFIX = "branch refs/heads/";
2511
- var BLOCK_SEPARATOR = "\n\n";
2512
- var ISSUE_PATTERN = /^[1-9]\d*$/u;
2513
- function line_value(lines, prefix) {
2514
- return lines.find((line) => line.startsWith(prefix))?.slice(prefix.length);
2829
+ // scripts/run/run-event-stream.ts
2830
+ import { z as z7 } from "zod";
2831
+ var EVENT_PREFIX = "josh-run-events-";
2832
+ var EVENT_SUFFIX = ".jsonl";
2833
+ var EVENT_CAP = 500;
2834
+ var FIRST_POSITION = 0;
2835
+ var POSITION_INCREMENT = 1;
2836
+ var LINE_SEPARATOR = "\n";
2837
+ var EVENT_KIND = {
2838
+ PLAN: "plan",
2839
+ CHILD_LAUNCH: "child-launch",
2840
+ MERGE: "merge",
2841
+ PARK: "park",
2842
+ SPLIT: "split",
2843
+ OUTAGE: "outage",
2844
+ CUT: "cut",
2845
+ // The backlog emptied while nothing of the run's own was in flight — the drain
2846
+ // (joshuafolkken/kit#2335). It marks the position at which the end-of-run retrospective is owed,
2847
+ // *before* the idle watch opens, so `run:step` fires the retrospective at the drain rather than after
2848
+ // the watch runs its course. It is not itself a stop: the run watches on once the retrospective has
2849
+ // run, and the real `STOP` follows when the watch expires.
2850
+ DRAIN: "drain",
2851
+ STOP: "stop",
2852
+ PR_OPENED: "pr-opened",
2853
+ REVIEW_ROUND: "review-round",
2854
+ // The end-of-run retrospective's result, recorded when the run marks the retrospective done
2855
+ // (joshuafolkken/kit#2342). A retrospective that files zero improvements and one that never ran look
2856
+ // the same from outside — no new Issue either way — so the result is put on the stream the report is
2857
+ // generated from: the issues filed (or that none were), and the candidates dropped with why. It is
2858
+ // emitted from the `run:carry --retrospective` close, so marking the retrospective done and recording
2859
+ // what it found are one action rather than two the run could do only one of.
2860
+ RETROSPECTIVE: "retrospective",
2861
+ // Ready backlog work is sitting undispatched while a lane is free and nothing has dispatched for a
2862
+ // while (joshuafolkken/kit#2359). Emitted once per stall episode by the stop-time detector, it is
2863
+ // both the dedup marker — `emit_once_since` refuses a second until a `CHILD_LAUNCH` intervenes
2864
+ // (joshuafolkken/kit#2464) — and the line a
2865
+ // terminal reader following the stream sees. `run:step` reads it as `backlog:next`, so a run that
2866
+ // stalled is pointed straight at dispatching the work rather than left waiting.
2867
+ STALL: "stall",
2868
+ // The run's own driver is gone: the cut handed the budget off, the owner has died, and no supervisor
2869
+ // is watching (joshuafolkken/kit#2375). Emitted once per strand episode by the stop-time detector,
2870
+ // exactly as `STALL` is — the newest-event dedup keeps a run polled every stop from notifying more
2871
+ // than once, and a terminal reader following the stream sees the line. The strand is the step before
2872
+ // the stall: a stalled run still has a driver that could dispatch, a stranded one has none.
2873
+ STRANDED: "stranded",
2874
+ // One `josh ship` stage starting, succeeding or failing (joshuafolkken/kit#2426). It is a trace of the
2875
+ // composite's progress rather than a position the run is at — the positions a ship leaves behind are
2876
+ // the closed issue its `followup` merges, or a detached supervisor's `SHIP_LAUNCH` / `SHIP_STOP` below
2877
+ // — so it is one of the `TRACE_KINDS` the last-event read skips.
2878
+ SHIP_STAGE: "ship-stage",
2879
+ // One progress watcher line — the `at … / next …` heartbeat `run:progress` prints
2880
+ // (joshuafolkken/kit#2437). Printed only to the watcher's own output, a headless successor's heartbeat
2881
+ // was buried in its log after a cut; on the stream it reaches the attached session's relay. It says
2882
+ // the run is alive rather than where it is, so it is a trace kind like `SHIP_STAGE`.
2883
+ HEARTBEAT: "heartbeat",
2884
+ // The post-implementation region handed to a detached `josh ship --detach` supervisor
2885
+ // (joshuafolkken/kit#2428). Unlike a stage trace it is a position: the agent has ended and the
2886
+ // supervisor carries the run, so `run:step` answers `wait` until it stops or the issue closes.
2887
+ SHIP_LAUNCH: "ship-launch",
2888
+ // The supervisor stopped at a failed stage — a red gate, a High/Medium review, a failed push, red CI
2889
+ // (joshuafolkken/kit#2428). The position that hands control back: `run:step` prints the command that
2890
+ // shows the stopped report, so the agent fixes it and relaunches rather than rebuilding the context.
2891
+ SHIP_STOP: "ship-stop"
2892
+ };
2893
+ var EVENT_KINDS = Object.values(EVENT_KIND);
2894
+ var TRACE_KINDS = /* @__PURE__ */ new Set([EVENT_KIND.SHIP_STAGE, EVENT_KIND.HEARTBEAT]);
2895
+ var event_schema = z7.object({
2896
+ pos: z7.number(),
2897
+ at: z7.string(),
2898
+ kind: z7.string(),
2899
+ text: z7.string()
2900
+ });
2901
+ function is_event_kind(value) {
2902
+ return EVENT_KINDS.includes(value);
2515
2903
  }
2516
- function seat_from_content(content) {
2517
- const seat = lane_environment.read_lane_seat(content);
2518
- if (seat === void 0) return void 0;
2519
- const port_pair = lane_environment.read_lane_ports(content);
2520
- return { seat, development_port: port_pair.development, preview_port: port_pair.preview };
2904
+ function target_of(repository2) {
2905
+ return stamp_file.stamp_path(EVENT_PREFIX, repository2, EVENT_SUFFIX);
2521
2906
  }
2522
- var EMPTY_SEAT = {
2523
- seat: void 0,
2524
- development_port: void 0,
2525
- preview_port: void 0
2526
- };
2527
- function held_seat(content) {
2528
- if (content === void 0) return EMPTY_SEAT;
2529
- return seat_from_content(content) ?? EMPTY_SEAT;
2907
+ function is_present(event) {
2908
+ return event !== void 0;
2530
2909
  }
2531
- function read_environment(directory) {
2532
- return file_reader.read_if_readable(path3.join(directory, ENV_FILE_NAME));
2910
+ function parse_line(line) {
2911
+ return event_schema.safeParse(json_value.parse_or_undefined(line)).data;
2533
2912
  }
2534
- function branch_issue(branch2) {
2535
- if (!branch2?.endsWith(lane_paths.LANE_BRANCH_SUFFIX)) return void 0;
2536
- const issue = branch2.slice(0, -lane_paths.LANE_BRANCH_SUFFIX.length);
2537
- return ISSUE_PATTERN.test(issue) ? issue : void 0;
2913
+ function read_events(target) {
2914
+ const raw = stamp_file.read_stamp_text(target);
2915
+ if (raw === void 0) return [];
2916
+ return raw.split(LINE_SEPARATOR).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => parse_line(line)).filter(is_present);
2538
2917
  }
2539
- function is_gone(directory) {
2540
- return !existsSync2(directory);
2918
+ function last_position(events) {
2919
+ return events[events.length - POSITION_INCREMENT]?.pos ?? FIRST_POSITION;
2541
2920
  }
2542
- function build_lane(issue, branch2, directory, is_stranded) {
2543
- const content = is_stranded ? void 0 : read_environment(directory);
2544
- const held = held_seat(content);
2545
- const output = content === void 0 ? void 0 : lane_environment.read_lane_output(content);
2546
- const profile = content === void 0 ? void 0 : lane_environment.read_lane_profile(content);
2921
+ function serialize(events) {
2922
+ return `${events.map((event) => JSON.stringify(event)).join(LINE_SEPARATOR)}${LINE_SEPARATOR}`;
2923
+ }
2924
+ function append(target, kind, text, at) {
2925
+ if (!is_event_kind(kind)) return { appended: false, position: FIRST_POSITION };
2926
+ const existing = read_events(target);
2927
+ const position = last_position(existing) + POSITION_INCREMENT;
2928
+ const bounded = [...existing, { pos: position, at, kind, text }].slice(-EVENT_CAP);
2929
+ stamp_file.write_text_stamp(target, serialize(bounded));
2930
+ return { appended: true, position };
2931
+ }
2932
+ function read_from(target, position) {
2933
+ const events = read_events(target);
2547
2934
  return {
2548
- issue,
2549
- branch: branch2,
2550
- directory,
2551
- seat: held.seat,
2552
- development_port: held.development_port,
2553
- preview_port: held.preview_port,
2554
- output,
2555
- profile,
2556
- is_stranded
2935
+ events: events.filter((event) => event.pos > position),
2936
+ next_position: last_position(events)
2557
2937
  };
2558
2938
  }
2559
- function is_lane_directory(root, issue, directory) {
2560
- return directory === lane_paths.lane_directory(root, issue);
2939
+ function read_last(target) {
2940
+ return read_events(target).findLast((event) => !TRACE_KINDS.has(event.kind));
2941
+ }
2942
+ function has_since(events, kind, reset_kind) {
2943
+ const reset_at = events.findLastIndex((event) => event.kind === reset_kind);
2944
+ return events.slice(reset_at + POSITION_INCREMENT).some((event) => event.kind === kind);
2945
+ }
2946
+ var FIELD_SEPARATOR = " \xB7 ";
2947
+ function format_event(event) {
2948
+ return [event.at, event.kind, event.text].join(FIELD_SEPARATOR);
2949
+ }
2950
+ var run_event_stream = {
2951
+ EVENT_CAP,
2952
+ EVENT_KIND,
2953
+ EVENT_KINDS,
2954
+ TRACE_KINDS,
2955
+ append,
2956
+ format_event,
2957
+ has_since,
2958
+ read_events,
2959
+ read_from,
2960
+ read_last,
2961
+ target_of
2962
+ };
2963
+
2964
+ // scripts/run/run-event-scope.ts
2965
+ var SINCE_SCOPE = "since";
2966
+ var UNKNOWN_SCOPE = "unknown";
2967
+ var UNKNOWN_EVENT_SCOPE = { kind: UNKNOWN_SCOPE };
2968
+ function moment_of(at) {
2969
+ const parsed = Date.parse(at);
2970
+ return Number.isNaN(parsed) ? void 0 : parsed;
2561
2971
  }
2562
- function parse_block(block, root) {
2563
- const lines = block.split("\n");
2564
- const directory = line_value(lines, WORKTREE_PREFIX);
2565
- const branch2 = line_value(lines, BRANCH_PREFIX);
2566
- const issue = branch_issue(branch2);
2567
- if (directory === void 0 || branch2 === void 0 || issue === void 0) return void 0;
2568
- if (!is_lane_directory(root, issue, directory)) return void 0;
2569
- return build_lane(issue, branch2, directory, is_gone(directory));
2972
+ function is_within(event, floor) {
2973
+ const at = moment_of(event.at);
2974
+ return at !== void 0 && at >= floor;
2570
2975
  }
2571
- function is_lane(lane) {
2572
- return lane !== void 0;
2976
+ function scoped_events(events, scope) {
2977
+ if (scope.kind === UNKNOWN_SCOPE) return void 0;
2978
+ const floor = moment_of(scope.started_at);
2979
+ if (floor === void 0) return void 0;
2980
+ return events.filter((event) => is_within(event, floor));
2573
2981
  }
2574
- async function worktree_blocks() {
2575
- const listing = await git_worktree.worktree_list();
2576
- return listing.split(BLOCK_SEPARATOR);
2982
+ function scope_of(read2) {
2983
+ if (read2.kind === "carried" || read2.kind === "expired") {
2984
+ return { kind: SINCE_SCOPE, started_at: read2.carry.started_at };
2985
+ }
2986
+ return UNKNOWN_EVENT_SCOPE;
2577
2987
  }
2578
- async function main_repository_root() {
2579
- return repo_discovery.main_worktree(await git_command.repository_root());
2988
+ function last_scoped_event(events, scope) {
2989
+ return scoped_events(events, scope)?.findLast(
2990
+ (event) => !run_event_stream.TRACE_KINDS.has(event.kind)
2991
+ );
2580
2992
  }
2581
- async function list_lanes() {
2582
- const root = lane_paths.lane_root(await main_repository_root());
2583
- const blocks = await worktree_blocks();
2584
- const lanes = blocks.map((block) => parse_block(block, root)).filter(is_lane);
2585
- return lanes.toSorted((left, right) => Number(left.issue) - Number(right.issue));
2993
+ var SHIP_POSITION_KINDS = /* @__PURE__ */ new Set([
2994
+ run_event_stream.EVENT_KIND.SHIP_LAUNCH,
2995
+ run_event_stream.EVENT_KIND.SHIP_STOP
2996
+ ]);
2997
+ var KIND = run_event_stream.EVENT_KIND;
2998
+ var BATCH_RECORD_KINDS = /* @__PURE__ */ new Set([
2999
+ KIND.CHILD_LAUNCH,
3000
+ KIND.PARK,
3001
+ KIND.SPLIT,
3002
+ KIND.RETROSPECTIVE,
3003
+ KIND.STRANDED
3004
+ ]);
3005
+ var ISSUE_REFERENCE = /#(\d+)/u;
3006
+ function is_foreign_ship_event(event, issue) {
3007
+ return SHIP_POSITION_KINDS.has(event.kind) && !event.text.startsWith(`#${issue} `);
3008
+ }
3009
+ function is_own_ship_event(event, issue) {
3010
+ return SHIP_POSITION_KINDS.has(event.kind) && !is_foreign_ship_event(event, issue);
3011
+ }
3012
+ function names_issue(event, issue) {
3013
+ return ISSUE_REFERENCE.exec(event.text)?.[1] === issue;
3014
+ }
3015
+ function is_lane_position(event, issue) {
3016
+ return !BATCH_RECORD_KINDS.has(event.kind) && names_issue(event, issue);
3017
+ }
3018
+ function since_own_launch(events, issue) {
3019
+ const launch3 = events.findLastIndex(
3020
+ (event) => event.kind === KIND.CHILD_LAUNCH && names_issue(event, issue)
3021
+ );
3022
+ return events.slice(launch3 + 1);
3023
+ }
3024
+ function is_foreign_event(event, issue, is_lane_child) {
3025
+ if (is_lane_child) return !is_lane_position(event, issue);
3026
+ return is_foreign_ship_event(event, issue);
3027
+ }
3028
+ function last_issue_event(events, scope, issue, is_lane_child) {
3029
+ const attempt = is_lane_child ? since_own_launch(events, issue) : events;
3030
+ const own2 = attempt.filter((event) => !is_foreign_event(event, issue, is_lane_child));
3031
+ if (scope.kind === SINCE_SCOPE) return last_scoped_event(own2, scope);
3032
+ return own2.findLast((event) => is_own_ship_event(event, issue));
3033
+ }
3034
+ var run_event_scope = {
3035
+ UNKNOWN_EVENT_SCOPE,
3036
+ last_issue_event,
3037
+ last_scoped_event,
3038
+ scope_of,
3039
+ scoped_events
3040
+ };
3041
+
3042
+ // scripts/run/run-event-stream-emit.ts
3043
+ function now_iso() {
3044
+ return (/* @__PURE__ */ new Date()).toISOString();
2586
3045
  }
2587
- function used_seats(lanes) {
2588
- return lanes.map((lane) => lane.seat).filter((seat) => seat !== void 0);
3046
+ async function stream_target() {
3047
+ const repository2 = await run_carry.repository_directory();
3048
+ return repository2 === void 0 ? void 0 : run_event_stream.target_of(repository2);
2589
3049
  }
2590
- function unreadable_lanes(lanes) {
2591
- return lanes.filter((lane) => !lane.is_stranded && lane.seat === void 0);
3050
+ async function emit(kind, text) {
3051
+ try {
3052
+ const target = await stream_target();
3053
+ if (target === void 0) return;
3054
+ run_event_stream.append(target, kind, text, now_iso());
3055
+ } catch {
3056
+ }
2592
3057
  }
2593
- function find_lane(lanes, issue) {
2594
- return lanes.find((lane) => lane.issue === issue);
3058
+ async function emit_once(kind, text) {
3059
+ try {
3060
+ const target = await stream_target();
3061
+ if (target === void 0 || run_event_stream.read_last(target)?.kind === kind) return false;
3062
+ return run_event_stream.append(target, kind, text, now_iso()).appended;
3063
+ } catch {
3064
+ return false;
3065
+ }
2595
3066
  }
2596
- async function find_open_lane(issue) {
2597
- return find_lane(await list_lanes(), issue);
3067
+ function invocation_events(repository2, target) {
3068
+ const events = run_event_stream.read_events(target);
3069
+ const scope = run_event_scope.scope_of(run_carry.read_carry(run_carry.carry_path(repository2)));
3070
+ return run_event_scope.scoped_events(events, scope) ?? events;
2598
3071
  }
2599
- var lane_registry = {
2600
- find_lane,
2601
- find_open_lane,
2602
- list_lanes,
2603
- main_repository_root,
2604
- parse_block,
2605
- read_environment,
2606
- unreadable_lanes,
2607
- used_seats
3072
+ async function emit_once_since(kind, text, reset_kind) {
3073
+ try {
3074
+ const repository2 = await run_carry.repository_directory();
3075
+ if (repository2 === void 0) return false;
3076
+ const target = run_event_stream.target_of(repository2);
3077
+ if (run_event_stream.has_since(invocation_events(repository2, target), kind, reset_kind)) {
3078
+ return false;
3079
+ }
3080
+ return run_event_stream.append(target, kind, text, now_iso()).appended;
3081
+ } catch {
3082
+ return false;
3083
+ }
3084
+ }
3085
+ async function emit_heartbeat(line) {
3086
+ await emit(run_event_stream.EVENT_KIND.HEARTBEAT, line);
3087
+ }
3088
+ async function current_events() {
3089
+ try {
3090
+ const repository2 = await run_carry.repository_directory();
3091
+ if (repository2 === void 0) return [];
3092
+ const read2 = run_carry.read_carry(run_carry.carry_path(repository2));
3093
+ const scope = run_event_scope.scope_of(read2);
3094
+ const events = run_event_stream.read_events(run_event_stream.target_of(repository2));
3095
+ return run_event_scope.scoped_events(events, scope) ?? [];
3096
+ } catch {
3097
+ return [];
3098
+ }
3099
+ }
3100
+ var run_event_stream_emit = {
3101
+ current_events,
3102
+ emit,
3103
+ emit_heartbeat,
3104
+ emit_once,
3105
+ emit_once_since,
3106
+ stream_target
2608
3107
  };
2609
3108
 
2610
- // scripts/backlog/backlog-budget-cli.ts
2611
- import { fileURLToPath } from "node:url";
2612
-
2613
- // scripts/git/changed-paths.ts
2614
- function to_paths(raw) {
2615
- return raw.split("\n").map((line) => line.trim()).filter((line) => line !== "");
3109
+ // scripts/run/run-ship-detach.ts
3110
+ var LOG_PREFIX = "josh-ship-log-";
3111
+ var PID_PREFIX = "josh-ship-pid-";
3112
+ var BODY_PREFIX = "josh-ship-notify-";
3113
+ var KEY_SEPARATOR = "-";
3114
+ var LOG_SUFFIX = ".log";
3115
+ var BODY_SUFFIX = ".txt";
3116
+ var PNPM = "pnpm";
3117
+ var INLINE_NOTIFY = "--notify-message";
3118
+ var FILE_NOTIFY = "--notify-message-file";
3119
+ var NOTIFY_VALUE_INDEX = 1;
3120
+ var SUPERVISED_KEY = "JOSH_SHIP_SUPERVISED";
3121
+ var LAUNCH_ID_KEY = "JOSH_SHIP_LAUNCH_ID";
3122
+ var SUPERVISED_VALUE = "1";
3123
+ var LAUNCHED = "launched";
3124
+ var BUSY = "busy";
3125
+ var FAILED = "failed";
3126
+ var RESULT_PREFIX = "josh-ship-result-";
3127
+ var RECORD_POLL_MS = 25;
3128
+ var RECORD_WAIT_ATTEMPTS = 200;
3129
+ var CLAIM_WINDOW_MS = 12e4;
3130
+ var ship_record_schema = z8.object({
3131
+ pid: z8.number().int().positive().optional(),
3132
+ process_start: z8.string().optional(),
3133
+ launched_at: z8.number().optional(),
3134
+ launcher_pid: z8.number().int().positive().optional(),
3135
+ launcher_start: z8.string().optional(),
3136
+ launch_id: z8.string(),
3137
+ result: z8.enum(["success", "failed"]).optional()
3138
+ });
3139
+ var LAUNCH_MARK = " \xB7 started by process ";
3140
+ var HEADER_START = "\n";
3141
+ function key(prefix, request) {
3142
+ return stamp_file.stamp_path(`${prefix}${request.number}${KEY_SEPARATOR}`, request.repository);
2616
3143
  }
2617
- async function read_changed_paths(is_staged) {
2618
- if (is_staged) return to_paths(await git_command.diff_cached_names());
2619
- const tracked = to_paths(await git_command.diff_main_names());
2620
- const untracked = to_paths(await git_command.untracked_names());
2621
- return [...tracked, ...untracked];
3144
+ function log_path(repository2, number) {
3145
+ return stamp_file.stamp_path(`${LOG_PREFIX}${number}${KEY_SEPARATOR}`, repository2, LOG_SUFFIX);
2622
3146
  }
2623
- var changed_paths = { read_changed_paths, to_paths };
2624
-
2625
- // scripts/josh/path-decision.ts
2626
- var STAGED_FLAG = "--staged";
2627
- var JSON_FLAG = "--json";
2628
- var KNOWN_FLAGS = [STAGED_FLAG, JSON_FLAG];
2629
- var FAILURE_EXIT_CODE = 1;
2630
- var MAX_LISTED_PATHS = 5;
2631
- function has_unknown_flag(argv, known) {
2632
- return argv.some((argument) => !known.includes(argument));
3147
+ function result_path(repository2, number) {
3148
+ return stamp_file.stamp_path(`${RESULT_PREFIX}${number}${KEY_SEPARATOR}`, repository2);
2633
3149
  }
2634
- function parse_options(argv) {
2635
- if (has_unknown_flag(argv, KNOWN_FLAGS)) return void 0;
2636
- return { is_staged: argv.includes(STAGED_FLAG), is_json: argv.includes(JSON_FLAG) };
3150
+ function read_record(repository2, number) {
3151
+ try {
3152
+ const raw = stamp_file.read_stamp_text(result_path(repository2, number));
3153
+ if (raw === void 0) return void 0;
3154
+ const value = JSON.parse(raw);
3155
+ const parsed = ship_record_schema.safeParse(value);
3156
+ return parsed.success ? parsed.data : void 0;
3157
+ } catch {
3158
+ return void 0;
3159
+ }
2637
3160
  }
2638
- function format_path_list(paths) {
2639
- const listed = paths.slice(0, MAX_LISTED_PATHS).join(", ");
2640
- if (paths.length <= MAX_LISTED_PATHS) return listed;
2641
- return `${listed}, +${String(paths.length - MAX_LISTED_PATHS)} more`;
3161
+ function record_launch(request, pid, launch_id) {
3162
+ stamp_file.write_text_stamp(key(PID_PREFIX, request), String(pid));
3163
+ const process_start = process_identity.read_start(pid);
3164
+ stamp_file.replace_stamp(result_path(request.repository, request.number), {
3165
+ pid,
3166
+ launch_id,
3167
+ launched_at: Date.now(),
3168
+ ...process_start !== void 0 && { process_start }
3169
+ });
2642
3170
  }
2643
- function print_decision(key, answer, reason, is_json) {
2644
- if (is_json) {
2645
- console.info(JSON.stringify({ [key]: answer, reason }));
2646
- return;
3171
+ function supervisor_environment(launch_id) {
3172
+ return {
3173
+ ...agent_role_profile.handoff_environment(),
3174
+ [SUPERVISED_KEY]: SUPERVISED_VALUE,
3175
+ [LAUNCH_ID_KEY]: launch_id
3176
+ };
3177
+ }
3178
+ function is_within_claim_window(record) {
3179
+ return record.launched_at !== void 0 && Date.now() - record.launched_at < CLAIM_WINDOW_MS;
3180
+ }
3181
+ function has_live_identity(pid, record) {
3182
+ if (record.process_start === void 0) {
3183
+ return is_within_claim_window(record) && process_identity.is_live_pid(pid);
2647
3184
  }
2648
- console.info(answer);
2649
- console.error(reason);
3185
+ return process_identity.is_same_process(pid, record.process_start) !== false;
2650
3186
  }
2651
- async function run_path_decision(argv, command) {
2652
- const options = parse_options(argv);
2653
- if (options === void 0) {
2654
- console.error(command.usage);
2655
- return FAILURE_EXIT_CODE;
3187
+ function result_of(record) {
3188
+ if (record.pid === void 0) {
3189
+ const is_launcher = process_identity.is_same_process(record.launcher_pid, record.launcher_start);
3190
+ return is_launcher === false ? "abnormal" : "running";
2656
3191
  }
2657
- const paths = await changed_paths.read_changed_paths(options.is_staged);
2658
- const answer = command.decide(paths);
2659
- print_decision(command.key, answer, command.explain(paths, answer), options.is_json);
2660
- return 0;
3192
+ if (has_live_identity(record.pid, record)) return "running";
3193
+ return record.result ?? "abnormal";
3194
+ }
3195
+ function read_result(repository2, number) {
3196
+ const record = read_record(repository2, number);
3197
+ return record === void 0 ? void 0 : { launch_id: record.launch_id, result: result_of(record) };
3198
+ }
3199
+ function current_record(repository2, number) {
3200
+ const record = read_record(repository2, number);
3201
+ return record?.launch_id === process.env[LAUNCH_ID_KEY] ? record : void 0;
3202
+ }
3203
+ function is_pending(record) {
3204
+ return record !== void 0 && record.pid === void 0;
3205
+ }
3206
+ async function ready_record(repository2, number) {
3207
+ let record = current_record(repository2, number);
3208
+ for (let attempt = 0; attempt < RECORD_WAIT_ATTEMPTS; attempt += 1) {
3209
+ if (!is_pending(record)) return record;
3210
+ await sleep(RECORD_POLL_MS);
3211
+ record = current_record(repository2, number);
3212
+ }
3213
+ return void 0;
3214
+ }
3215
+ async function update_record(repository2, number, fields) {
3216
+ const record = await ready_record(repository2, number);
3217
+ if (record === void 0) return;
3218
+ stamp_file.replace_stamp(result_path(repository2, number), { ...record, ...fields });
3219
+ }
3220
+ async function mark_result(repository2, number, code) {
3221
+ await update_record(repository2, number, { result: code === 0 ? "success" : "failed" });
3222
+ }
3223
+ async function claim_identity(repository2, number) {
3224
+ const own2 = process_identity.own_fields();
3225
+ if (own2.process_start === void 0) return;
3226
+ await update_record(repository2, number, own2);
3227
+ }
3228
+ function failed_launch(request, note) {
3229
+ stamp_file.remove_stamp(result_path(request.repository, request.number));
3230
+ return { verdict: FAILED, note };
3231
+ }
3232
+ function notify_arguments(request) {
3233
+ const [flag, value] = [request.notify[0], request.notify[NOTIFY_VALUE_INDEX]];
3234
+ if (flag !== INLINE_NOTIFY || value === void 0) return request.notify;
3235
+ const body_path = stamp_file.stamp_path(
3236
+ `${BODY_PREFIX}${request.number}${KEY_SEPARATOR}`,
3237
+ request.repository,
3238
+ BODY_SUFFIX
3239
+ );
3240
+ stamp_file.write_text_stamp(body_path, value);
3241
+ return [FILE_NOTIFY, body_path];
2661
3242
  }
2662
- var path_decision = {
2663
- format_path_list,
2664
- has_unknown_flag,
2665
- JSON_FLAG,
2666
- KNOWN_FLAGS,
2667
- MAX_LISTED_PATHS,
2668
- parse_options,
2669
- print_decision,
2670
- run_path_decision,
2671
- STAGED_FLAG
3243
+ function supervisor_argv(request) {
3244
+ const review = request.is_review ? ["--review"] : [];
3245
+ const cites = request.cites.flatMap((cite) => ["--cite", cite]);
3246
+ return {
3247
+ command: PNPM,
3248
+ args: [
3249
+ "josh",
3250
+ "ship",
3251
+ ...review,
3252
+ ...notify_arguments(request),
3253
+ ...request.body,
3254
+ ...cites,
3255
+ request.title
3256
+ ]
3257
+ };
3258
+ }
3259
+ function prepare_launch(request) {
3260
+ const launch_id = randomUUID2();
3261
+ const launcher = process_identity.own_fields();
3262
+ stamp_file.replace_stamp(result_path(request.repository, request.number), {
3263
+ launch_id,
3264
+ launcher_pid: launcher.pid,
3265
+ ...launcher.process_start !== void 0 && { launcher_start: launcher.process_start }
3266
+ });
3267
+ return launch_id;
3268
+ }
3269
+ function launch2(request) {
3270
+ const notes = [];
3271
+ const log = log_path(request.repository, request.number);
3272
+ const launch_id = prepare_launch(request);
3273
+ const result = detached_launch.launch(
3274
+ {
3275
+ argv: supervisor_argv(request),
3276
+ cwd: request.cwd,
3277
+ log_path: log,
3278
+ // The provider travels as a mark because the launch strips the session keys that name it, and
3279
+ // `ship --review` resolves its reviewer from it (joshuafolkken/kit#2456).
3280
+ env: supervisor_environment(launch_id)
3281
+ },
3282
+ (note) => {
3283
+ notes.push(note);
3284
+ }
3285
+ );
3286
+ if (result.kind === "failed") return failed_launch(request, result.note);
3287
+ record_launch(request, result.pid, launch_id);
3288
+ return {
3289
+ verdict: LAUNCHED,
3290
+ note: [`supervisor ${String(result.pid)} \u2014 log: ${log}`, ...notes].join("\n")
3291
+ };
3292
+ }
3293
+ async function detach(request) {
3294
+ if (read_result(request.repository, request.number)?.result === "running") {
3295
+ return { verdict: BUSY, note: `a ship supervisor for #${request.number} is already running` };
3296
+ }
3297
+ const result = launch2(request);
3298
+ if (result.verdict === LAUNCHED) {
3299
+ await run_event_stream_emit.emit(
3300
+ run_event_stream.EVENT_KIND.SHIP_LAUNCH,
3301
+ `#${request.number} ship supervisor launched`
3302
+ );
3303
+ }
3304
+ return result;
3305
+ }
3306
+ function last_launch(text) {
3307
+ const mark = text.lastIndexOf(LAUNCH_MARK);
3308
+ if (mark === -1) return text;
3309
+ return text.slice(text.lastIndexOf(HEADER_START, mark) + HEADER_START.length);
3310
+ }
3311
+ function read_log(repository2, number) {
3312
+ const text = stamp_file.read_stamp_text(log_path(repository2, number));
3313
+ return text === void 0 ? void 0 : last_launch(text);
3314
+ }
3315
+ function is_supervised(source = process.env) {
3316
+ return source[SUPERVISED_KEY] === SUPERVISED_VALUE;
3317
+ }
3318
+ var run_ship_detach = {
3319
+ BUSY,
3320
+ FAILED,
3321
+ LAUNCHED,
3322
+ LAUNCH_ID_KEY,
3323
+ claim_identity,
3324
+ mark_result,
3325
+ read_result,
3326
+ SUPERVISED_KEY,
3327
+ detach,
3328
+ is_supervised,
3329
+ last_launch,
3330
+ log_path,
3331
+ read_log,
3332
+ supervisor_argv
2672
3333
  };
2673
3334
 
2674
- // scripts/lib/cli-flags.ts
2675
- import { parseArgs } from "node:util";
2676
- function refuse_unknown_flags(argv, known_flags, command) {
2677
- const unknown = argv.filter((argument) => !known_flags.includes(argument));
2678
- if (unknown.length === 0) return void 0;
2679
- const usage = `Usage: josh ${command} [${known_flags.join("] [")}]`;
2680
- return `Unknown argument(s): ${unknown.join(" ")}
2681
- ${usage}`;
3335
+ // scripts/lane/lane-handoff.ts
3336
+ function is_ship_running(issue, directory) {
3337
+ const repository2 = git_common_directory.repository(directory);
3338
+ return repository2 !== void 0 && run_ship_detach.read_result(repository2, issue)?.result === "running";
2682
3339
  }
2683
- function parse_or_undefined(config) {
2684
- try {
2685
- return parseArgs(config);
2686
- } catch {
2687
- return void 0;
2688
- }
3340
+ function is_handed_off(directory, source = process.env) {
3341
+ const issue = lane_child_marker.marked_issue(source);
3342
+ if (issue === void 0 || !lane_child_marker.is_child_of(directory, source)) return false;
3343
+ return is_ship_running(issue, directory);
2689
3344
  }
2690
- var cli_flags = { refuse_unknown_flags, parse_or_undefined };
3345
+ var lane_handoff = { is_handed_off, is_ship_running };
2691
3346
 
2692
- // scripts/backlog/backlog-budget-cli.ts
2693
- var ARGV_OFFSET = 2;
2694
- var SUCCESS_EXIT_CODE = 0;
2695
- var FAILURE_EXIT_CODE2 = 1;
2696
- var JSON_KEY = "budget";
2697
- var USAGE = `Usage: josh backlog:budget --answer <${backlog_budget.ANSWERS.join("|")}> --started <ISO-8601> [--active <ISO-8601>, required unless the watch is off] [--merged <count>] [--running <count>] [--max <count>] [--idle <minutes>, 0 to turn the watch off] [--json]`;
2698
- var OPTIONS = {
2699
- active: { type: "string" },
2700
- answer: { type: "string" },
2701
- idle: { type: "string" },
2702
- json: { type: "boolean" },
2703
- max: { type: "string" },
2704
- merged: { type: "string" },
2705
- running: { type: "string" },
2706
- started: { type: "string" }
3347
+ // scripts/issue/issue-labels.ts
3348
+ var EPIC_LABEL = "epic";
3349
+ var ENHANCEMENT_LABEL = "enhancement";
3350
+ var BREAKING_CHANGE_LABEL = "breaking-change";
3351
+ var IN_PROGRESS_LABEL = "in-progress";
3352
+ var PLANNED_LABEL = "run:planned";
3353
+ var NEEDS_DECISION_LABEL = "needs-decision";
3354
+ var RUN_SOLO_LABEL = "run:solo";
3355
+ var RUN_LANE_LABEL = "run:lane";
3356
+ var PRIORITY_HIGH_LABEL = "priority:high";
3357
+ var NEEDS_HUMAN_REVIEW_LABEL = "needs-human-review";
3358
+ var ALREADY_DONE_LABEL = "already-done";
3359
+ var INTERRUPT_ROUTE_LABEL = "route:interrupt";
3360
+ var REVIEW_CAP_ROUTE_LABEL = "route:review-cap";
3361
+ var SPLIT_ROUTE_LABEL = "route:split";
3362
+ var TIER_A_ROUTE_LABEL = "route:tier-a";
3363
+ var FILING_ROUTE_LABELS = [
3364
+ {
3365
+ name: INTERRUPT_ROUTE_LABEL,
3366
+ // Not `5319e7`, which is the `epic` label's purple: in a listing an interrupt would render as
3367
+ // an epic, and the two are read at a glance rather than by name.
3368
+ color: "1d76db",
3369
+ // Keyed to the tests rather than to severity. "A serious defect" is the self-assessment
3370
+ // `wip-cap.md` bans as a criterion, and this string is what a person reads in `gh label list`.
3371
+ description: "Filed past the WIP cap \u2014 meets one of the three interrupt tests (wip-cap.md)"
3372
+ },
3373
+ {
3374
+ name: REVIEW_CAP_ROUTE_LABEL,
3375
+ color: "eab308",
3376
+ description: "Filed by the review round cap's carry-forward (prompts/review.md \u2192 Review round cap)"
3377
+ },
3378
+ {
3379
+ name: SPLIT_ROUTE_LABEL,
3380
+ color: "0e8a16",
3381
+ description: "A child issue created by a split (split-assessment.md)"
3382
+ },
3383
+ {
3384
+ name: TIER_A_ROUTE_LABEL,
3385
+ color: "d93f0b",
3386
+ description: "Filed Tier A during implementation \u2014 an upstream defect or a prerequisite"
3387
+ }
3388
+ ];
3389
+ var DEPTH_0_LABEL = "depth:0";
3390
+ var DEPTH_1_LABEL = "depth:1";
3391
+ var DEPTH_2_LABEL = "depth:2";
3392
+ var DEPTH_LABELS = [
3393
+ {
3394
+ name: DEPTH_0_LABEL,
3395
+ color: "0e8a16",
3396
+ description: "Depth 0 \u2014 what a consumer of this package touches (SKILL.md \xA72i)"
3397
+ },
3398
+ {
3399
+ name: DEPTH_1_LABEL,
3400
+ color: "fbc02d",
3401
+ description: "Depth 1 \u2014 the run orchestration that executes an Issue (SKILL.md \xA72i)"
3402
+ },
3403
+ {
3404
+ name: DEPTH_2_LABEL,
3405
+ color: "c5def5",
3406
+ description: "Depth 2 \u2014 what measures a run (SKILL.md \xA72i)"
3407
+ }
3408
+ ];
3409
+ var WORKFLOW_LABELS = [
3410
+ {
3411
+ name: EPIC_LABEL,
3412
+ color: "5319e7",
3413
+ description: "Tracks a batch of child issues from one split"
3414
+ },
3415
+ { name: IN_PROGRESS_LABEL, color: "0075ca", description: "Work is actively in progress" },
3416
+ { name: PLANNED_LABEL, color: "c2e0c6", description: "kickoff posted the plan" },
3417
+ {
3418
+ name: RUN_SOLO_LABEL,
3419
+ color: "b60205",
3420
+ description: "Runs alone in a backlogrun: nothing starts beside it"
3421
+ },
3422
+ {
3423
+ name: RUN_LANE_LABEL,
3424
+ color: "0e8a16",
3425
+ description: "Judged safe to run beside others in a backlogrun"
3426
+ },
3427
+ {
3428
+ name: PRIORITY_HIGH_LABEL,
3429
+ color: "e99695",
3430
+ description: "Offered first in a backlogrun; only a person removes it"
3431
+ },
3432
+ ...FILING_ROUTE_LABELS,
3433
+ ...DEPTH_LABELS
3434
+ ];
3435
+ var BUGFIX_LABEL = "bugfix";
3436
+ var OTHER_CHANGE_LABEL = "other-change";
3437
+ var IGNORE_FOR_RELEASE_LABEL = "ignore-for-release";
3438
+ var RELEASE_CLASSIFICATION_NAMES = [
3439
+ BREAKING_CHANGE_LABEL,
3440
+ ENHANCEMENT_LABEL,
3441
+ BUGFIX_LABEL,
3442
+ OTHER_CHANGE_LABEL,
3443
+ IGNORE_FOR_RELEASE_LABEL
3444
+ ];
3445
+ var RELEASE_CLASSIFICATION_METADATA = {
3446
+ [BREAKING_CHANGE_LABEL]: { color: "d93f0b", description: "Release: a breaking change (major)" },
3447
+ [ENHANCEMENT_LABEL]: { color: "84b6eb", description: "Release: a new feature (minor)" },
3448
+ [BUGFIX_LABEL]: { color: "d73a4a", description: "Release: a bug fix (patch)" },
3449
+ [OTHER_CHANGE_LABEL]: { color: "c2e0c6", description: "Release: any other change (patch)" },
3450
+ [IGNORE_FOR_RELEASE_LABEL]: { color: "cfd3d7", description: "Release: left out of the notes" }
2707
3451
  };
2708
- function read_arguments(argv) {
2709
- return cli_flags.parse_or_undefined({ args: [...argv], options: OPTIONS, strict: true })?.values;
2710
- }
2711
- function text_of(value) {
2712
- return typeof value === "string" ? value : void 0;
3452
+ var RELEASE_CLASSIFICATION_LABELS = RELEASE_CLASSIFICATION_NAMES.map((name) => ({ name, ...RELEASE_CLASSIFICATION_METADATA[name] }));
3453
+ var REPOSITORY_LABELS = [
3454
+ ...WORKFLOW_LABELS,
3455
+ ...RELEASE_CLASSIFICATION_LABELS
3456
+ ];
3457
+ function has_any_label(labels, wanted) {
3458
+ return (labels ?? []).some((label2) => wanted.has(label2.name.toLowerCase()));
2713
3459
  }
2714
- function to_answer(raw) {
2715
- return backlog_budget.ANSWERS.find((answer) => answer === raw);
3460
+ function label_name_of(labels, wanted) {
3461
+ const target = wanted.toLowerCase();
3462
+ return labels.find((label2) => label2.toLowerCase() === target);
2716
3463
  }
2717
- function to_time_ms(raw) {
2718
- if (raw === void 0) return void 0;
2719
- const parsed = Date.parse(raw);
2720
- return Number.isNaN(parsed) ? void 0 : parsed;
3464
+ function has_label_name(labels, wanted) {
3465
+ return label_name_of(labels, wanted) !== void 0;
2721
3466
  }
2722
- var COUNT_PATTERN = /^\d+$/u;
2723
- function to_count(raw) {
2724
- if (raw === void 0 || !COUNT_PATTERN.test(raw)) return void 0;
2725
- const parsed = Number(raw);
2726
- return Number.isSafeInteger(parsed) ? parsed : void 0;
3467
+
3468
+ // scripts/rules/lane-park.ts
3469
+ var NOTIFY_COMMANDS = /* @__PURE__ */ new Set(["notify"]);
3470
+ var CONFIRMATION_TASK_TYPE = /(?:^|\s)--task-type[=\s]+["']?confirmation\b/u;
3471
+ function is_confirmation_notify(command) {
3472
+ return shell_segments.segments_of(command).some(
3473
+ (segment) => shell_segments.is_josh_command(segment, NOTIFY_COMMANDS) && CONFIRMATION_TASK_TYPE.test(segment)
3474
+ );
2727
3475
  }
2728
- function is_readable(raw, parsed) {
2729
- return raw === void 0 || parsed !== void 0;
3476
+ var NOTIFY_INVOCATION = /josh\s+(?:notify|nf)\b/u;
3477
+ function mentions_confirmation_notify(text) {
3478
+ return NOTIFY_INVOCATION.test(text) && CONFIRMATION_TASK_TYPE.test(text);
2730
3479
  }
2731
- var COUNT_NAMES = ["idle", "max", "merged", "running"];
2732
- function counts_of(values) {
2733
- return {
2734
- idle: to_count(text_of(values.idle)),
2735
- max: to_count(text_of(values.max)),
2736
- merged: to_count(text_of(values.merged)),
2737
- running: to_count(text_of(values.running))
2738
- };
3480
+ function is_parking_child(directory = process.cwd()) {
3481
+ return lane_child_marker.is_child_of(directory) && !lane_handoff.is_handed_off(directory);
2739
3482
  }
2740
- function are_counts_readable(values, counts) {
2741
- return COUNT_NAMES.every((name) => is_readable(text_of(values[name]), counts[name]));
3483
+ function is_unparked_stop(command, is_child = is_parking_child) {
3484
+ if (!is_confirmation_notify(command)) return false;
3485
+ return is_child();
2742
3486
  }
2743
- var NO_IDLE_MINUTES = 0;
2744
- function to_idle_ms(idle_minutes) {
2745
- if (idle_minutes === void 0) return backlog_budget.DEFAULT_IDLE_MS;
2746
- if (idle_minutes === NO_IDLE_MINUTES) return void 0;
2747
- return idle_minutes * backlog_budget.MS_PER_MINUTE;
3487
+ var APPLIES_NEEDS_DECISION = new RegExp(
3488
+ String.raw`(?:labels\[\]=|--add-label[= ]'?)${NEEDS_DECISION_LABEL}\b`,
3489
+ "u"
3490
+ );
3491
+ function records_the_park(command) {
3492
+ return APPLIES_NEEDS_DECISION.test(command);
2748
3493
  }
2749
- function is_watch_readable(values) {
2750
- if (to_idle_ms(to_count(text_of(values.idle))) === void 0) return true;
2751
- return text_of(values.active) !== void 0;
3494
+ var LANE_PARK_REASON = '\u26D4 lane child park: this session is a dispatched lane child, and a stop that waits on a person must leave the question on the Issue before it sends this Telegram \u2014 otherwise the parent finds an OPEN Issue with `in-progress` and no question, and a person cannot tell what to answer (joshuafolkken/kit#2034 measured this twice in one run). The child is the only process that knows the question, the options and whether it stashed, so it records the park itself rather than leaving the parent to guess from a log. Before you notify and stop: apply `needs-decision` and post a comment carrying the question, the options, and whether work was stashed \u2014 `gh api repos/{owner}/{repo}/issues/<N>/labels -f \'labels[]=needs-decision\'` then `pnpm josh issue:comment <N> --body-file <path>`. If this stop is a `needs-human-review` or `already-done` one, that label is already on the Issue and there is nothing to add. Then reissue this notify \u2014 it fires once per run and cannot repeat on the call in hand. The procedure is `.claude/skills/workflow-commands/pre-gate-cut.md` \u2192 "A lane child records its park before it stops", and the park itself is `backlogrun-park.md` \u2192 "park and continue".';
3495
+ var lane_park = {
3496
+ LANE_PARK_REASON,
3497
+ is_confirmation_notify,
3498
+ is_parking_child,
3499
+ is_unparked_stop,
3500
+ mentions_confirmation_notify,
3501
+ records_the_park
3502
+ };
3503
+
3504
+ // scripts/discovery/repo-discovery.ts
3505
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
3506
+ import path4 from "node:path";
3507
+
3508
+ // scripts/discovery/repo-origin.ts
3509
+ var GITHUB_HOST = "github.com";
3510
+ var SSH_ALIAS_PATTERN = /^github(?:-\w+)*$/u;
3511
+ var GIT_SUFFIX = ".git";
3512
+ var PATH_SEPARATOR2 = "/";
3513
+ var USER_SEPARATOR = "@";
3514
+ var HOST_SEPARATOR = ":";
3515
+ var OWNER_INDEX = 0;
3516
+ var REPO_INDEX = 1;
3517
+ var REPO_PATH_SEGMENTS = 2;
3518
+ var NOT_FOUND2 = -1;
3519
+ var SECTION_PREFIX = "[";
3520
+ var ORIGIN_SECTION = /^\[remote\s+"origin"\]$/u;
3521
+ var URL_KEY = "url";
3522
+ function is_github_hostname(host) {
3523
+ return host.toLowerCase() === GITHUB_HOST;
2752
3524
  }
2753
- function are_values_readable(values) {
2754
- return is_readable(text_of(values.active), to_time_ms(text_of(values.active))) && is_watch_readable(values) && are_counts_readable(values, counts_of(values));
3525
+ function is_github_ssh_host(host) {
3526
+ const normalized = host.toLowerCase();
3527
+ return is_github_hostname(normalized) || SSH_ALIAS_PATTERN.test(normalized);
2755
3528
  }
2756
- function input_of(values, base) {
2757
- const counts = counts_of(values);
2758
- return {
2759
- ...base,
2760
- active_at_ms: to_time_ms(text_of(values.active)) ?? base.started_at_ms,
2761
- merged: counts.merged ?? 0,
2762
- running: counts.running ?? 0,
2763
- max_issues: counts.max,
2764
- idle_budget_ms: to_idle_ms(counts.idle)
2765
- };
3529
+ function strip_git_suffix(name) {
3530
+ return name.endsWith(GIT_SUFFIX) ? name.slice(0, -GIT_SUFFIX.length) : name;
2766
3531
  }
2767
- function build_input(values, now_ms) {
2768
- const answer = to_answer(text_of(values.answer));
2769
- if (answer === void 0) return void 0;
2770
- const started_at_ms = to_time_ms(text_of(values.started));
2771
- if (started_at_ms === void 0) return void 0;
2772
- if (!are_values_readable(values)) return void 0;
2773
- return input_of(values, { answer, now_ms, started_at_ms });
3532
+ function to_identity(repository_path) {
3533
+ const segments = repository_path.split(PATH_SEPARATOR2).filter((segment) => segment !== "");
3534
+ if (segments.length !== REPO_PATH_SEGMENTS) return void 0;
3535
+ const owner = segments[OWNER_INDEX] ?? "";
3536
+ const repo = strip_git_suffix(segments[REPO_INDEX] ?? "");
3537
+ return repo === "" ? void 0 : { owner, repo };
2774
3538
  }
2775
- function refuse() {
2776
- console.error(USAGE);
2777
- return FAILURE_EXIT_CODE2;
3539
+ function parse_scp_like(url) {
3540
+ const user_end = url.indexOf(USER_SEPARATOR);
3541
+ const authority = user_end === NOT_FOUND2 ? url : url.slice(user_end + 1);
3542
+ const host_end = authority.indexOf(HOST_SEPARATOR);
3543
+ if (host_end === NOT_FOUND2) return void 0;
3544
+ if (!is_github_ssh_host(authority.slice(0, host_end))) return void 0;
3545
+ return to_identity(authority.slice(host_end + 1));
2778
3546
  }
2779
- function run(argv, now_ms = Date.now()) {
2780
- const values = read_arguments(argv);
2781
- if (values === void 0) return refuse();
2782
- const input = build_input(values, now_ms);
2783
- if (input === void 0) return refuse();
2784
- const { verdict, reason } = backlog_budget.decide(input);
2785
- if (values.json === true) {
2786
- console.info(
2787
- JSON.stringify({ [JSON_KEY]: verdict, reason, is_finish: backlog_budget.is_finish(input) })
2788
- );
2789
- return SUCCESS_EXIT_CODE;
3547
+ function parse_url_like(url) {
3548
+ try {
3549
+ const parsed = new URL(url);
3550
+ return is_github_hostname(parsed.hostname) ? to_identity(parsed.pathname) : void 0;
3551
+ } catch {
3552
+ return void 0;
2790
3553
  }
2791
- path_decision.print_decision(JSON_KEY, verdict, reason, false);
2792
- return SUCCESS_EXIT_CODE;
2793
3554
  }
2794
- function main(argv) {
2795
- process.exitCode = run(argv);
3555
+ function parse_origin_url(url) {
3556
+ const trimmed2 = url.trim();
3557
+ if (trimmed2 === "") return void 0;
3558
+ return parse_url_like(trimmed2) ?? parse_scp_like(trimmed2);
2796
3559
  }
2797
- var backlog_budget_cli = {
2798
- JSON_KEY,
2799
- OPTIONS,
2800
- SUCCESS_EXIT_CODE,
2801
- USAGE,
2802
- build_input,
2803
- main,
2804
- read_arguments,
2805
- run,
2806
- to_answer,
2807
- to_count,
2808
- to_time_ms
3560
+ function is_section_header(line) {
3561
+ return line.startsWith(SECTION_PREFIX);
3562
+ }
3563
+ function origin_section_lines(lines) {
3564
+ const start = lines.findIndex((line) => ORIGIN_SECTION.test(line));
3565
+ if (start === NOT_FOUND2) return [];
3566
+ const rest = lines.slice(start + 1);
3567
+ const end = rest.findIndex((line) => is_section_header(line));
3568
+ return end === NOT_FOUND2 ? rest : rest.slice(0, end);
3569
+ }
3570
+ function entry_value(line, key2) {
3571
+ const separator = line.indexOf("=");
3572
+ if (separator === NOT_FOUND2) return void 0;
3573
+ if (line.slice(0, separator).trim() !== key2) return void 0;
3574
+ return line.slice(separator + 1).trim();
3575
+ }
3576
+ function parse_origin_from_config(content) {
3577
+ const lines = content.split("\n").map((line) => line.trim());
3578
+ return origin_section_lines(lines).map((line) => entry_value(line, URL_KEY)).find((url) => url !== void 0);
3579
+ }
3580
+ function format_identity(identity) {
3581
+ return `${identity.owner}${PATH_SEPARATOR2}${identity.repo}`.toLowerCase();
3582
+ }
3583
+ var repo_origin = {
3584
+ GITHUB_HOST,
3585
+ is_github_hostname,
3586
+ is_github_ssh_host,
3587
+ parse_origin_from_config,
3588
+ parse_origin_url,
3589
+ format_identity
2809
3590
  };
2810
- if (process.argv[1] === fileURLToPath(import.meta.url)) main(process.argv.slice(ARGV_OFFSET));
2811
3591
 
2812
- // scripts/run/run-invocation.ts
2813
- var BACKLOG_COMMAND = "backlogrun";
2814
- var KNOWN_FLAGS2 = ["--max", "--idle"];
2815
- var ONLY_FLAG = "--only";
2816
- var ISSUE_PREFIX = "#";
2817
- var TOKEN_SEPARATOR = " ";
2818
- var FIRST_TOKEN = 0;
2819
- var FIRST_ARGUMENT_INDEX = 1;
2820
- var VALUE_OFFSET = 1;
2821
- var FLAG_PAIR_LENGTH = 2;
2822
- var ONLY_STEP = 1;
2823
- var EMPTY_LENGTH2 = 0;
2824
- var NOT_FOUND2 = -1;
2825
- function tokens_of(invocation) {
2826
- return invocation.split(TOKEN_SEPARATOR).filter((token) => token.length > EMPTY_LENGTH2);
3592
+ // scripts/discovery/repo-map-logic.ts
3593
+ var OVERRIDE_ENTRY_SEPARATOR = ",";
3594
+ var OVERRIDE_PAIR_SEPARATOR = "=";
3595
+ var OVERRIDE_PAIR_PARTS = 2;
3596
+ var KEY_INDEX = 0;
3597
+ var PATH_INDEX = 1;
3598
+ function is_same_owner(identity, current_owner2) {
3599
+ return identity.owner.toLowerCase() === current_owner2.toLowerCase();
2827
3600
  }
2828
- function flag_token(token, raw) {
2829
- const flag = KNOWN_FLAGS2.find((known) => known === token);
2830
- const value = backlog_budget_cli.to_count(raw);
2831
- if (flag === void 0 || value === void 0) return void 0;
2832
- return `${flag} ${String(value)}`;
3601
+ function add_entry(target, origin_url, repository_path, current_owner2) {
3602
+ if (origin_url === void 0) return;
3603
+ const identity = repo_origin.parse_origin_url(origin_url);
3604
+ if (identity === void 0 || !is_same_owner(identity, current_owner2)) return;
3605
+ target.set(repo_origin.format_identity(identity), repository_path);
2833
3606
  }
2834
- function read_flag(tokens, index) {
2835
- const token = tokens[index] ?? "";
2836
- if (token === ONLY_FLAG) return { part: ONLY_FLAG, advance: ONLY_STEP };
2837
- const part = flag_token(token, tokens[index + VALUE_OFFSET]);
2838
- return part === void 0 ? void 0 : { part, advance: FLAG_PAIR_LENGTH };
3607
+ function parse_override_entry(entry) {
3608
+ const parts = entry.split(OVERRIDE_PAIR_SEPARATOR).map((part) => part.trim());
3609
+ if (parts.length !== OVERRIDE_PAIR_PARTS) return void 0;
3610
+ const key2 = parts[KEY_INDEX] ?? "";
3611
+ const repository_path = parts[PATH_INDEX] ?? "";
3612
+ if (repository_path === "") return void 0;
3613
+ return { path: repository_path, origin_url: `https://${repo_origin.GITHUB_HOST}/${key2}` };
2839
3614
  }
2840
- function flag_tokens(tokens, start) {
2841
- const rendered = [];
2842
- let index = start;
2843
- while (index < tokens.length) {
2844
- const read2 = read_flag(tokens, index);
2845
- if (read2 === void 0) return void 0;
2846
- rendered.push(read2.part);
2847
- index += read2.advance;
3615
+ function parse_overrides(raw) {
3616
+ if (raw === void 0) return [];
3617
+ return raw.split(OVERRIDE_ENTRY_SEPARATOR).map((entry) => parse_override_entry(entry)).filter((entry) => entry !== void 0);
3618
+ }
3619
+ function build_repository_map(discovered, current_owner2, override_value2) {
3620
+ const result = /* @__PURE__ */ new Map();
3621
+ for (const entry of discovered) add_entry(result, entry.origin_url, entry.path, current_owner2);
3622
+ for (const entry of parse_overrides(override_value2)) {
3623
+ add_entry(result, entry.origin_url, entry.path, current_owner2);
2848
3624
  }
2849
- return rendered;
3625
+ return result;
2850
3626
  }
2851
- function issue_of(token) {
2852
- if (!token.startsWith(ISSUE_PREFIX)) return void 0;
2853
- const digits = token.slice(ISSUE_PREFIX.length);
2854
- if (!run_issue_number.ISSUE_NUMBER_PATTERN.test(digits)) return void 0;
2855
- const issue = Number(digits);
2856
- return Number.isSafeInteger(issue) ? issue : void 0;
3627
+ var repo_map_logic = {
3628
+ is_same_owner,
3629
+ build_repository_map
3630
+ };
3631
+
3632
+ // scripts/discovery/repo-discovery.ts
3633
+ var GIT_DIRECTORY = ".git";
3634
+ var GIT_CONFIG = "config";
3635
+ var GITDIR_PREFIX = "gitdir:";
3636
+ var COMMON_DIRECTORY_FILE = "commondir";
3637
+ var OVERRIDE_ENV_KEY = "JOSH_REPO_PATHS";
3638
+ function resolve_common_directory(git_directory) {
3639
+ const pointer = path4.join(git_directory, COMMON_DIRECTORY_FILE);
3640
+ if (!existsSync2(pointer)) return git_directory;
3641
+ try {
3642
+ return path4.resolve(git_directory, readFileSync3(pointer, "utf8").trim());
3643
+ } catch {
3644
+ return git_directory;
3645
+ }
2857
3646
  }
2858
- function take_leading_prefixed(args) {
2859
- const stop = args.findIndex((token) => !token.startsWith(ISSUE_PREFIX));
2860
- return args.slice(FIRST_TOKEN, stop === NOT_FOUND2 ? args.length : stop);
3647
+ function follow_git_file(repository_path, git_file) {
3648
+ const pointer = readFileSync3(git_file, "utf8").trim();
3649
+ if (!pointer.startsWith(GITDIR_PREFIX)) return void 0;
3650
+ return path4.resolve(repository_path, pointer.slice(GITDIR_PREFIX.length).trim());
2861
3651
  }
2862
- function leading_issues(tokens) {
2863
- const issues = [];
2864
- const prefixed = take_leading_prefixed(tokens.slice(FIRST_ARGUMENT_INDEX));
2865
- for (const token of prefixed) {
2866
- const issue = issue_of(token);
2867
- if (issue === void 0) return void 0;
2868
- issues.push(issue);
3652
+ function resolve_git_directory(repository_path) {
3653
+ const candidate = path4.join(repository_path, GIT_DIRECTORY);
3654
+ if (!existsSync2(candidate)) return void 0;
3655
+ try {
3656
+ if (statSync(candidate).isDirectory()) return candidate;
3657
+ const linked = follow_git_file(repository_path, candidate);
3658
+ return linked === void 0 ? void 0 : resolve_common_directory(linked);
3659
+ } catch {
3660
+ return void 0;
2869
3661
  }
2870
- return issues;
2871
3662
  }
2872
- function issue_token(issue) {
2873
- return `${ISSUE_PREFIX}${String(issue)}`;
3663
+ function read_origin_url(repository_path) {
3664
+ const git_directory = resolve_git_directory(repository_path);
3665
+ if (git_directory === void 0) return void 0;
3666
+ try {
3667
+ return repo_origin.parse_origin_from_config(
3668
+ readFileSync3(path4.join(git_directory, GIT_CONFIG), "utf8")
3669
+ );
3670
+ } catch {
3671
+ return void 0;
3672
+ }
2874
3673
  }
2875
- function joined(command, rendered) {
2876
- return [command, ...rendered].join(TOKEN_SEPARATOR);
3674
+ function sibling_directories(parent) {
3675
+ try {
3676
+ return readdirSync(parent, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path4.join(parent, entry.name)).toSorted((left, right) => left.localeCompare(right));
3677
+ } catch {
3678
+ return [];
3679
+ }
2877
3680
  }
2878
- function rebuilt_backlog(tokens) {
2879
- const issues = leading_issues(tokens);
2880
- if (issues === void 0) return void 0;
2881
- const flags = flag_tokens(tokens, FIRST_ARGUMENT_INDEX + issues.length);
2882
- if (flags === void 0) return void 0;
2883
- return joined(BACKLOG_COMMAND, [...issues.map((issue) => issue_token(issue)), ...flags]);
3681
+ function scan_siblings(parent) {
3682
+ return sibling_directories(parent).map((repository_path) => ({
3683
+ path: repository_path,
3684
+ origin_url: read_origin_url(repository_path)
3685
+ }));
2884
3686
  }
2885
- function rebuild(invocation) {
2886
- const tokens = tokens_of(invocation);
2887
- if (tokens[FIRST_TOKEN] !== BACKLOG_COMMAND) return void 0;
2888
- return rebuilt_backlog(tokens);
3687
+ function resolve_current_owner(repository_path) {
3688
+ const origin_url = read_origin_url(repository_path);
3689
+ if (origin_url === void 0) return void 0;
3690
+ return repo_origin.parse_origin_url(origin_url)?.owner;
2889
3691
  }
2890
- function issue_numbers(invocation) {
2891
- const tokens = tokens_of(invocation);
2892
- if (tokens[FIRST_TOKEN] !== BACKLOG_COMMAND) return void 0;
2893
- const issues = leading_issues(tokens);
2894
- if (issues === void 0 || issues.length === EMPTY_LENGTH2) return void 0;
2895
- return issues;
3692
+ function main_worktree(repository_path) {
3693
+ const git_directory = resolve_git_directory(repository_path);
3694
+ if (git_directory === void 0) return repository_path;
3695
+ if (path4.basename(git_directory) !== GIT_DIRECTORY) return repository_path;
3696
+ return path4.dirname(git_directory);
2896
3697
  }
2897
- function has_only(invocation) {
2898
- const tokens = tokens_of(invocation);
2899
- if (tokens[FIRST_TOKEN] !== BACKLOG_COMMAND) return false;
2900
- return tokens.slice(FIRST_ARGUMENT_INDEX).includes(ONLY_FLAG);
3698
+ function discover_repositories(repository_path, environment2 = process.env) {
3699
+ const main2 = main_worktree(repository_path);
3700
+ const current_owner2 = resolve_current_owner(main2);
3701
+ if (current_owner2 === void 0) return /* @__PURE__ */ new Map();
3702
+ return repo_map_logic.build_repository_map(
3703
+ scan_siblings(path4.dirname(main2)),
3704
+ current_owner2,
3705
+ environment2[OVERRIDE_ENV_KEY]
3706
+ );
2901
3707
  }
2902
- var run_invocation = {
2903
- has_only,
2904
- issue_numbers,
2905
- rebuild
3708
+ var repo_discovery = {
3709
+ OVERRIDE_ENV_KEY,
3710
+ main_worktree,
3711
+ read_origin_url,
3712
+ resolve_current_owner,
3713
+ scan_siblings,
3714
+ discover_repositories
2906
3715
  };
2907
3716
 
2908
- // scripts/run/run-carry.ts
2909
- import { z as z5 } from "zod";
2910
-
2911
- // scripts/run/run-carry-streak.ts
2912
- var NO_INCREMENT = 0;
2913
- var OUTAGE_FOLD_WINDOW_MS = 12e4;
2914
- function has_increment(value) {
2915
- return (value ?? NO_INCREMENT) > NO_INCREMENT;
3717
+ // scripts/discovery/repo-party.ts
3718
+ var FIRST_PARTY = "first-party";
3719
+ var THIRD_PARTY = "third-party";
3720
+ var UNKNOWN = "unknown";
3721
+ function classify4(session_owner, target) {
3722
+ if (session_owner === void 0 || target === void 0) return UNKNOWN;
3723
+ return session_owner.toLowerCase() === target.toLowerCase() ? FIRST_PARTY : THIRD_PARTY;
2916
3724
  }
2917
- function next_failures(carry, change) {
2918
- if (has_increment(change.merged)) return NO_INCREMENT;
2919
- return carry.failures + (change.failures ?? NO_INCREMENT);
3725
+ function target_owner(owner_repo) {
3726
+ return repo_origin.parse_origin_url(`https://${repo_origin.GITHUB_HOST}/${owner_repo}`)?.owner;
2920
3727
  }
2921
- function is_within_fold_window(last_outage_at, now) {
2922
- if (last_outage_at === void 0) return false;
2923
- const last = Date.parse(last_outage_at);
2924
- if (Number.isNaN(last)) return false;
2925
- return now.getTime() - last <= OUTAGE_FOLD_WINDOW_MS;
3728
+ function current_owner(repository_path = process.cwd()) {
3729
+ return repo_discovery.resolve_current_owner(repository_path);
2926
3730
  }
2927
- function folded_outage(carry) {
2928
- return { outages: carry.outages, last_outage_at: carry.last_outage_at };
3731
+ var repo_party = { FIRST_PARTY, THIRD_PARTY, UNKNOWN, classify: classify4, current_owner, target_owner };
3732
+
3733
+ // scripts/lane/lane-reap.ts
3734
+ import { execFileSync as execFileSync3 } from "node:child_process";
3735
+ var PGREP_COMMAND = "pgrep";
3736
+ var MATCH_FULL_COMMAND_LINE = "-f";
3737
+ var CHILDREN_OF_FLAG = "-P";
3738
+ var PS_COMMAND2 = "/bin/ps";
3739
+ var PARENT_FORMAT = ["-o", "ppid=", "-p"];
3740
+ var TERMINATE_SIGNAL = "SIGTERM";
3741
+ var PROBE_TIMEOUT_MS3 = 2e3;
3742
+ var LOWEST_CHILD_PID = 2;
3743
+ function parse_pids(printed) {
3744
+ return printed.split("\n").map((line) => Number(line.trim())).filter((pid) => Number.isSafeInteger(pid) && pid >= LOWEST_CHILD_PID);
3745
+ }
3746
+ function run_probe(command, args) {
3747
+ try {
3748
+ return execFileSync3(command, [...args], {
3749
+ encoding: "utf8",
3750
+ timeout: PROBE_TIMEOUT_MS3,
3751
+ stdio: ["ignore", "pipe", "ignore"]
3752
+ });
3753
+ } catch {
3754
+ return "";
3755
+ }
2929
3756
  }
2930
- function counted_outage(carry, change, now) {
2931
- const outages = carry.outages + (change.outages ?? NO_INCREMENT);
2932
- return { outages, last_outage_at: now.toISOString() };
3757
+ function parent_of(pid) {
3758
+ return parse_pids(run_probe(PS_COMMAND2, [...PARENT_FORMAT, String(pid)]))[0];
2933
3759
  }
2934
- function next_outage(carry, change, now) {
2935
- if (has_increment(change.outages)) {
2936
- return is_within_fold_window(carry.last_outage_at, now) ? folded_outage(carry) : counted_outage(carry, change, now);
3760
+ function terminate(pid) {
3761
+ try {
3762
+ process.kill(pid, TERMINATE_SIGNAL);
3763
+ return true;
3764
+ } catch {
3765
+ return false;
2937
3766
  }
2938
- const is_reachable = has_increment(change.merged) || has_increment(change.failures);
2939
- return is_reachable ? { outages: NO_INCREMENT, last_outage_at: void 0 } : folded_outage(carry);
2940
- }
2941
- function next_state(carry, change, now) {
2942
- const outage = next_outage(carry, change, now);
2943
- return {
2944
- failures: next_failures(carry, change),
2945
- outages: outage.outages,
2946
- last_outage_at: outage.last_outage_at
2947
- };
2948
3767
  }
2949
- var run_carry_streak = { OUTAGE_FOLD_WINDOW_MS, next_state };
3768
+ var SYSTEM_PROBES = {
3769
+ matching: (pattern) => parse_pids(run_probe(PGREP_COMMAND, [MATCH_FULL_COMMAND_LINE, pattern])),
3770
+ children_of: (pid) => parse_pids(run_probe(PGREP_COMMAND, [CHILDREN_OF_FLAG, String(pid)])),
3771
+ parent_of,
3772
+ terminate
3773
+ };
3774
+ function ancestry_of(pid, probes) {
3775
+ const ancestry = /* @__PURE__ */ new Set([pid]);
3776
+ let current = probes.parent_of(pid);
3777
+ while (current !== void 0 && !ancestry.has(current)) {
3778
+ ancestry.add(current);
3779
+ current = probes.parent_of(current);
3780
+ }
3781
+ return ancestry;
3782
+ }
3783
+ function own_ancestry(probes = SYSTEM_PROBES) {
3784
+ return ancestry_of(process.pid, probes);
3785
+ }
3786
+ function tree_of(roots, probes) {
3787
+ const tree = [];
3788
+ const pending = [...roots];
3789
+ let next = pending.shift();
3790
+ while (next !== void 0) {
3791
+ if (!tree.includes(next)) {
3792
+ tree.push(next);
3793
+ pending.push(...probes.children_of(next));
3794
+ }
3795
+ next = pending.shift();
3796
+ }
3797
+ return tree;
3798
+ }
3799
+ function reap_child(issue, probes = SYSTEM_PROBES) {
3800
+ const own2 = own_ancestry(probes);
3801
+ const pattern = lane_child_invocation.process_pattern(issue);
3802
+ const roots = probes.matching(pattern).filter((pid) => !own2.has(pid));
3803
+ const doomed = tree_of(roots, probes).filter((pid) => !own2.has(pid));
3804
+ return doomed.filter((pid) => probes.terminate(pid));
3805
+ }
3806
+ var lane_reap = {
3807
+ own_ancestry,
3808
+ parse_pids,
3809
+ reap_child
3810
+ };
2950
3811
 
2951
- // scripts/run/run-carry-change.ts
2952
- var NO_INCREMENT2 = 0;
2953
- function next_done(carry, done) {
2954
- if (done === void 0) return carry.done;
2955
- const current = carry.done ?? [];
2956
- return current.includes(done) ? current : [...current, done];
2957
- }
2958
- function next_merged_issues(carry, issue) {
2959
- if (issue === void 0) return carry.merged_issues;
2960
- return [...carry.merged_issues ?? [], issue];
2961
- }
2962
- function is_duplicate_merge(carry, change) {
2963
- const issue = change.merged_issue;
2964
- return issue !== void 0 && carry.merged_issues?.includes(issue) === true;
2965
- }
2966
- function next_counts(carry, change) {
2967
- const cuts = change.cuts ?? NO_INCREMENT2;
2968
- return {
2969
- merged: carry.merged + (change.merged ?? NO_INCREMENT2),
2970
- filed: carry.filed + (change.filed ?? NO_INCREMENT2),
2971
- cuts: carry.cuts + cuts,
2972
- is_handed_off: cuts > NO_INCREMENT2
2973
- };
2974
- }
2975
- function apply_change(target, carry, change, now = /* @__PURE__ */ new Date()) {
2976
- if (is_duplicate_merge(carry, change)) return carry;
2977
- const streak = run_carry_streak.next_state(carry, change, now);
2978
- const next = {
2979
- ...carry,
2980
- ...next_counts(carry, change),
2981
- merged_issues: next_merged_issues(carry, change.merged_issue),
2982
- failures: streak.failures,
2983
- outages: streak.outages,
2984
- last_outage_at: streak.last_outage_at,
2985
- done: next_done(carry, change.done),
2986
- retrospective: change.retrospective === true || carry.retrospective
2987
- };
2988
- stamp_file.write_stamp(target, next);
2989
- return next;
3812
+ // scripts/lane/lane-registry.ts
3813
+ import { existsSync as existsSync3 } from "node:fs";
3814
+ import path5 from "node:path";
3815
+
3816
+ // scripts/git/git-ls-remote.ts
3817
+ var LS_REMOTE_COMMAND = "ls-remote";
3818
+ var HEADS_FLAG = "--heads";
3819
+ var ORIGIN_REMOTE = "origin";
3820
+ var HEADS_REF_PREFIX = "refs/heads/";
3821
+ function ls_remote_branch_arguments(branch_name) {
3822
+ return [LS_REMOTE_COMMAND, HEADS_FLAG, ORIGIN_REMOTE, `${HEADS_REF_PREFIX}${branch_name}`];
2990
3823
  }
2991
- var run_carry_change = { apply_change };
2992
3824
 
2993
- // scripts/run/run-carry.ts
2994
- var CARRY_PREFIX = "josh-run-carry-";
2995
- var REPOSITORY_DIRECTORY_INDEX = 1;
2996
- var CARRY_MAX_AGE_HOURS = backlog_budget.WHOLE_RUN_BUDGET_HOURS;
2997
- var CARRY_MAX_AGE_MS = backlog_budget.WHOLE_RUN_BUDGET_MS;
2998
- var NO_INCREMENT3 = 0;
2999
- var MAX_CUTS = 6;
3000
- var END_COMMAND2 = "pnpm josh run:carry --end";
3001
- var RESUME_COMMAND = "pnpm josh run:carry --resume";
3002
- var READ_COMMAND2 = "pnpm josh run:carry --json";
3003
- var OWNER_ARGUMENT = '--owner "$PPID"';
3004
- var NONE_READ2 = { kind: "none" };
3005
- var UNREADABLE_READ3 = { kind: "unreadable" };
3006
- var NO_OWNER = {};
3007
- var run_carry_schema = z5.object({
3008
- invocation: z5.string(),
3009
- started_at: z5.string(),
3010
- merged: z5.number(),
3011
- merged_issues: z5.array(z5.number()).optional(),
3012
- filed: z5.number(),
3013
- cuts: z5.number(),
3014
- // Defaulted, so a record written before this field existed parses with a zero streak rather than
3015
- // failing to read — the same backward-compatibility the optional owner fields below carry.
3016
- failures: z5.number().default(0),
3017
- // Defaulted for the same backward-compatibility as `failures` (joshuafolkken/kit#2240).
3018
- outages: z5.number().default(0),
3019
- // Optional, so a record written before the outage fold existed still parses (joshuafolkken/kit#2317).
3020
- last_outage_at: z5.string().optional(),
3021
- // Optional, so a record written by the previous shape still parses. Read as "no owner declared",
3022
- // which is the not-provably-live answer rather than a live one.
3023
- owner_pid: z5.number().optional(),
3024
- owner_start: z5.string().optional(),
3025
- is_handed_off: z5.boolean().optional(),
3026
- done: z5.array(z5.number()).optional(),
3027
- // Optional, so a record written before the retrospective existed still parses (joshuafolkken/kit#2328).
3028
- retrospective: z5.boolean().optional()
3029
- });
3030
- function carry_path(git_directory) {
3031
- return stamp_file.stamp_path(CARRY_PREFIX, git_directory);
3825
+ // scripts/git/repository-lock.ts
3826
+ import { randomUUID as randomUUID3 } from "node:crypto";
3827
+ import { linkSync, renameSync, rmSync as rmSync2 } from "node:fs";
3828
+ import { setTimeout as sleep2 } from "node:timers/promises";
3829
+
3830
+ // scripts/josh/process-owner.ts
3831
+ import { z as z9 } from "zod";
3832
+ var process_owner_schema = z9.object({ pid: z9.number(), process_start: z9.string().optional() });
3833
+
3834
+ // scripts/git/repository-lock.ts
3835
+ var POLL_INTERVAL_MS = 200;
3836
+ function lock_path(prefix, cwd) {
3837
+ return stamp_file.stamp_path(prefix, git_common_directory.repository(cwd) ?? cwd ?? process.cwd());
3032
3838
  }
3033
- async function repository_directory() {
3034
- const directories = await git_command.git_directories();
3035
- return directories[REPOSITORY_DIRECTORY_INDEX];
3839
+ function parse_owner(raw) {
3840
+ if (raw === void 0) return void 0;
3841
+ const parsed = process_owner_schema.safeParse(json_value.parse_or_undefined(raw));
3842
+ return parsed.success ? parsed.data : void 0;
3036
3843
  }
3037
- function parse_carry(raw) {
3844
+ function read_owner(target) {
3845
+ return parse_owner(stamp_file.read_stamp_text(target));
3846
+ }
3847
+ function is_owner_gone(owner) {
3848
+ return process_identity.is_same_process(owner.pid, owner.process_start) === false;
3849
+ }
3850
+ function restore(aside, target) {
3038
3851
  try {
3039
- const parsed = run_carry_schema.safeParse(JSON.parse(raw));
3040
- return parsed.success ? parsed.data : void 0;
3852
+ linkSync(aside, target);
3853
+ } catch {
3854
+ }
3855
+ }
3856
+ function stale_record(target, is_gone2) {
3857
+ const raw = stamp_file.read_stamp_text(target);
3858
+ const owner = parse_owner(raw);
3859
+ return owner !== void 0 && is_gone2(owner) ? raw : void 0;
3860
+ }
3861
+ function move_aside(target) {
3862
+ const aside = `${target}.${String(process.pid)}.${randomUUID3()}`;
3863
+ try {
3864
+ renameSync(target, aside);
3865
+ return aside;
3041
3866
  } catch {
3042
3867
  return void 0;
3043
3868
  }
3044
3869
  }
3045
- function is_expired2(carry, now) {
3046
- const started = Date.parse(carry.started_at);
3047
- if (Number.isNaN(started)) return true;
3048
- return now.getTime() - started > CARRY_MAX_AGE_MS;
3870
+ function clear_stale(target, is_gone2 = is_owner_gone) {
3871
+ const raw = stale_record(target, is_gone2);
3872
+ if (raw === void 0) return;
3873
+ const aside = move_aside(target);
3874
+ if (aside === void 0) return;
3875
+ if (stamp_file.read_stamp_text(aside) !== raw) restore(aside, target);
3876
+ rmSync2(aside, { force: true });
3049
3877
  }
3050
- function classify4(raw, now) {
3051
- if (raw === void 0) return NONE_READ2;
3052
- const carry = parse_carry(raw);
3053
- if (carry === void 0) return UNREADABLE_READ3;
3054
- return { kind: is_expired2(carry, now) ? "expired" : "carried", carry };
3878
+ function claim(target) {
3879
+ clear_stale(target);
3880
+ return stamp_file.create_stamp(target, process_identity.own_fields());
3055
3881
  }
3056
- function read_carry(target, now = /* @__PURE__ */ new Date()) {
3057
- return classify4(stamp_file.read_stamp_text(target), now);
3882
+ function release(target) {
3883
+ const owner = read_owner(target);
3884
+ if (owner !== void 0 && process_identity.is_own_process(owner.pid, owner.process_start)) {
3885
+ stamp_file.remove_stamp(target);
3886
+ }
3058
3887
  }
3059
- function owner_of(pid, read2 = process_identity.read_start) {
3060
- return { pid, start: read2(pid) };
3888
+ async function acquire(target, max_wait_ms) {
3889
+ const deadline = Date.now() + max_wait_ms;
3890
+ while (!claim(target)) {
3891
+ if (Date.now() >= deadline) return false;
3892
+ await sleep2(POLL_INTERVAL_MS);
3893
+ }
3894
+ return true;
3061
3895
  }
3062
- function fresh_carry(invocation, owner, now) {
3063
- return {
3064
- invocation,
3065
- started_at: now.toISOString(),
3066
- merged: NO_INCREMENT3,
3067
- filed: NO_INCREMENT3,
3068
- cuts: NO_INCREMENT3,
3069
- failures: NO_INCREMENT3,
3070
- outages: NO_INCREMENT3,
3071
- owner_pid: owner.pid,
3072
- owner_start: owner.start
3073
- };
3896
+ async function with_lock(work, target, max_wait_ms) {
3897
+ if (!await acquire(target, max_wait_ms)) return void 0;
3898
+ try {
3899
+ return await work();
3900
+ } finally {
3901
+ release(target);
3902
+ }
3074
3903
  }
3075
- function begin_carry(target, invocation, owner = NO_OWNER, now = /* @__PURE__ */ new Date()) {
3076
- const carry = fresh_carry(invocation, owner, now);
3077
- return stamp_file.create_stamp(target, carry) ? carry : void 0;
3904
+ var repository_lock = { clear_stale, lock_path, with_lock };
3905
+
3906
+ // scripts/git/git-worktree.ts
3907
+ var WORKTREE = "worktree";
3908
+ var WORKTREE_LOCK_PREFIX = "josh-worktree-lock-";
3909
+ var WORKTREE_LOCK_MAX_WAIT_MS = 12e4;
3910
+ async function locked(arguments_) {
3911
+ const target = repository_lock.lock_path(WORKTREE_LOCK_PREFIX);
3912
+ const output = await repository_lock.with_lock(
3913
+ async () => await git_spawn.read(arguments_),
3914
+ target,
3915
+ WORKTREE_LOCK_MAX_WAIT_MS
3916
+ );
3917
+ if (output === void 0) {
3918
+ throw new Error(
3919
+ `git ${arguments_.join(" ")}: timed out waiting for the work-tree lock ${target}`
3920
+ );
3921
+ }
3922
+ return output;
3078
3923
  }
3079
- function replace_carry(target, invocation, owner = NO_OWNER, now = /* @__PURE__ */ new Date()) {
3080
- stamp_file.remove_stamp(target);
3081
- return begin_carry(target, invocation, owner, now);
3924
+ async function worktree_list() {
3925
+ return await git_spawn.read([WORKTREE, "list", PORCELAIN_FLAG]);
3082
3926
  }
3083
- function adopt_carry(target, carry, owner = NO_OWNER) {
3084
- const next = {
3085
- ...carry,
3086
- owner_pid: owner.pid,
3087
- owner_start: owner.start,
3088
- is_handed_off: false
3089
- };
3090
- stamp_file.remove_stamp(target);
3091
- return stamp_file.create_stamp(target, next) ? next : void 0;
3927
+ async function worktree_add(directory, branch_name, start_point) {
3928
+ const flags = start_point === void 0 ? ["add"] : ["add", "--no-track", "-b", branch_name];
3929
+ return await locked([WORKTREE, ...flags, directory, start_point ?? branch_name]);
3092
3930
  }
3093
- function is_owner_live(carry) {
3094
- return process_identity.is_same_process(carry.owner_pid, carry.owner_start) !== false;
3931
+ async function worktree_add_detached(directory, commit2) {
3932
+ return await locked([WORKTREE, "add", "--detach", directory, commit2]);
3095
3933
  }
3096
- function is_owned_by(carry, owner) {
3097
- return carry.owner_pid === owner.pid && carry.owner_start === owner.start;
3934
+ async function worktree_remove(directory) {
3935
+ return await locked([WORKTREE, "remove", "--force", directory]);
3098
3936
  }
3099
- function is_foreign_live_owner(carry, owner) {
3100
- return is_owner_live(carry) && !is_owned_by(carry, owner);
3937
+ async function worktree_prune() {
3938
+ return await locked([WORKTREE, "prune"]);
3101
3939
  }
3102
- function is_handed_off_to(carry, invocation) {
3103
- return carry.is_handed_off === true && carry.invocation === invocation;
3940
+ async function ls_remote_branch(branch_name) {
3941
+ return await git_spawn.read(ls_remote_branch_arguments(branch_name));
3104
3942
  }
3105
- function classify_claim(carry, request) {
3106
- if (is_handed_off_to(carry, request.invocation)) return "resume";
3107
- if (is_foreign_live_owner(carry, request.owner)) return "busy";
3108
- if (carry.invocation !== request.invocation) return "mismatch";
3109
- return request.is_adoption ? "resume" : "standing";
3943
+ async function branch_delete(branch_name) {
3944
+ return await git_spawn.read(["branch", "-D", branch_name]);
3110
3945
  }
3111
- function is_count_refused(carry, owner) {
3112
- return carry.is_handed_off === true || is_foreign_live_owner(carry, owner);
3946
+ var git_worktree = {
3947
+ branch_delete,
3948
+ ls_remote_branch,
3949
+ worktree_add,
3950
+ worktree_add_detached,
3951
+ worktree_list,
3952
+ worktree_prune,
3953
+ worktree_remove
3954
+ };
3955
+
3956
+ // scripts/lane/lane-environment.ts
3957
+ var LANE_OUTPUT_KEY = "JOSH_LANE_OUTPUT";
3958
+ var LANE_PROVIDER_KEY = "JOSH_LANE_AGENT_PROVIDER";
3959
+ var LANE_ROLE_KEY = "JOSH_LANE_AGENT_ROLE";
3960
+ var LANE_MODEL_KEY = "JOSH_LANE_AGENT_MODEL";
3961
+ var LANE_EFFORT_KEY = "JOSH_LANE_AGENT_EFFORT";
3962
+ var ASSIGNMENT_SEPARATOR = "=";
3963
+ var EXPORT_PREFIX = "export ";
3964
+ var QUOTE_CHARACTERS = /* @__PURE__ */ new Set(['"', "'"]);
3965
+ var MIN_QUOTED_LENGTH = 2;
3966
+ var FIRST_CHARACTER_END = 1;
3967
+ var LAST_CHARACTER_START = -1;
3968
+ function strip_export(line) {
3969
+ const trimmed2 = line.trimStart();
3970
+ return trimmed2.startsWith(EXPORT_PREFIX) ? trimmed2.slice(EXPORT_PREFIX.length).trimStart() : trimmed2;
3113
3971
  }
3114
- function is_at_cut_cap(carry) {
3115
- return carry.cuts >= MAX_CUTS;
3972
+ function is_key_line(line, key2) {
3973
+ const body = strip_export(line);
3974
+ if (!body.startsWith(key2)) return false;
3975
+ return body.slice(key2.length).trimStart().startsWith(ASSIGNMENT_SEPARATOR);
3116
3976
  }
3117
- function end_carry(target) {
3118
- stamp_file.remove_stamp(target);
3977
+ function is_seat_line(line) {
3978
+ return is_key_line(line, LANE_SEAT_KEY);
3119
3979
  }
3120
- function retrospective_done_of(read2) {
3121
- if (read2.kind !== "carried" && read2.kind !== "expired") return false;
3122
- return read2.carry.retrospective === true;
3980
+ function is_output_line(line) {
3981
+ return is_key_line(line, LANE_OUTPUT_KEY);
3123
3982
  }
3124
- function remaining_of(carry) {
3125
- if (carry === void 0) return void 0;
3126
- const declared = run_invocation.issue_numbers(carry.invocation);
3127
- if (declared === void 0) return void 0;
3128
- const done = carry.done ?? [];
3129
- return declared.filter((issue) => !done.includes(issue));
3983
+ function split_lines(content) {
3984
+ const trimmed2 = content.trimEnd();
3985
+ return trimmed2.length === 0 ? [] : trimmed2.split("\n");
3130
3986
  }
3131
- function done_note(carry) {
3132
- const done = carry.done ?? [];
3133
- return done.length === 0 ? "" : `, issues ${done.join(", ")} done`;
3987
+ function is_quoted(raw) {
3988
+ const first = raw.slice(0, FIRST_CHARACTER_END);
3989
+ if (!QUOTE_CHARACTERS.has(first)) return false;
3990
+ return raw.length >= MIN_QUOTED_LENGTH && raw.endsWith(first);
3134
3991
  }
3135
- function describe_carry(carry) {
3136
- return `${carry.invocation} started ${carry.started_at}; ${String(carry.merged)} merged, ${String(carry.filed)} filed, ${String(carry.failures)} failed in a row, ${String(carry.outages)} outage(s) in a row, ${String(carry.cuts)} cut(s) crossed${done_note(carry)}`;
3992
+ function unquote(raw) {
3993
+ return is_quoted(raw) ? raw.slice(FIRST_CHARACTER_END, LAST_CHARACTER_START) : raw;
3137
3994
  }
3138
- function expired_message(carry) {
3139
- return `The ${String(CARRY_MAX_AGE_HOURS)}-hour whole-run bound is spent: ${describe_carry(carry)}. Clear it with \`${END_COMMAND2}\`.`;
3995
+ function assignment_value(line) {
3996
+ return unquote(line.slice(line.indexOf(ASSIGNMENT_SEPARATOR) + FIRST_CHARACTER_END).trim());
3140
3997
  }
3141
- function mismatch_message(carry, invocation) {
3142
- return `A different invocation is carried here \u2014 ${describe_carry(carry)}. This one is \`${invocation}\`. End the standing record with \`${END_COMMAND2}\` once you know its run is over, then begin again.`;
3998
+ function read_assignment(content, key2) {
3999
+ const line = split_lines(content).findLast((entry) => is_key_line(entry, key2));
4000
+ return line === void 0 ? void 0 : assignment_value(line);
3143
4001
  }
3144
- function busy_message2(carry) {
3145
- return `A live run holds this budget \u2014 ${describe_carry(carry)}, owner pid ${String(carry.owner_pid)} still running. Nothing was established. Wait for that run, or end its record with \`${END_COMMAND2}\` once you know it is over.`;
4002
+ function read_root_seed(content) {
4003
+ return ports.resolve_seed({ [PORT_SEED_KEY]: read_assignment(content, PORT_SEED_KEY) });
3146
4004
  }
3147
- function standing_message(carry) {
3148
- return `A run record is standing here that no cut handed off \u2014 ${describe_carry(carry)}. Nothing was established. Carry that budget with \`${RESUME_COMMAND} "${carry.invocation}" ${OWNER_ARGUMENT}\`, or discard it with \`${END_COMMAND2}\` and begin again.`;
4005
+ function read_lane_seat(content) {
4006
+ const raw = read_assignment(content, LANE_SEAT_KEY);
4007
+ return raw === void 0 ? void 0 : ports.resolve_lane({ [LANE_SEAT_KEY]: raw });
4008
+ }
4009
+ function read_lane_ports(content) {
4010
+ const environment2 = {
4011
+ [PORT_SEED_KEY]: read_assignment(content, PORT_SEED_KEY),
4012
+ [LANE_SEAT_KEY]: read_assignment(content, LANE_SEAT_KEY)
4013
+ };
4014
+ return {
4015
+ development: ports.resolve_development_port(environment2),
4016
+ preview: ports.resolve_preview_port(environment2)
4017
+ };
4018
+ }
4019
+ function read_lane_output(content) {
4020
+ const line = split_lines(content).findLast((entry) => is_output_line(entry));
4021
+ const raw = line === void 0 ? void 0 : assignment_value(line);
4022
+ return raw === void 0 || raw.length === 0 ? void 0 : raw;
3149
4023
  }
3150
- function count_refused_message(carry) {
3151
- if (carry.is_handed_off === true) {
3152
- return `This budget was handed off at a cut and is waiting for its successor \u2014 ${describe_carry(carry)}. Nothing was counted; the cutting session must not advance a handed-off budget.`;
3153
- }
3154
- return `A live run owns this budget \u2014 ${describe_carry(carry)}, owner pid ${String(carry.owner_pid)} still running. Nothing was counted; only its owner advances the budget.`;
4024
+ function read_lane_profile(content) {
4025
+ return agent_role_profile.parse({
4026
+ provider: read_assignment(content, LANE_PROVIDER_KEY),
4027
+ role: read_assignment(content, LANE_ROLE_KEY),
4028
+ model: read_assignment(content, LANE_MODEL_KEY),
4029
+ effort: read_assignment(content, LANE_EFFORT_KEY)
4030
+ });
3155
4031
  }
3156
- function lost_message() {
3157
- return `Another process established a run record here first, so nothing was established. Read it with \`${READ_COMMAND2}\`.`;
4032
+ function replace_assignment(lines, keep, assignment, key2) {
4033
+ return lines.flatMap((entry, index) => {
4034
+ if (index === keep) return [assignment];
4035
+ return is_key_line(entry, key2) ? [] : [entry];
4036
+ });
3158
4037
  }
3159
- function nothing_to_resume_message() {
3160
- return `Nothing is carried here, so there is no budget to resume. Begin one with \`pnpm josh run:carry --begin "<invocation>"\`.`;
4038
+ function upsert_assignment(content, key2, value) {
4039
+ const assignment = `${key2}${ASSIGNMENT_SEPARATOR}${value}`;
4040
+ const lines = split_lines(content);
4041
+ const last = lines.findLastIndex((entry) => is_key_line(entry, key2));
4042
+ const body = last === -1 ? [...lines, assignment] : replace_assignment(lines, last, assignment, key2);
4043
+ return `${body.join("\n")}
4044
+ `;
3161
4045
  }
3162
- function unreadable_message3() {
3163
- return `A run record is here but could not be read; clear it with \`${END_COMMAND2}\` once you know no run is using it.`;
4046
+ function lane_file_content(root_content, seat) {
4047
+ return upsert_assignment(root_content, LANE_SEAT_KEY, String(seat));
3164
4048
  }
3165
- function unknown_message3() {
3166
- return "This repository\u2019s git directory could not be read, so no run record was established.";
4049
+ function with_lane_output(content, output, profile) {
4050
+ const recorded = upsert_assignment(content, LANE_OUTPUT_KEY, `"${output}"`);
4051
+ if (profile === void 0) return recorded;
4052
+ const with_provider = upsert_assignment(recorded, LANE_PROVIDER_KEY, profile.provider);
4053
+ const with_role = upsert_assignment(with_provider, LANE_ROLE_KEY, profile.role);
4054
+ const with_model = upsert_assignment(with_role, LANE_MODEL_KEY, profile.model);
4055
+ return upsert_assignment(with_model, LANE_EFFORT_KEY, profile.effort);
3167
4056
  }
3168
- var run_carry = {
3169
- CARRY_MAX_AGE_HOURS,
3170
- CARRY_MAX_AGE_MS,
3171
- END_COMMAND: END_COMMAND2,
3172
- MAX_CUTS,
3173
- NO_OWNER,
3174
- RESUME_COMMAND,
3175
- adopt_carry,
3176
- apply_change: run_carry_change.apply_change,
3177
- begin_carry,
3178
- busy_message: busy_message2,
3179
- carry_path,
3180
- classify: classify4,
3181
- classify_claim,
3182
- count_refused_message,
3183
- is_at_cut_cap,
3184
- describe_carry,
3185
- end_carry,
3186
- expired_message,
3187
- mismatch_message,
3188
- fresh_carry,
3189
- is_count_refused,
3190
- is_expired: is_expired2,
3191
- is_foreign_live_owner,
3192
- is_owner_live,
3193
- lost_message,
3194
- nothing_to_resume_message,
3195
- owner_of,
3196
- parse_carry,
3197
- read_carry,
3198
- remaining_of,
3199
- replace_carry,
3200
- repository_directory,
3201
- retrospective_done_of,
3202
- standing_message,
3203
- unknown_message: unknown_message3,
3204
- unreadable_message: unreadable_message3
4057
+ var lane_environment = {
4058
+ is_seat_line,
4059
+ lane_file_content,
4060
+ read_lane_output,
4061
+ read_lane_profile,
4062
+ read_lane_ports,
4063
+ read_lane_seat,
4064
+ read_root_seed,
4065
+ with_lane_output
3205
4066
  };
3206
4067
 
3207
- // scripts/run/run-progress-clock.ts
3208
- import { execaSync } from "execa";
3209
- import { z as z6 } from "zod";
3210
- var PROGRESS_PREFIX = "josh-run-progress-";
3211
- var LIFE_PREFIX = "josh-run-progress-life-";
3212
- var LOG_SUFFIX = ".log";
3213
- var LOG_KEEP = 12;
3214
- var MS_PER_MINUTE2 = 6e4;
3215
- var LOG_STALE_MINUTES = 90;
3216
- var LOG_STALE_MS = LOG_STALE_MINUTES * MS_PER_MINUTE2;
3217
- var BLOCK_SEPARATOR2 = "\n\n";
3218
- var GIT_TIMEOUT_MS = 5e3;
3219
- var GIT_DIRECTORY_ARGUMENTS2 = ["rev-parse", "--absolute-git-dir"];
3220
- var report_stamp_schema = z6.object({ reported_at: z6.string(), line: z6.string().optional() });
3221
- function parse_stamp(raw) {
3222
- const parsed = report_stamp_schema.safeParse(JSON.parse(raw));
3223
- if (!parsed.success) return void 0;
3224
- const reported_at = Date.parse(parsed.data.reported_at);
3225
- return Number.isNaN(reported_at) ? void 0 : reported_at;
3226
- }
3227
- function read_last_line(target) {
3228
- const raw = stamp_file.read_stamp_text(target);
3229
- if (raw === void 0) return void 0;
3230
- try {
3231
- return report_stamp_schema.safeParse(JSON.parse(raw)).data?.line;
3232
- } catch {
3233
- return void 0;
3234
- }
4068
+ // scripts/lane/lane-registry.ts
4069
+ var WORKTREE_PREFIX = "worktree ";
4070
+ var BRANCH_PREFIX = "branch refs/heads/";
4071
+ var BLOCK_SEPARATOR = "\n\n";
4072
+ var ISSUE_PATTERN = /^[1-9]\d*$/u;
4073
+ function line_value(lines, prefix) {
4074
+ return lines.find((line) => line.startsWith(prefix))?.slice(prefix.length);
3235
4075
  }
3236
- function read_last_report(target) {
3237
- const raw = stamp_file.read_stamp_text(target);
3238
- if (raw === void 0) return void 0;
3239
- try {
3240
- return parse_stamp(raw);
3241
- } catch {
3242
- return void 0;
3243
- }
4076
+ function seat_from_content(content) {
4077
+ const seat = lane_environment.read_lane_seat(content);
4078
+ if (seat === void 0) return void 0;
4079
+ const port_pair = lane_environment.read_lane_ports(content);
4080
+ return { seat, development_port: port_pair.development, preview_port: port_pair.preview };
3244
4081
  }
3245
- function log_path_of(target) {
3246
- return `${target}${LOG_SUFFIX}`;
4082
+ var EMPTY_SEAT = {
4083
+ seat: void 0,
4084
+ development_port: void 0,
4085
+ preview_port: void 0
4086
+ };
4087
+ function held_seat(content) {
4088
+ if (content === void 0) return EMPTY_SEAT;
4089
+ return seat_from_content(content) ?? EMPTY_SEAT;
3247
4090
  }
3248
- function is_log_stale(target, now_ms) {
3249
- const previous_ms = read_last_report(target);
3250
- return previous_ms === void 0 || now_ms - previous_ms > LOG_STALE_MS;
4091
+ function read_environment(directory) {
4092
+ return file_reader.read_if_readable(path5.join(directory, ENV_FILE_NAME));
3251
4093
  }
3252
- function recent_blocks(log_target, is_fresh) {
3253
- if (is_fresh) return [];
3254
- const raw = stamp_file.read_stamp_text(log_target);
3255
- if (raw === void 0) return [];
3256
- return raw.split(BLOCK_SEPARATOR2).map((block) => block.trimEnd()).filter((block) => block.length > 0);
4094
+ function branch_issue(branch2) {
4095
+ if (!branch2?.endsWith(lane_paths.LANE_BRANCH_SUFFIX)) return void 0;
4096
+ const issue = branch2.slice(0, -lane_paths.LANE_BRANCH_SUFFIX.length);
4097
+ return ISSUE_PATTERN.test(issue) ? issue : void 0;
3257
4098
  }
3258
- function append_line(log_target, reported_at, line, is_fresh) {
3259
- const block = `${reported_at}
3260
- ${line}`;
3261
- const blocks = [...recent_blocks(log_target, is_fresh), block].slice(-LOG_KEEP);
3262
- stamp_file.write_text_stamp(log_target, `${blocks.join(BLOCK_SEPARATOR2)}
3263
- `);
4099
+ function is_gone(directory) {
4100
+ return !existsSync3(directory);
3264
4101
  }
3265
- function mark(target, now_ms, line) {
3266
- const reported_at = new Date(now_ms).toISOString();
3267
- const kept = line ?? read_last_line(target);
3268
- if (line !== void 0) {
3269
- append_line(log_path_of(target), reported_at, line, is_log_stale(target, now_ms));
3270
- }
3271
- stamp_file.write_stamp(target, kept === void 0 ? { reported_at } : { reported_at, line: kept });
4102
+ function build_lane(issue, branch2, directory, is_stranded) {
4103
+ const content = is_stranded ? void 0 : read_environment(directory);
4104
+ const held = held_seat(content);
4105
+ const output = content === void 0 ? void 0 : lane_environment.read_lane_output(content);
4106
+ const profile = content === void 0 ? void 0 : lane_environment.read_lane_profile(content);
4107
+ return {
4108
+ issue,
4109
+ branch: branch2,
4110
+ directory,
4111
+ seat: held.seat,
4112
+ development_port: held.development_port,
4113
+ preview_port: held.preview_port,
4114
+ output,
4115
+ profile,
4116
+ is_stranded
4117
+ };
3272
4118
  }
3273
- function stamp_target_of(git_directory) {
3274
- return stamp_file.stamp_path(PROGRESS_PREFIX, git_directory);
4119
+ function is_lane_directory(root, issue, directory) {
4120
+ return directory === lane_paths.lane_directory(root, issue);
3275
4121
  }
3276
- function life_target_of(git_directory) {
3277
- return stamp_file.stamp_path(LIFE_PREFIX, git_directory);
4122
+ function parse_block(block, root) {
4123
+ const lines = block.split("\n");
4124
+ const directory = line_value(lines, WORKTREE_PREFIX);
4125
+ const branch2 = line_value(lines, BRANCH_PREFIX);
4126
+ const issue = branch_issue(branch2);
4127
+ if (directory === void 0 || branch2 === void 0 || issue === void 0) return void 0;
4128
+ if (!is_lane_directory(root, issue, directory)) return void 0;
4129
+ return build_lane(issue, branch2, directory, is_gone(directory));
3278
4130
  }
3279
- var life_schema = z6.object({ alive: z6.literal(true), pinged_at: z6.string().optional() });
3280
- function begin_life(target) {
3281
- stamp_file.write_stamp(target, { alive: true, pinged_at: (/* @__PURE__ */ new Date()).toISOString() });
4131
+ function is_lane(lane) {
4132
+ return lane !== void 0;
3282
4133
  }
3283
- function ping_life(target) {
3284
- begin_life(target);
4134
+ async function worktree_blocks() {
4135
+ const listing = await git_worktree.worktree_list();
4136
+ return listing.split(BLOCK_SEPARATOR);
3285
4137
  }
3286
- function parse_pinged_at(raw) {
3287
- const parsed = life_schema.safeParse(JSON.parse(raw));
3288
- if (!parsed.success || parsed.data.pinged_at === void 0) return void 0;
3289
- const pinged_ms = Date.parse(parsed.data.pinged_at);
3290
- return Number.isNaN(pinged_ms) ? void 0 : pinged_ms;
4138
+ async function main_repository_root() {
4139
+ return repo_discovery.main_worktree(await git_command.repository_root());
3291
4140
  }
3292
- function is_life_fresh(target, threshold_ms) {
3293
- const raw = stamp_file.read_stamp_text(target);
3294
- if (raw === void 0) return false;
3295
- try {
3296
- const pinged_ms = parse_pinged_at(raw);
3297
- return pinged_ms !== void 0 && Date.now() - pinged_ms < threshold_ms;
3298
- } catch {
3299
- return false;
3300
- }
4141
+ async function list_lanes() {
4142
+ const root = lane_paths.lane_root(await main_repository_root());
4143
+ const blocks = await worktree_blocks();
4144
+ const lanes = blocks.map((block) => parse_block(block, root)).filter(is_lane);
4145
+ return lanes.toSorted((left, right) => Number(left.issue) - Number(right.issue));
4146
+ }
4147
+ async function has_lanes_in_flight() {
4148
+ const lanes = await list_lanes();
4149
+ return lanes.some((lane) => !lane.is_stranded);
3301
4150
  }
3302
- function is_life_ended(target) {
3303
- return stamp_file.read_stamp_text(target) === void 0;
4151
+ function used_seats(lanes) {
4152
+ return lanes.map((lane) => lane.seat).filter((seat) => seat !== void 0);
3304
4153
  }
3305
- function end_life(target) {
3306
- stamp_file.remove_stamp(target);
4154
+ function unreadable_lanes(lanes) {
4155
+ return lanes.filter((lane) => !lane.is_stranded && lane.seat === void 0);
3307
4156
  }
3308
- function git_directory_sync() {
3309
- const git_binary = git_utilities.get_git_command_for_spawn();
3310
- const result = execaSync(git_binary, GIT_DIRECTORY_ARGUMENTS2, {
3311
- reject: false,
3312
- timeout: GIT_TIMEOUT_MS
3313
- });
3314
- if (result.exitCode !== 0) return void 0;
3315
- const output = result.stdout.trim();
3316
- return output === "" ? void 0 : output;
3317
- }
3318
- function stamp_target_sync() {
3319
- const directory = git_directory_sync();
3320
- return directory === void 0 ? void 0 : stamp_target_of(directory);
3321
- }
3322
- function read_last_report_sync() {
3323
- const target = stamp_target_sync();
3324
- return target === void 0 ? void 0 : read_last_report(target);
3325
- }
3326
- var run_progress_clock = {
3327
- LIFE_PREFIX,
3328
- LOG_KEEP,
3329
- LOG_STALE_MS,
3330
- PROGRESS_PREFIX,
3331
- append_line,
3332
- begin_life,
3333
- end_life,
3334
- is_life_ended,
3335
- is_life_fresh,
3336
- life_target_of,
3337
- log_path_of,
3338
- mark,
3339
- parse_stamp,
3340
- ping_life,
3341
- read_last_line,
3342
- read_last_report,
3343
- read_last_report_sync,
3344
- stamp_target_of,
3345
- stamp_target_sync
4157
+ function find_lane(lanes, issue) {
4158
+ return lanes.find((lane) => lane.issue === issue);
4159
+ }
4160
+ async function find_open_lane(issue) {
4161
+ return find_lane(await list_lanes(), issue);
4162
+ }
4163
+ var lane_registry = {
4164
+ find_lane,
4165
+ find_open_lane,
4166
+ has_lanes_in_flight,
4167
+ list_lanes,
4168
+ main_repository_root,
4169
+ parse_block,
4170
+ read_environment,
4171
+ unreadable_lanes,
4172
+ used_seats
3346
4173
  };
3347
4174
 
3348
- // scripts/run/run-watcher-guard.ts
3349
- var WATCHER_TICK_MS = 3e4;
3350
- var TICK_MULTIPLIER = 3;
3351
- var MS_PER_SECOND2 = 1e3;
3352
- var STALE_THRESHOLD_MS = WATCHER_TICK_MS * TICK_MULTIPLIER;
3353
- var STALE_THRESHOLD_S = STALE_THRESHOLD_MS / MS_PER_SECOND2;
3354
- var STALE_NOTE = `Lane children are in-flight but the progress watcher has not pinged in the last ${String(STALE_THRESHOLD_S)}s \u2014 run \`pnpm josh run:progress --wait\` in the background before proceeding`;
3355
- var OK_RESULT = { kind: "ok" };
3356
- async function has_lanes_in_flight() {
3357
- const all_lanes = await lane_registry.list_lanes();
3358
- return all_lanes.some((lane) => !lane.is_stranded);
3359
- }
3360
- async function check(life_target) {
3361
- if (!await has_lanes_in_flight()) return OK_RESULT;
3362
- if (run_progress_clock.is_life_fresh(life_target, STALE_THRESHOLD_MS)) return OK_RESULT;
3363
- return { kind: "stale", note: STALE_NOTE };
3364
- }
3365
- var run_watcher_guard = {
3366
- STALE_NOTE,
3367
- STALE_THRESHOLD_MS,
3368
- check,
3369
- has_lanes_in_flight
4175
+ // scripts/run/run-headless.ts
4176
+ var HEADLESS_ENV_KEY = "JOSH_RUN_HEADLESS";
4177
+ var HEADLESS_VALUE = "1";
4178
+ function environment() {
4179
+ return { [HEADLESS_ENV_KEY]: HEADLESS_VALUE };
4180
+ }
4181
+ function is_headless(source = process.env) {
4182
+ return source[HEADLESS_ENV_KEY] === HEADLESS_VALUE;
4183
+ }
4184
+ function is_driving(read2) {
4185
+ return read2?.kind === "carried" && read2.carry.is_handed_off !== true;
4186
+ }
4187
+ async function read_carry_here() {
4188
+ const repository2 = await run_carry.repository_directory();
4189
+ return repository2 === void 0 ? void 0 : run_carry.read_carry(run_carry.carry_path(repository2));
4190
+ }
4191
+ async function must_keep_waiting(source = process.env) {
4192
+ const is_candidate2 = is_headless(source) && lane_child_marker.marked_issue(source) === void 0;
4193
+ if (!is_candidate2 || !await lane_registry.has_lanes_in_flight()) return false;
4194
+ return is_driving(await read_carry_here());
4195
+ }
4196
+ async function current_carry() {
4197
+ const read2 = await read_carry_here();
4198
+ return read2?.kind === "carried" ? read2.carry : void 0;
4199
+ }
4200
+ function is_owned_here(carry, ancestry) {
4201
+ return carry.owner_pid !== void 0 && ancestry().has(carry.owner_pid);
4202
+ }
4203
+ function driven_here(read2, ancestry) {
4204
+ if (read2?.kind !== "carried" || !is_driving(read2)) return void 0;
4205
+ return is_owned_here(read2.carry, ancestry) ? read2.carry : void 0;
4206
+ }
4207
+ async function driving_carry(source = process.env, ancestry = lane_reap.own_ancestry) {
4208
+ if (lane_child_marker.marked_issue(source) !== void 0) return void 0;
4209
+ return driven_here(await read_carry_here(), ancestry);
4210
+ }
4211
+ async function is_backlog_parent(source = process.env, ancestry = lane_reap.own_ancestry) {
4212
+ return await driving_carry(source, ancestry) !== void 0;
4213
+ }
4214
+ var run_headless = {
4215
+ HEADLESS_ENV_KEY,
4216
+ current_carry,
4217
+ driving_carry,
4218
+ environment,
4219
+ is_backlog_parent,
4220
+ is_headless,
4221
+ is_owned_here,
4222
+ must_keep_waiting
3370
4223
  };
3371
4224
 
3372
- // scripts/git/git-gh-api-path.ts
4225
+ // scripts/issue/issue-cite.ts
4226
+ var GITHUB_URL_PREFIX = "https://github.com/";
4227
+ var ISSUE_PATH = "/issues/";
4228
+ var SUMMARY_SEPARATOR = " \u2014 ";
4229
+ var REPO_QUALIFIED = /^([^\s/#]+\/[^\s/#]+)#(\d+)$/u;
4230
+ var BARE_NUMBER = /^#?(\d+)$/u;
4231
+ function issue_url(slug, number) {
4232
+ return `${GITHUB_URL_PREFIX}${slug}${ISSUE_PATH}${number}`;
4233
+ }
4234
+ function citation_line(slug, number, summary) {
4235
+ return `[#${number}](${issue_url(slug, number)})${SUMMARY_SEPARATOR}${summary}`;
4236
+ }
4237
+ function citer(slug, titles) {
4238
+ return function cite(number) {
4239
+ if (slug === void 0) return `#${number}`;
4240
+ const title = titles.get(number);
4241
+ return title === void 0 ? `[#${number}](${issue_url(slug, number)})` : citation_line(slug, number, title);
4242
+ };
4243
+ }
4244
+ function qualified_target(token) {
4245
+ const match = REPO_QUALIFIED.exec(token);
4246
+ if (match === null) return void 0;
4247
+ const [, repo = "", number = ""] = match;
4248
+ return { number, repo };
4249
+ }
4250
+ function bare_target(token, default_repo) {
4251
+ const match = BARE_NUMBER.exec(token);
4252
+ if (match === null) return void 0;
4253
+ const [, number = ""] = match;
4254
+ return { number, repo: default_repo };
4255
+ }
4256
+ function parse_target(token, default_repo) {
4257
+ return qualified_target(token) ?? bare_target(token, default_repo);
4258
+ }
4259
+ function label(target) {
4260
+ return target.repo === void 0 ? `#${target.number}` : `${target.repo}#${target.number}`;
4261
+ }
4262
+ function missing_line(target) {
4263
+ return `\u2716 ${label(target)}: issue does not resolve \u2014 check the number and the repository`;
4264
+ }
4265
+ function unreadable_line(target) {
4266
+ return `\u2716 ${label(target)}: could not be read \u2014 a rate limit, expired auth, or a dropped connection`;
4267
+ }
4268
+ function no_repo_line(target) {
4269
+ return `\u2716 ${label(target)}: could not read this repository \u2014 check gh auth and the origin remote`;
4270
+ }
4271
+ var issue_cite = {
4272
+ citation_line,
4273
+ citer,
4274
+ issue_url,
4275
+ missing_line,
4276
+ no_repo_line,
4277
+ parse_target,
4278
+ unreadable_line
4279
+ };
4280
+
4281
+ // scripts/gh/git-gh-api-path.ts
3373
4282
  var FULL_PAGE_QUERY = "?per_page=100";
3374
4283
  function repo_api_path(repo) {
3375
4284
  return `repos/${repo ?? "{owner}/{repo}"}`;
@@ -3431,6 +4340,36 @@ var git_gh_api_path = {
3431
4340
  commit_status_api_path
3432
4341
  };
3433
4342
 
4343
+ // scripts/lib/timeouts.ts
4344
+ var FETCH_TIMEOUT_MS = 1e4;
4345
+ var COMMAND_TIMEOUT_MS = 3e4;
4346
+
4347
+ // scripts/lib/error-message.ts
4348
+ function message_of(error) {
4349
+ return error instanceof Error ? error.message : String(error);
4350
+ }
4351
+ var error_text = { message_of };
4352
+
4353
+ // scripts/gh/git-gh-exec.ts
4354
+ import { execa as execa5, execaSync as execaSync2 } from "execa";
4355
+
4356
+ // scripts/gh/git-gh-check.ts
4357
+ import { execa as execa4 } from "execa";
4358
+ var GH_NOT_INSTALLED_MSG = "gh CLI is not installed. Install it from https://cli.github.com/";
4359
+ var gh_check = {};
4360
+ async function run_gh_check() {
4361
+ try {
4362
+ await execa4("gh", ["--version"], { timeout: COMMAND_TIMEOUT_MS });
4363
+ } catch {
4364
+ delete gh_check.promise;
4365
+ throw new Error(GH_NOT_INSTALLED_MSG);
4366
+ }
4367
+ }
4368
+ async function check_gh_installed() {
4369
+ gh_check.promise ??= run_gh_check();
4370
+ await gh_check.promise;
4371
+ }
4372
+
3434
4373
  // scripts/git/parse-json-array.ts
3435
4374
  var MALFORMED_JSON = /* @__PURE__ */ Symbol("malformed-json");
3436
4375
  function parse_json_loose(raw_json) {
@@ -3454,52 +4393,18 @@ function read_json_listing(raw_json, element_schema) {
3454
4393
  return { kind: "unexpected_shape" };
3455
4394
  }
3456
4395
  }
3457
- function parse_json_array_safe(raw_json, element_schema) {
3458
- const value = parse_json_loose(raw_json);
3459
- if (value === MALFORMED_JSON) return [];
3460
- return element_schema.array().parse(value);
3461
- }
3462
4396
  function parse_json_object_safe(raw_json, schema) {
3463
4397
  const value = parse_json_loose(raw_json);
3464
4398
  if (value === MALFORMED_JSON) return void 0;
3465
4399
  return schema.parse(value);
3466
4400
  }
3467
4401
  var parse_json = {
3468
- parse_json_array_safe,
3469
- parse_json_array_or_undefined,
3470
- parse_json_object_safe,
3471
- read_json_listing
4402
+ parse_json_object_safe
3472
4403
  };
3473
4404
 
3474
- // scripts/lib/error-message.ts
3475
- function message_of(error) {
3476
- return error instanceof Error ? error.message : String(error);
3477
- }
3478
- var error_text = { message_of };
3479
-
3480
- // scripts/git/git-gh-exec.ts
3481
- import { execa as execa4, execaSync as execaSync2 } from "execa";
3482
-
3483
- // scripts/git/git-gh-check.ts
3484
- import { execa as execa3 } from "execa";
3485
- var GH_NOT_INSTALLED_MSG = "gh CLI is not installed. Install it from https://cli.github.com/";
3486
- var gh_check = {};
3487
- async function run_gh_check() {
3488
- try {
3489
- await execa3("gh", ["--version"]);
3490
- } catch {
3491
- delete gh_check.promise;
3492
- throw new Error(GH_NOT_INSTALLED_MSG);
3493
- }
3494
- }
3495
- async function check_gh_installed() {
3496
- gh_check.promise ??= run_gh_check();
3497
- await gh_check.promise;
3498
- }
3499
-
3500
- // scripts/git/git-gh-failure.ts
3501
- import { z as z7 } from "zod";
3502
- var error_document_schema = z7.object({ status: z7.union([z7.string(), z7.number()]).optional() }).catch({});
4405
+ // scripts/gh/git-gh-failure.ts
4406
+ import { z as z10 } from "zod";
4407
+ var error_document_schema = z10.object({ status: z10.union([z10.string(), z10.number()]).optional() }).catch({});
3503
4408
  var GH_FAILURE_FIELD = "gh_failure";
3504
4409
  function has_gh_failure(error) {
3505
4410
  return error instanceof Error && Object.hasOwn(error, GH_FAILURE_FIELD);
@@ -3526,7 +4431,7 @@ function failure_of(error) {
3526
4431
  }
3527
4432
  var gh_failure = { attach, classify_stdout, failure_of, parse_response_status };
3528
4433
 
3529
- // scripts/git/git-gh-exec.ts
4434
+ // scripts/gh/git-gh-exec.ts
3530
4435
  var BODY_FROM_STDIN = "-";
3531
4436
  var GH_REQUEST_TIMEOUT_MS = 6e4;
3532
4437
  function to_timeout_option(timeout_ms) {
@@ -3568,7 +4473,7 @@ async function exec_gh_command(arguments_, timeout_ms, cwd) {
3568
4473
  await check_gh_installed();
3569
4474
  const options = to_spawn_options(timeout_ms, cwd);
3570
4475
  try {
3571
- const { stdout } = await execa4("gh", arguments_, options);
4476
+ const { stdout } = await execa5("gh", arguments_, options);
3572
4477
  return stdout.trimEnd();
3573
4478
  } catch (error) {
3574
4479
  throw to_gh_error(error);
@@ -3578,7 +4483,7 @@ async function exec_gh_command_with_stdin(input) {
3578
4483
  await check_gh_installed();
3579
4484
  const options = { input: input.stdin_body, ...to_spawn_options(input.timeout_ms, input.cwd) };
3580
4485
  try {
3581
- const { stdout } = await execa4("gh", input.args, options);
4486
+ const { stdout } = await execa5("gh", input.args, options);
3582
4487
  return stdout.trimEnd();
3583
4488
  } catch (error) {
3584
4489
  throw to_gh_error(error);
@@ -3592,20 +4497,20 @@ function parse_status_line(output) {
3592
4497
  function status_of_failure(error) {
3593
4498
  return has_stdout_field(error) ? parse_status_line(error.stdout) : void 0;
3594
4499
  }
3595
- async function exec_gh_api_status(path4, timeout_ms) {
4500
+ async function exec_gh_api_status(path6, timeout_ms) {
3596
4501
  const options = to_spawn_options(timeout_ms);
3597
4502
  try {
3598
4503
  await check_gh_installed();
3599
- const { stdout } = await execa4("gh", ["api", "--include", "--silent", path4], options);
4504
+ const { stdout } = await execa5("gh", ["api", "--include", "--silent", path6], options);
3600
4505
  return parse_status_line(stdout);
3601
4506
  } catch (error) {
3602
4507
  return status_of_failure(error);
3603
4508
  }
3604
4509
  }
3605
- function exec_gh_api_status_sync(path4, timeout_ms) {
4510
+ function exec_gh_api_status_sync(path6, timeout_ms) {
3606
4511
  const options = to_spawn_options(timeout_ms);
3607
4512
  try {
3608
- const { stdout } = execaSync2("gh", ["api", "--include", "--silent", path4], options);
4513
+ const { stdout } = execaSync2("gh", ["api", "--include", "--silent", path6], options);
3609
4514
  return parse_status_line(stdout);
3610
4515
  } catch (error) {
3611
4516
  return status_of_failure(error);
@@ -3677,7 +4582,9 @@ var git_gh_exec = {
3677
4582
  export {
3678
4583
  shell_segments,
3679
4584
  bash_triggers,
3680
- run_progress_clock,
4585
+ issue_cite,
4586
+ git_command,
4587
+ git_common_directory,
3681
4588
  tail_commands,
3682
4589
  filing_cap,
3683
4590
  CONTEXT_CUT_THRESHOLD,
@@ -3685,8 +4592,6 @@ export {
3685
4592
  agent_session_environment,
3686
4593
  agent_role_profile,
3687
4594
  backlog_budget,
3688
- git_command,
3689
- lane_child_invocation,
3690
4595
  run_hold,
3691
4596
  run_cut,
3692
4597
  run_tail,
@@ -3703,21 +4608,26 @@ export {
3703
4608
  RELEASE_CLASSIFICATION_NAMES,
3704
4609
  has_any_label,
3705
4610
  has_label_name,
4611
+ process_identity,
4612
+ run_event_stream,
4613
+ run_invocation,
4614
+ run_carry,
4615
+ run_event_stream_emit,
4616
+ lane_handoff,
3706
4617
  lane_park,
3707
4618
  repo_origin,
3708
4619
  repo_party,
4620
+ process_owner_schema,
4621
+ lane_registry,
4622
+ run_headless,
3709
4623
  FULL_PAGE_QUERY,
3710
4624
  git_gh_api_path,
3711
4625
  error_text,
4626
+ FETCH_TIMEOUT_MS,
3712
4627
  parse_json_array_or_undefined,
3713
4628
  read_json_listing,
3714
4629
  parse_json_object_safe,
3715
4630
  gh_failure,
3716
4631
  has_stderr_field,
3717
- git_gh_exec,
3718
- process_identity,
3719
- lane_registry,
3720
- run_invocation,
3721
- run_carry,
3722
- run_watcher_guard
4632
+ git_gh_exec
3723
4633
  };