@joshuafolkken/kit 1.1050.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 (581) hide show
  1. package/.claude/settings.json +9 -8
  2. package/.claude/skills/epic-commands/SKILL.md +5 -7
  3. package/.claude/skills/workflow-commands/SKILL.md +19 -11
  4. package/.claude/skills/workflow-commands/background-commands.md +4 -3
  5. package/.claude/skills/workflow-commands/backlogrun-child.md +1 -1
  6. package/.claude/skills/workflow-commands/backlogrun-lanes.md +8 -6
  7. package/.claude/skills/workflow-commands/backlogrun-progress.md +1 -1
  8. package/.claude/skills/workflow-commands/backlogrun-steps.md +10 -10
  9. package/.claude/skills/workflow-commands/backlogrun.md +2 -2
  10. package/.claude/skills/workflow-commands/chain-rule.md +4 -2
  11. package/.claude/skills/workflow-commands/delegation.md +3 -3
  12. package/.claude/skills/workflow-commands/followup-reference.md +1 -1
  13. package/.claude/skills/workflow-commands/followup.md +7 -3
  14. package/.claude/skills/workflow-commands/fullrun-steps.md +3 -2
  15. package/.claude/skills/workflow-commands/fullrun.md +3 -2
  16. package/.claude/skills/workflow-commands/halfrun.md +16 -19
  17. package/.claude/skills/workflow-commands/issue-comments.md +1 -1
  18. package/.claude/skills/workflow-commands/issue-fold-existing.md +10 -10
  19. package/.claude/skills/workflow-commands/issue-scout.md +1 -1
  20. package/.claude/skills/workflow-commands/kickoff.md +9 -4
  21. package/.claude/skills/workflow-commands/latest-gate.md +1 -1
  22. package/.claude/skills/workflow-commands/needs-human-review.md +1 -1
  23. package/.claude/skills/workflow-commands/observation-filing.md +3 -3
  24. package/.claude/skills/workflow-commands/prerequisite.md +1 -1
  25. package/.claude/skills/workflow-commands/prrun.md +36 -0
  26. package/.claude/skills/workflow-commands/retrospective.md +1 -1
  27. package/.claude/skills/workflow-commands/working-tree-hold.md +22 -2
  28. package/.codex/hooks.json +16 -20
  29. package/.github/actions/setup-pnpm/action.yml +93 -0
  30. package/.github/dependabot.yml +5 -1
  31. package/.github/workflows/ci.yml +26 -128
  32. package/.github/workflows/pr-classification.yml +5 -41
  33. package/.github/workflows/publish.yml +33 -38
  34. package/.github/workflows/sonar-qube.yml +37 -4
  35. package/AGENTS.md +12 -2
  36. package/CLAUDE.md +15 -16
  37. package/GEMINI.md +14 -2
  38. package/README.md +59 -18
  39. package/SECURITY.md +36 -6
  40. package/cspell/index.yaml +2 -1
  41. package/dist/CLAUDE.md +15 -16
  42. package/dist/config-merge/index.js +15 -3
  43. package/dist/hooks/batch-guard.js +4 -4
  44. package/dist/hooks/{chunk-HJIISEAC.js → chunk-3C4YCCEF.js} +5 -13
  45. package/dist/hooks/{chunk-EJDX2TOB.js → chunk-7ZPFAVR7.js} +7 -88
  46. package/dist/hooks/{chunk-WQ4OSBSS.js → chunk-E54KGFEQ.js} +2 -2
  47. package/dist/hooks/{chunk-GQ7KP45M.js → chunk-IL27YTPH.js} +2 -2
  48. package/dist/hooks/{chunk-DMQN45FN.js → chunk-KHWHKJOM.js} +3 -3
  49. package/dist/hooks/{chunk-5BK3K7V4.js → chunk-M2XDCUKD.js} +997 -478
  50. package/dist/hooks/{chunk-7VPDUBRN.js → chunk-NKOG3XAV.js} +150 -153
  51. package/dist/hooks/{chunk-NJQOBE6L.js → chunk-O4LL4HTI.js} +2 -7
  52. package/dist/hooks/{chunk-NAS4C7GP.js → chunk-OIZUHPJI.js} +2 -2
  53. package/dist/hooks/{chunk-RLDX5SOC.js → chunk-UD24EAIL.js} +2084 -1412
  54. package/dist/hooks/codex-hook-adapter.js +10 -11
  55. package/dist/hooks/duplicate-read-guard.js +5 -5
  56. package/dist/hooks/format-edited.js +2 -2
  57. package/dist/hooks/inputs.json +279 -0
  58. package/dist/hooks/investigation-guard.js +5 -5
  59. package/dist/hooks/pretool-guard.js +9 -9
  60. package/dist/hooks/session-lang.js +0 -1
  61. package/dist/hooks/stop-guard.js +178 -324
  62. package/dist/josh.js +162 -119
  63. package/dist/managed-marker/index.js +2 -1
  64. package/dist/version/index.js +34 -58
  65. package/eslint/rules/promise.js +5 -0
  66. package/package.json +9 -4
  67. package/prompts/collaboration-workflow/issue-citation.md +5 -5
  68. package/prompts/collaboration-workflow/operating-rules.md +4 -4
  69. package/prompts/collaboration-workflow/overview.md +13 -1
  70. package/prompts/collaboration-workflow/report-format.md +1 -1
  71. package/prompts/collaboration-workflow/residency.md +3 -3
  72. package/prompts/collaboration-workflow/rule-delivery.md +1 -1
  73. package/prompts/collaboration-workflow/turn-batching.md +3 -3
  74. package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
  75. package/prompts/collaboration-workflow/wip-cap.md +25 -22
  76. package/prompts/collaboration-workflow.md +5 -8
  77. package/prompts/review-rubric.md +1 -1
  78. package/prompts/review.md +3 -3
  79. package/prompts/sonar-hotspot-handling.md +11 -3
  80. package/prompts/testing-guide.md +3 -3
  81. package/scripts/adopt/adopt-toolkits.ts +0 -2
  82. package/scripts/adopt/adopt.ts +0 -2
  83. package/scripts/agent/agent-argv.ts +0 -2
  84. package/scripts/agent/agent-diagnostics.ts +1 -1
  85. package/scripts/agent/agent-role-profile.ts +0 -7
  86. package/scripts/agent/claude-agent-argv.ts +0 -2
  87. package/scripts/agent/codex-agent-argv.ts +1 -1
  88. package/scripts/agent/codex-hooks.ts +92 -0
  89. package/scripts/agent/interactive-ask.ts +1 -1
  90. package/scripts/auto-ok/auto-ok-cli.ts +6 -16
  91. package/scripts/backlog/backlog-budget.ts +0 -2
  92. package/scripts/backlog/backlog-defect-priority.ts +2 -2
  93. package/scripts/backlog/backlog-drive-cli.ts +10 -8
  94. package/scripts/backlog/backlog-drive-named-offer.ts +16 -3
  95. package/scripts/backlog/backlog-drive-named.ts +2 -1
  96. package/scripts/backlog/backlog-drive.ts +76 -16
  97. package/scripts/backlog/backlog-named.ts +0 -3
  98. package/scripts/backlog/backlog-next.ts +9 -15
  99. package/scripts/backlog/backlog-offer-cli.ts +0 -5
  100. package/scripts/backlog/backlog-plan-cli.ts +1 -7
  101. package/scripts/backlog/backlog-plan.ts +1 -1
  102. package/scripts/backlog/backlog-pool.ts +3 -6
  103. package/scripts/backlog/backlog-rank.ts +2 -2
  104. package/scripts/backlog/backlog-ready.ts +19 -3
  105. package/scripts/backlog/backlog-scope.ts +1 -3
  106. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  107. package/scripts/backlog/backlog-stalled.ts +0 -1
  108. package/scripts/backlog/backlog-waves.ts +5 -2
  109. package/scripts/behavior/behavior-cli.ts +1 -1
  110. package/scripts/build/build-codex-hooks.ts +26 -0
  111. package/scripts/build/build-hooks.ts +85 -9
  112. package/scripts/build/hook-bundle-stamp.ts +111 -0
  113. package/scripts/cases/cases-cli.ts +1 -1
  114. package/scripts/cases/cases-logic.ts +1 -1
  115. package/scripts/ci/composite-actions.ts +30 -0
  116. package/scripts/ci/pr-classification.ts +3 -3
  117. package/scripts/claude/workflow-destination.ts +10 -1
  118. package/scripts/clone/clone-fingerprint.ts +0 -1
  119. package/scripts/clone/clone-scan-cli.ts +1 -1
  120. package/scripts/clone/clone-scan.ts +1 -1
  121. package/scripts/config-merge/json-format.ts +0 -1
  122. package/scripts/config-merge/patch-yaml-list.ts +1 -1
  123. package/scripts/cost-runtime/codex-usage.ts +2 -9
  124. package/scripts/cost-runtime/cost-attribute.ts +0 -1
  125. package/scripts/cost-runtime/cost-cli.ts +0 -3
  126. package/scripts/cost-runtime/cost-corpus.ts +0 -2
  127. package/scripts/cost-runtime/cost-format.ts +0 -3
  128. package/scripts/cost-runtime/cost-pricing.ts +0 -1
  129. package/scripts/cost-runtime/cost-tokens.ts +0 -1
  130. package/scripts/delegation/delegation-cli.ts +1 -1
  131. package/scripts/delegation/delegation-policy.ts +0 -1
  132. package/scripts/delegation/duplicate-read-guard.ts +0 -8
  133. package/scripts/delegation/fanout-cli.ts +1 -1
  134. package/scripts/delegation/fanout.ts +0 -2
  135. package/scripts/delegation/investigation-reads.ts +1 -164
  136. package/scripts/discovery/repo-map-logic.ts +0 -1
  137. package/scripts/discovery/repo-party-cli.ts +1 -1
  138. package/scripts/doctor/doctor-consumer.ts +4 -6
  139. package/scripts/doctor/doctor-logic.ts +0 -4
  140. package/scripts/doctor/doctor.ts +14 -9
  141. package/scripts/document/document-byte-budget.ts +4 -5
  142. package/scripts/document/document-byte-check.ts +0 -1
  143. package/scripts/document/document-read-cli.ts +0 -2
  144. package/scripts/document/document-section-cli.ts +1 -1
  145. package/scripts/document/document-section.ts +0 -3
  146. package/scripts/document/edit-files-cli.ts +0 -1
  147. package/scripts/document/entry-read-budget.ts +10 -2
  148. package/scripts/document/lane-child-read-set.ts +1 -6
  149. package/scripts/document/read-files-cli.ts +0 -4
  150. package/scripts/document/read-set-cli.ts +0 -2
  151. package/scripts/document/read-set-point-of-use.ts +1 -0
  152. package/scripts/document/read-set-pricing.ts +0 -1
  153. package/scripts/document/read-set-trim.ts +1 -1
  154. package/scripts/epic/epic-add.ts +1 -1
  155. package/scripts/epic/epic-audit-cli.ts +1 -4
  156. package/scripts/epic/epic-audit-orphans.ts +1 -3
  157. package/scripts/epic/epic-audit-rationale.ts +0 -1
  158. package/scripts/epic/epic-audit-report.ts +0 -2
  159. package/scripts/epic/epic-audit.ts +5 -4
  160. package/scripts/epic/epic-bundle-cli.ts +1 -1
  161. package/scripts/epic/epic-bundle-evidence.ts +0 -1
  162. package/scripts/epic/epic-bundle-referenced.ts +1 -5
  163. package/scripts/epic/epic-bundle.ts +0 -4
  164. package/scripts/epic/epic-busy.ts +4 -5
  165. package/scripts/epic/epic-candidate-confirm.ts +0 -5
  166. package/scripts/epic/epic-classify.ts +2 -2
  167. package/scripts/epic/epic-cli-argv.ts +0 -1
  168. package/scripts/epic/epic-close-comment.ts +1 -1
  169. package/scripts/epic/epic-close.ts +5 -2
  170. package/scripts/epic/epic-cross-repo.ts +2 -4
  171. package/scripts/epic/epic-decision.ts +0 -1
  172. package/scripts/epic/epic-fetch.ts +2 -2
  173. package/scripts/epic/epic-index.ts +2 -3
  174. package/scripts/epic/epic-issue.ts +1 -15
  175. package/scripts/epic/epic-lane-offer.ts +9 -8
  176. package/scripts/epic/epic-nested.ts +1 -1
  177. package/scripts/epic/epic-next-read.ts +1 -3
  178. package/scripts/epic/epic-next-views.ts +0 -1
  179. package/scripts/epic/epic-next.ts +1 -10
  180. package/scripts/epic/epic-parse.ts +0 -1
  181. package/scripts/epic/epic-promote.ts +0 -9
  182. package/scripts/epic/epic-rank.ts +2 -2
  183. package/scripts/epic/epic-read.ts +1 -2
  184. package/scripts/epic/epic-reconcile.ts +1 -1
  185. package/scripts/epic/epic-reference.ts +1 -13
  186. package/scripts/epic/epic-relation-recheck.ts +1 -1
  187. package/scripts/epic/epic-relations.ts +1 -1
  188. package/scripts/epic/epic-remove.ts +1 -1
  189. package/scripts/epic/epic-report.ts +0 -1
  190. package/scripts/epic/epic-run.ts +2 -3
  191. package/scripts/epic/epic-shape.ts +1 -1
  192. package/scripts/epic/epic-solo-stale.ts +107 -0
  193. package/scripts/epic/epic-solo.ts +3 -2
  194. package/scripts/epic/epic-triage.ts +1 -1
  195. package/scripts/epic/epic-validate.ts +1 -1
  196. package/scripts/epic/epic.ts +1 -1
  197. package/scripts/exports/unused-members-cli.ts +53 -0
  198. package/scripts/{git → followup}/followup-issue-number.ts +1 -6
  199. package/scripts/{git → followup}/git-followup-cleanup.ts +2 -1
  200. package/scripts/{git → followup}/git-followup-finish.ts +1 -3
  201. package/scripts/{git → followup}/git-followup-flush.ts +2 -2
  202. package/scripts/{git → followup}/git-followup-issue-close.ts +2 -2
  203. package/scripts/{git → followup}/git-followup-label.ts +4 -4
  204. package/scripts/followup/git-followup-merged.ts +112 -0
  205. package/scripts/{git → followup}/git-followup-pending.ts +1 -1
  206. package/scripts/{git → followup}/git-followup-workflow.ts +19 -11
  207. package/scripts/{git → followup}/git-pr-followup-wrapup.ts +9 -3
  208. package/scripts/{git → followup}/git-pr-followup.ts +53 -16
  209. package/scripts/gate/core-budget.ts +3 -9
  210. package/scripts/gate/gate-plan.ts +7 -4
  211. package/scripts/gate/gate-report.ts +0 -6
  212. package/scripts/gate/gate-tree.ts +1 -1
  213. package/scripts/gate/pre-commit-type-check.ts +0 -6
  214. package/scripts/gate/project-checks.ts +7 -8
  215. package/scripts/gate/scoped-green.ts +0 -1
  216. package/scripts/gate/type-check-step.ts +1 -1
  217. package/scripts/gate/verification-gate.ts +0 -2
  218. package/scripts/gh/fix-gh-packages.ts +15 -12
  219. package/scripts/gh/gh-spawn.ts +2 -2
  220. package/scripts/{git → gh}/git-ai-review-scan.ts +1 -5
  221. package/scripts/{git → gh}/git-gh-check.ts +2 -1
  222. package/scripts/{git → gh}/git-gh-exec.ts +1 -1
  223. package/scripts/{git → gh}/git-gh-failure.ts +1 -1
  224. package/scripts/{git → gh}/git-gh-helpers.ts +0 -1
  225. package/scripts/{git → gh}/git-gh-issue-list.ts +2 -0
  226. package/scripts/{git → gh}/git-gh-issue-read.ts +1 -1
  227. package/scripts/{git → gh}/git-gh-issue-rest.ts +6 -3
  228. package/scripts/{git → gh}/git-gh-pr-read.ts +24 -0
  229. package/scripts/{git → gh}/git-gh-pr-rest.ts +5 -2
  230. package/scripts/{git → gh}/git-gh-pr-review.ts +2 -2
  231. package/scripts/{git → gh}/git-gh-pr-rollup.ts +1 -3
  232. package/scripts/{git → gh}/git-gh-pr.ts +3 -1
  233. package/scripts/{git → gh}/git-pr-ai-review.ts +2 -5
  234. package/scripts/{git → gh}/git-pr-checks-eval.ts +0 -10
  235. package/scripts/{git → gh}/git-pr-checks-parse.ts +4 -20
  236. package/scripts/{git → gh}/git-pr-checks.ts +2 -1
  237. package/scripts/{git → gh}/git-pr-coderabbit.ts +2 -5
  238. package/scripts/{git → gh}/git-pr-confirmation.ts +1 -3
  239. package/scripts/{git → gh}/git-pr-managed-config.ts +2 -3
  240. package/scripts/{git → gh}/git-pr.ts +3 -3
  241. package/scripts/git/changed-file-scope.ts +0 -1
  242. package/scripts/git/git-branch.ts +3 -6
  243. package/scripts/git/git-commit.ts +1 -1
  244. package/scripts/git/git-common-directory.ts +80 -24
  245. package/scripts/git/git-error.ts +66 -9
  246. package/scripts/git/git-issue.ts +1 -1
  247. package/scripts/git/git-preflight.ts +2 -2
  248. package/scripts/git/git-prompt.ts +6 -31
  249. package/scripts/git/git-push.ts +1 -1
  250. package/scripts/git/git-staging.ts +1 -0
  251. package/scripts/git/git-status.ts +1 -1
  252. package/scripts/git/git-workflow.ts +1 -1
  253. package/scripts/git/gone-branch.ts +1 -0
  254. package/scripts/git/listing-cutoff.ts +1 -1
  255. package/scripts/git/main-merge.ts +1 -1
  256. package/scripts/git/main-sync.ts +19 -1
  257. package/scripts/git/parse-json-array.ts +0 -3
  258. package/scripts/git/repository-lock.ts +16 -16
  259. package/scripts/git/{git-stash.ts → stash/git-stash.ts} +1 -1
  260. package/scripts/git/{stash-pop-args.ts → stash/stash-pop-args.ts} +1 -1
  261. package/scripts/git/{stash-pop-cli.ts → stash/stash-pop-cli.ts} +0 -1
  262. package/scripts/git/{stash-sweep-lock.ts → stash/stash-sweep-lock.ts} +3 -3
  263. package/scripts/hooks/codex-hook-adapter.ts +0 -1
  264. package/scripts/hooks/edited-cspell.ts +1 -1
  265. package/scripts/hooks/format-edited-file.ts +2 -0
  266. package/scripts/hooks/pre-push-unit.ts +0 -4
  267. package/scripts/hooks/pretool-guard.ts +22 -4
  268. package/scripts/hooks/reserved-run.ts +1 -1
  269. package/scripts/hooks/step-zero-notice.ts +76 -0
  270. package/scripts/hooks/stop-guard.ts +47 -21
  271. package/scripts/init/claude-plugin-config.ts +4 -5
  272. package/scripts/init/hook-command-rewrite.ts +16 -8
  273. package/scripts/init/hook-launch.ts +41 -15
  274. package/scripts/init/init-basic.ts +2 -5
  275. package/scripts/init/init-logic.ts +7 -0
  276. package/scripts/init/init.ts +0 -3
  277. package/scripts/init/project-profile.ts +6 -11
  278. package/scripts/init/start-exec.ts +1 -1
  279. package/scripts/init/start-setup-pr.ts +6 -6
  280. package/scripts/init/start-steps.ts +1 -0
  281. package/scripts/issue/behavior-change-lint.ts +0 -1
  282. package/scripts/issue/defect-rate-cli.ts +3 -3
  283. package/scripts/issue/defect-rate.ts +1 -2
  284. package/scripts/{git → issue}/git-next-issues.ts +3 -3
  285. package/scripts/issue/issue-backlinks-cli.ts +1 -5
  286. package/scripts/issue/issue-cite-cli.ts +2 -6
  287. package/scripts/issue/issue-cite.ts +0 -2
  288. package/scripts/issue/issue-classification.ts +1 -9
  289. package/scripts/issue/issue-closing-pr.ts +4 -4
  290. package/scripts/issue/issue-comment-cli.ts +2 -2
  291. package/scripts/issue/issue-file-cli.ts +5 -5
  292. package/scripts/issue/issue-file.ts +2 -2
  293. package/scripts/issue/issue-fold-cli.ts +1 -1
  294. package/scripts/issue/issue-fold-existing-cli.ts +1 -1
  295. package/scripts/{git → issue}/issue-labels.ts +8 -0
  296. package/scripts/issue/issue-lint-cli.ts +1 -1
  297. package/scripts/issue/issue-merged.ts +2 -2
  298. package/scripts/{git → issue}/issue-rank.ts +1 -1
  299. package/scripts/issue/issue-read-cli.ts +8 -47
  300. package/scripts/issue/issue-read.ts +0 -2
  301. package/scripts/issue/issue-report-failures.ts +80 -0
  302. package/scripts/issue/issue-scout-cli.ts +1 -7
  303. package/scripts/issue/issue-scout-closed.ts +2 -2
  304. package/scripts/issue/issue-scout.ts +0 -2
  305. package/scripts/issue/issue-state-cli.ts +5 -46
  306. package/scripts/issue/issue-state.ts +1 -5
  307. package/scripts/issue/markdown-section.ts +1 -1
  308. package/scripts/issue/measure-rerun-cli.ts +1 -1
  309. package/scripts/issue/reproduction-measure.ts +1 -1
  310. package/scripts/josh/cli-body.ts +1 -1
  311. package/scripts/josh/file-map-stamp.ts +0 -3
  312. package/scripts/josh/josh-command-map.ts +2 -0
  313. package/scripts/josh/josh-commands-ai.ts +9 -102
  314. package/scripts/josh/josh-commands-backlog.ts +5 -1
  315. package/scripts/josh/josh-commands-development.ts +8 -1
  316. package/scripts/josh/josh-commands-issue.ts +2 -2
  317. package/scripts/josh/josh-commands-maintenance.ts +7 -0
  318. package/scripts/josh/josh-commands-project.ts +6 -0
  319. package/scripts/josh/josh-commands-run.ts +114 -0
  320. package/scripts/josh/josh-commands-versioning.ts +8 -0
  321. package/scripts/josh/josh-commands-workflow.ts +2 -2
  322. package/scripts/josh/path-decision.ts +0 -3
  323. package/scripts/josh/process-owner.ts +16 -0
  324. package/scripts/josh/session-language-cli.ts +0 -1
  325. package/scripts/lane/lane-await.ts +3 -6
  326. package/scripts/lane/lane-capacity.ts +6 -3
  327. package/scripts/lane/lane-child-invocation.ts +20 -7
  328. package/scripts/lane/lane-cli.ts +1 -4
  329. package/scripts/lane/lane-close.ts +1 -3
  330. package/scripts/lane/lane-dispatch.ts +3 -4
  331. package/scripts/lane/lane-environment.ts +0 -9
  332. package/scripts/lane/lane-handoff.ts +38 -0
  333. package/scripts/lane/lane-launch-cli.ts +21 -11
  334. package/scripts/lane/lane-open.ts +0 -3
  335. package/scripts/lane/lane-output.ts +0 -1
  336. package/scripts/lane/lane-registry.ts +9 -0
  337. package/scripts/lane/openai-lane-ship.ts +2 -0
  338. package/scripts/lane/openai-lane-supervisor-decision.ts +2 -1
  339. package/scripts/lane/openai-lane-supervisor.ts +6 -4
  340. package/scripts/lane/openai-review-broker.ts +3 -0
  341. package/scripts/lib/bounded-pool.ts +1 -0
  342. package/scripts/lib/json-value.ts +5 -4
  343. package/scripts/lib/poll.ts +3 -1
  344. package/scripts/lib/timed-fetch.ts +15 -0
  345. package/scripts/lib/timeouts.ts +13 -0
  346. package/scripts/{yaml → lib}/yaml-document.ts +3 -1
  347. package/scripts/{bytes → lines}/bytes-command.ts +0 -2
  348. package/scripts/lines/lines-command.ts +0 -1
  349. package/scripts/lint/lint-related-scope.ts +0 -1
  350. package/scripts/lint/lint-related.ts +1 -1
  351. package/scripts/{git → notify}/telegram-notify.ts +2 -2
  352. package/scripts/{git → notify}/telegram-test.ts +4 -4
  353. package/scripts/observations/observation-ledger-home.ts +0 -1
  354. package/scripts/observations/observation-ledger-line.ts +0 -1
  355. package/scripts/observations/observation-ledger-migrate.ts +1 -1
  356. package/scripts/observations/observations-flush-landing.ts +3 -3
  357. package/scripts/observations/observations-flush.ts +2 -2
  358. package/scripts/overrides/lockfile-overrides.ts +1 -1
  359. package/scripts/overrides/overrides-logic.ts +1 -1
  360. package/scripts/package/package-scout-cli.ts +2 -7
  361. package/scripts/package/package-scout.ts +0 -2
  362. package/scripts/propagate/propagate-git-failure.ts +0 -1
  363. package/scripts/propagate/propagate-git.ts +0 -2
  364. package/scripts/propagate/propagate-publish.ts +2 -3
  365. package/scripts/propagate/propagate-run.ts +0 -2
  366. package/scripts/propagate/propagate-steps.ts +3 -5
  367. package/scripts/propagate/propagate-targets.ts +0 -4
  368. package/scripts/propagate/propagate.ts +0 -4
  369. package/scripts/refactor/import-graph.ts +0 -1
  370. package/scripts/refactor/refactor-lint.ts +0 -2
  371. package/scripts/refactor/refactor-scan.ts +0 -1
  372. package/scripts/refactor/refactor-targets.ts +0 -1
  373. package/scripts/registry-migration/migrate-logic.ts +0 -2
  374. package/scripts/registry-migration/migrate.ts +19 -9
  375. package/scripts/release/github-release-cli.ts +8 -6
  376. package/scripts/release/github-release-environment.ts +46 -0
  377. package/scripts/release/github-release.ts +151 -94
  378. package/scripts/release/release-history.ts +0 -1
  379. package/scripts/release/release-plan.ts +0 -1
  380. package/scripts/release/release-publish.ts +2 -3
  381. package/scripts/release/release-scope-cli.ts +1 -2
  382. package/scripts/release/release-tag.ts +2 -4
  383. package/scripts/repo/auto-merge-setting-logic.ts +1 -4
  384. package/scripts/repo/repo-setting.ts +1 -1
  385. package/scripts/repo/repository-labels.ts +3 -3
  386. package/scripts/repo/required-checks-logic.ts +206 -0
  387. package/scripts/repo/required-checks-report.ts +71 -0
  388. package/scripts/repo/ruleset-check-cli.ts +53 -0
  389. package/scripts/repo/ruleset-check.ts +147 -0
  390. package/scripts/report/report-lint-cli.ts +0 -4
  391. package/scripts/review/disposition-cli.ts +1 -1
  392. package/scripts/review/live-evidence.ts +1 -1
  393. package/scripts/review/review-attest-cli.ts +0 -6
  394. package/scripts/review/review-attest.ts +4 -16
  395. package/scripts/review/review-brief-cli.ts +23 -6
  396. package/scripts/review/review-brief.ts +21 -8
  397. package/scripts/review/review-diff-parts.ts +89 -0
  398. package/scripts/review/review-diff-write.ts +97 -0
  399. package/scripts/review/review-finding-ledger.ts +0 -1
  400. package/scripts/review/review-record-cli.ts +1 -1
  401. package/scripts/review/review-record.ts +0 -1
  402. package/scripts/review/review-round2-cli.ts +1 -1
  403. package/scripts/review/review-stamps.ts +0 -3
  404. package/scripts/review/review-tree.ts +14 -2
  405. package/scripts/rules/decision-oracle-stage.ts +18 -0
  406. package/scripts/rules/decision-oracle.ts +10 -7
  407. package/scripts/rules/delivered-rules.ts +9 -1
  408. package/scripts/rules/destructive-command.ts +119 -0
  409. package/scripts/rules/direct-filing.ts +1 -1
  410. package/scripts/rules/file-body.ts +0 -3
  411. package/scripts/rules/filing-cap.ts +0 -2
  412. package/scripts/rules/gh-api.ts +7 -2
  413. package/scripts/rules/git-argv.ts +18 -7
  414. package/scripts/rules/git-force.ts +0 -2
  415. package/scripts/rules/implementation-cut.ts +0 -1
  416. package/scripts/rules/index-guard.ts +71 -0
  417. package/scripts/rules/issue-citation.ts +50 -25
  418. package/scripts/rules/lane-background.ts +15 -7
  419. package/scripts/rules/lane-park.ts +13 -6
  420. package/scripts/rules/last-prompt.ts +1 -0
  421. package/scripts/rules/oracle-consulted.ts +1 -1
  422. package/scripts/rules/oracle-firing.ts +1 -0
  423. package/scripts/rules/oracle-list-cli.ts +0 -3
  424. package/scripts/rules/permission-guards.ts +14 -0
  425. package/scripts/rules/protected-files.ts +74 -0
  426. package/scripts/rules/reply-language.ts +1 -1
  427. package/scripts/rules/rule-body-guard.ts +0 -3
  428. package/scripts/rules/rule-value-cli.ts +0 -1
  429. package/scripts/rules/shell-body-trigger.ts +0 -1
  430. package/scripts/rules/shell-segments.ts +0 -1
  431. package/scripts/rules/stop-rules.ts +13 -5
  432. package/scripts/rules/tail-commands.ts +1 -1
  433. package/scripts/rules/test-declared-commit.ts +1 -1
  434. package/scripts/rules/third-party-write.ts +0 -1
  435. package/scripts/rules/worktree-guard.ts +26 -9
  436. package/scripts/run/run-carry-cli.ts +0 -2
  437. package/scripts/run/run-carry-stash.ts +2 -2
  438. package/scripts/run/run-carry-streak.ts +1 -1
  439. package/scripts/run/run-cut-cli.ts +0 -7
  440. package/scripts/run/run-cut.ts +8 -54
  441. package/scripts/run/run-ending-cli.ts +1 -1
  442. package/scripts/run/run-ending.ts +1 -4
  443. package/scripts/run/run-entry-cli.ts +101 -24
  444. package/scripts/run/run-entry.ts +0 -1
  445. package/scripts/run/run-event-cli.ts +0 -2
  446. package/scripts/run/run-event-follow.ts +1 -0
  447. package/scripts/run/run-event-render.ts +1 -1
  448. package/scripts/run/run-event-scope.ts +53 -4
  449. package/scripts/run/run-event-stream.ts +0 -3
  450. package/scripts/run/run-event-watch.ts +1 -0
  451. package/scripts/run/run-halfrun-resume.ts +20 -7
  452. package/scripts/run/run-headless.ts +2 -2
  453. package/scripts/run/run-hold-cli.ts +33 -10
  454. package/scripts/run/run-hold.ts +25 -5
  455. package/scripts/run/run-issue-number.ts +1 -1
  456. package/scripts/run/run-liveness-cli.ts +1 -1
  457. package/scripts/run/run-liveness.ts +2 -3
  458. package/scripts/run/run-merge-cli.ts +5 -13
  459. package/scripts/run/run-merge-steps.ts +5 -5
  460. package/scripts/run/run-merge.ts +1 -1
  461. package/scripts/run/run-next-cli.ts +1 -1
  462. package/scripts/run/run-parent-cut-hook.ts +0 -1
  463. package/scripts/run/run-preflight.ts +2 -9
  464. package/scripts/run/run-prep-cli.ts +2 -2
  465. package/scripts/run/run-prep-locate.ts +1 -1
  466. package/scripts/run/run-progress-cli.ts +4 -7
  467. package/scripts/run/run-progress-clock.ts +6 -34
  468. package/scripts/run/run-progress-config.ts +0 -4
  469. package/scripts/run/run-progress-read.ts +1 -2
  470. package/scripts/run/run-progress.ts +1 -2
  471. package/scripts/run/run-prrun-resume.ts +117 -0
  472. package/scripts/run/run-report-cli.ts +1 -2
  473. package/scripts/run/run-report.ts +0 -1
  474. package/scripts/run/run-retrospective.ts +1 -1
  475. package/scripts/run/run-review-cli.ts +0 -3
  476. package/scripts/run/run-review-steps.ts +2 -0
  477. package/scripts/run/run-review.ts +0 -2
  478. package/scripts/run/run-ship-cli.ts +21 -3
  479. package/scripts/run/run-ship-detach.ts +1 -0
  480. package/scripts/run/run-ship-next.ts +106 -0
  481. package/scripts/run/run-ship-preflight.ts +1 -1
  482. package/scripts/run/run-ship-return.ts +8 -3
  483. package/scripts/run/run-ship-review-steps.ts +18 -4
  484. package/scripts/run/run-ship-review.ts +11 -0
  485. package/scripts/run/run-ship-scoped.ts +1 -0
  486. package/scripts/run/run-ship-steps.ts +1 -1
  487. package/scripts/run/run-ship.ts +0 -1
  488. package/scripts/run/run-stage-read.ts +62 -0
  489. package/scripts/run/run-stage.ts +160 -0
  490. package/scripts/run/run-status-cli.ts +1 -1
  491. package/scripts/run/run-step-cli.ts +21 -14
  492. package/scripts/run/run-step.ts +1 -1
  493. package/scripts/run/run-stop-notify.ts +1 -3
  494. package/scripts/run/run-stranded-cli.ts +0 -4
  495. package/scripts/run/run-stranded-detect.ts +2 -2
  496. package/scripts/run/run-tail-cli.ts +17 -11
  497. package/scripts/run/run-tail.ts +2 -0
  498. package/scripts/run/run-tidy-lanes.ts +2 -1
  499. package/scripts/run/run-tidy-stashes.ts +12 -12
  500. package/scripts/run/run-wake-cli.ts +2 -3
  501. package/scripts/run/run-wake-loop.ts +2 -2
  502. package/scripts/run/run-wake.ts +2 -4
  503. package/scripts/run/run-watcher-guard-cli.ts +0 -4
  504. package/scripts/run/run-watcher-guard.ts +9 -7
  505. package/scripts/run/run-watcher-hook.ts +1 -1
  506. package/scripts/safe-chain/ci-installer-pin.ts +11 -5
  507. package/scripts/safe-chain/project-config.ts +1 -1
  508. package/scripts/security/security-audit-provision-logic.ts +0 -1
  509. package/scripts/security/security-audit-provision.ts +3 -2
  510. package/scripts/security/security-audit.ts +0 -1
  511. package/scripts/security/security-updates-logic.ts +0 -1
  512. package/scripts/sonar/sonar-hotspots-cli.ts +10 -52
  513. package/scripts/sonar/sonar-hotspots.ts +0 -1
  514. package/scripts/sonar/sonar-new-code-cli.ts +150 -0
  515. package/scripts/sonar/sonar-new-code.ts +79 -0
  516. package/scripts/sonar/sonar-project.ts +97 -0
  517. package/scripts/split/split-assess-cli.ts +1 -1
  518. package/scripts/split/split-assess.ts +2 -5
  519. package/scripts/sync/managed-config-scope-cli.ts +1 -1
  520. package/scripts/sync/skill-migration.ts +0 -1
  521. package/scripts/sync/sync-hook-safety.ts +2 -3
  522. package/scripts/sync/sync.ts +1 -8
  523. package/scripts/sync/template-source-logic.ts +0 -1
  524. package/scripts/sync/workflow-pin-logic.ts +16 -28
  525. package/scripts/test/test-declared-logic.ts +0 -1
  526. package/scripts/test/test-declared.ts +0 -2
  527. package/scripts/test/test-red-commit.ts +2 -2
  528. package/scripts/test/test-red.ts +14 -7
  529. package/scripts/test/test-type-logic.ts +1 -1
  530. package/scripts/test/unit-worker-share.ts +7 -16
  531. package/scripts/time-runtime/time-batch-guard.ts +1 -2
  532. package/scripts/time-runtime/time-bundle-call.ts +0 -1
  533. package/scripts/time-runtime/time-bundles.ts +0 -4
  534. package/scripts/time-runtime/time-density-hook.ts +0 -2
  535. package/scripts/time-runtime/time-followup-stage.ts +1 -1
  536. package/scripts/time-runtime/time-format.ts +0 -73
  537. package/scripts/time-runtime/time-round-trips.ts +0 -4
  538. package/scripts/time-runtime/time-single-check.ts +1 -1
  539. package/scripts/time-runtime/time-spans.ts +1 -1
  540. package/scripts/time-runtime/time-transcript-line.ts +1 -2
  541. package/scripts/ui/ui-routes-cli.ts +0 -5
  542. package/scripts/ui/ui-routes.ts +0 -5
  543. package/scripts/version/effective-upstream.ts +2 -2
  544. package/scripts/version/latest-corepack.ts +7 -9
  545. package/scripts/version/latest-scope-cli.ts +0 -2
  546. package/scripts/version/latest-update.ts +0 -2
  547. package/scripts/version/npm-registry.ts +2 -2
  548. package/scripts/version/publishable-range-check.ts +1 -1
  549. package/scripts/version/release-hold.ts +0 -4
  550. package/scripts/version/running-binary.ts +2 -2
  551. package/scripts/version/version-check-logic.ts +0 -2
  552. package/scripts/version/version-remote.ts +3 -3
  553. package/scripts/version/version-targets.ts +4 -4
  554. package/templates/sonar-project.properties +28 -1
  555. package/templates/workflows/dependabot-auto-merge.yml +5 -3
  556. package/templates/workflows/github-release.yml +81 -0
  557. package/prompts/collaboration-workflow/cross-repo-epic.md +0 -5
  558. package/prompts/collaboration-workflow/epic-audit.md +0 -5
  559. package/prompts/collaboration-workflow/epic-bundle.md +0 -5
  560. package/scripts/init/plugin-install-hint.ts +0 -47
  561. package/scripts/version/parse-json.ts +0 -13
  562. /package/scripts/{git → followup}/git-followup-stages.ts +0 -0
  563. /package/scripts/{git → gh}/git-gh-api-path.ts +0 -0
  564. /package/scripts/{git → gh}/git-gh-command.ts +0 -0
  565. /package/scripts/{git → gh}/git-gh-issue-write.ts +0 -0
  566. /package/scripts/{git → gh}/git-gh-issue.ts +0 -0
  567. /package/scripts/{git → gh}/git-gh-pr-auto-merge.ts +0 -0
  568. /package/scripts/{git → gh}/git-gh-pr-snapshot.ts +0 -0
  569. /package/scripts/{git → gh}/git-gh-reachability.ts +0 -0
  570. /package/scripts/{git → gh}/git-gh-repo.ts +0 -0
  571. /package/scripts/{git → gh}/git-gh-rest-state.ts +0 -0
  572. /package/scripts/{git → gh}/git-pr-checks-watch.ts +0 -0
  573. /package/scripts/{git → gh}/git-pr-error.ts +0 -0
  574. /package/scripts/{git → gh}/git-pr-messages.ts +0 -0
  575. /package/scripts/{git → gh}/github-issue-url.ts +0 -0
  576. /package/scripts/git/{stash-orphans.ts → stash/stash-orphans.ts} +0 -0
  577. /package/scripts/{git → issue}/issue-reference.ts +0 -0
  578. /package/scripts/{git → lib}/animation-helpers.ts +0 -0
  579. /package/scripts/{git → lib}/git-animation.ts +0 -0
  580. /package/scripts/{git → notify}/git-notify.ts +0 -0
  581. /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-WQ4OSBSS.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-7VPDUBRN.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
@@ -601,19 +598,20 @@ function hold_path(git_directory) {
601
598
  return stamp_file.stamp_path(HOLD_PREFIX, git_directory);
602
599
  }
603
600
  async function worktree_directory() {
604
- const directories = await git_command.git_directories();
605
- return directories[0];
601
+ const directories2 = await git_command.git_directories();
602
+ return directories2[0];
606
603
  }
607
604
  async function is_linked_worktree() {
608
- const directories = await git_command.git_directories();
609
- 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];
610
607
  }
611
608
  var run_hold_schema = z.object({
612
609
  issue: z.string(),
613
610
  taken_at: z.string(),
614
611
  pid: z.number(),
615
612
  is_fullrun: z.boolean().optional(),
616
- is_halfrun_stop: z.boolean().optional()
613
+ is_halfrun_stop: z.boolean().optional(),
614
+ prrun_stop_head: z.string().optional()
617
615
  });
618
616
  function parse_hold(raw) {
619
617
  try {
@@ -648,8 +646,12 @@ function write_hold(target, issue, now = /* @__PURE__ */ new Date()) {
648
646
  function create_hold(target, issue, now = /* @__PURE__ */ new Date(), is_fullrun) {
649
647
  return stamp_file.create_stamp(target, build_hold(issue, now, is_fullrun));
650
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
+ }
651
653
  function create_halfrun_stop_hold(target, issue, now = /* @__PURE__ */ new Date()) {
652
- 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);
653
655
  }
654
656
  async function is_tree_dirty(directory) {
655
657
  try {
@@ -705,8 +707,9 @@ var run_hold = {
705
707
  UNNUMBERED_ISSUE,
706
708
  classify,
707
709
  create_halfrun_stop_hold,
710
+ create_stop_hold,
711
+ HALFRUN_STOP_FIELDS,
708
712
  create_hold,
709
- describe_holder,
710
713
  foreign_release_message,
711
714
  forced_release_message,
712
715
  held_message,
@@ -715,7 +718,6 @@ var run_hold = {
715
718
  is_own_hold,
716
719
  is_tree_dirty,
717
720
  own_release_command,
718
- parse_hold,
719
721
  race_message,
720
722
  read_hold,
721
723
  release_hold,
@@ -727,9 +729,6 @@ var run_hold = {
727
729
  write_hold
728
730
  };
729
731
 
730
- // scripts/run/run-cut.ts
731
- import { execFileSync } from "node:child_process";
732
-
733
732
  // scripts/josh/agent-session-environment.ts
734
733
  var SESSION_ID_KEY = "CLAUDE_CODE_SESSION_ID";
735
734
  var PARENT_SESSION_KEYS = [
@@ -774,13 +773,13 @@ function is_declared(value) {
774
773
  return value !== void 0 && value !== NO_HOST;
775
774
  }
776
775
  function loopback_proxy_keys(source, is_removed = is_loopback_proxy) {
777
- const declared = PROXY_KEYS.filter((key) => is_declared(source[key]));
778
- 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]));
779
778
  const is_certificate_included = matched.length > 0 && matched.length === declared.length;
780
779
  return is_certificate_included ? [...matched, PROXY_CERTIFICATE_KEY] : matched;
781
780
  }
782
781
  function to_removed(keys) {
783
- return Object.fromEntries(keys.map((key) => [key, void 0]));
782
+ return Object.fromEntries(keys.map((key2) => [key2, void 0]));
784
783
  }
785
784
  function removed_environment(source = process.env) {
786
785
  return to_removed([...PARENT_SESSION_KEYS, ...loopback_proxy_keys(source)]);
@@ -883,27 +882,27 @@ function trimmed(value) {
883
882
  return result === void 0 || result.length === EMPTY_LENGTH ? void 0 : result;
884
883
  }
885
884
  function override_value(role, field, environment2) {
886
- const key = ENV_KEYS[role][field];
887
- const value = trimmed(environment2[key]);
888
- if (value !== void 0) return { key, value };
889
- if (role !== WORKER) return { key, value: void 0 };
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 };
890
889
  const legacy = LEGACY_WORKER_KEYS[field];
891
890
  const legacy_value = trimmed(environment2[legacy]);
892
- return legacy_value === void 0 ? { key, value: void 0 } : { key: legacy, value: legacy_value };
891
+ return legacy_value === void 0 ? { key: key2, value: void 0 } : { key: legacy, value: legacy_value };
893
892
  }
894
893
  function model_override(role, environment2, provider) {
895
- const key = ENV_KEYS[role].model;
896
- return provider === OPENAI_PROVIDER ? { key, value: void 0 } : override_value(role, "model", environment2);
894
+ const key2 = ENV_KEYS[role].model;
895
+ return provider === OPENAI_PROVIDER ? { key: key2, value: void 0 } : override_value(role, "model", environment2);
897
896
  }
898
- function rejection(key, value, expected) {
899
- 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` };
900
899
  }
901
900
  function resolved_effort(override, fallback) {
902
901
  const parsed = EFFORT_SCHEMA.safeParse(override.value ?? fallback);
903
902
  return parsed.success ? { kind: "effort", effort: parsed.data } : rejection(override.key, override.value ?? "", "is not an allowed effort");
904
903
  }
905
904
  function has_session(environment2, keys) {
906
- return keys.some((key) => trimmed(environment2[key]) !== void 0);
905
+ return keys.some((key2) => trimmed(environment2[key2]) !== void 0);
907
906
  }
908
907
  function session_rejection(is_conflicting) {
909
908
  const note = is_conflicting ? "both Codex and Claude Code sessions were detected" : "no Codex or Claude Code session was detected";
@@ -976,17 +975,11 @@ function parse(value) {
976
975
  }
977
976
  var agent_role_profile = {
978
977
  DEFAULT_PROFILES,
979
- EFFORT_SCHEMA,
980
- ENV_KEYS,
981
978
  HANDED_PROVIDER_KEY,
982
979
  IMPLEMENTATION_PHASE,
983
- LEGACY_WORKER_KEYS,
984
980
  MAX_VALUE_LENGTH,
985
981
  OPENAI_PROFILES,
986
- PHASE_EFFORT,
987
- PHASE_SCHEMA,
988
982
  PRE_GATE_PHASE,
989
- PROVIDER_SCHEMA,
990
983
  PROFILE_SCHEMA,
991
984
  REVIEWER,
992
985
  SCHEDULER,
@@ -996,7 +989,6 @@ var agent_role_profile = {
996
989
  has_completion_callback,
997
990
  is_safe_value,
998
991
  parse,
999
- phase_effort,
1000
992
  resolve,
1001
993
  resolve_provider,
1002
994
  with_phase_effort
@@ -1103,7 +1095,6 @@ function decide2(input) {
1103
1095
  }
1104
1096
  var backlog_budget = {
1105
1097
  ANSWERS,
1106
- ANSWER_STOP_REASONS,
1107
1098
  BLOCKED_REASON,
1108
1099
  DEFAULT_IDLE_MINUTES,
1109
1100
  DEFAULT_IDLE_MS,
@@ -1118,7 +1109,6 @@ var backlog_budget = {
1118
1109
  UNREADABLE_REASON,
1119
1110
  WATCH_VERDICT,
1120
1111
  WHOLE_RUN_BUDGET_HOURS,
1121
- WHOLE_RUN_BUDGET_MINUTES,
1122
1112
  WHOLE_RUN_BUDGET_MS,
1123
1113
  WHOLE_RUN_REASON,
1124
1114
  decide: decide2,
@@ -1204,7 +1194,6 @@ var cost_pricing = {
1204
1194
  CACHE_WRITE_5M_MULTIPLIER,
1205
1195
  CACHE_WRITE_1H_MULTIPLIER,
1206
1196
  CACHE_READ_MULTIPLIER,
1207
- MODEL_PRICES,
1208
1197
  resolve_price,
1209
1198
  estimate_cost,
1210
1199
  estimate_composition,
@@ -1243,6 +1232,9 @@ var context_cut_payback = {
1243
1232
  var CONTEXT_CUT_THRESHOLD = context_cut_payback.break_even_context();
1244
1233
  var RECENT_REQUEST_WINDOW = 10;
1245
1234
 
1235
+ // scripts/git/git-common-directory.ts
1236
+ import { execFileSync } from "node:child_process";
1237
+
1246
1238
  // scripts/git/git-location-environment.ts
1247
1239
  var GIT_LOCATION_VARIABLES = [
1248
1240
  "GIT_ALTERNATE_OBJECT_DIRECTORIES",
@@ -1272,6 +1264,197 @@ function clear_git_location_variables() {
1272
1264
  }
1273
1265
  var git_location_environment = { clear_git_location_variables, location_free_environment };
1274
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
+
1275
1458
  // scripts/lane/lane-child-invocation.ts
1276
1459
  var CHILD_INVOCATION = "fullrun";
1277
1460
  var RESUME_GUIDE = ".claude/skills/workflow-commands/pre-gate-cut.md";
@@ -1292,12 +1475,12 @@ function outage_resume_invocation(issue) {
1292
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.`;
1293
1476
  return `${preamble} ${child_invocation(issue)}`;
1294
1477
  }
1295
- function ship_stop_invocation(issue) {
1296
- 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.`;
1297
- 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)}`;
1298
1482
  }
1299
1483
  var lane_child_invocation = {
1300
- CHILD_INVOCATION,
1301
1484
  child_invocation,
1302
1485
  outage_resume_invocation,
1303
1486
  process_pattern,
@@ -1306,16 +1489,16 @@ var lane_child_invocation = {
1306
1489
  };
1307
1490
 
1308
1491
  // scripts/run/run-cut.ts
1309
- import { z as z4 } from "zod";
1492
+ import { z as z5 } from "zod";
1310
1493
 
1311
1494
  // scripts/run/run-cut-handoff.ts
1312
1495
  import { readFileSync } from "node:fs";
1313
- import { z as z3 } from "zod";
1314
- var handoff_schema = z3.object({
1315
- instruction: z3.string(),
1316
- completed: z3.array(z3.string()),
1317
- remaining: z3.array(z3.string()),
1318
- 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())
1319
1502
  });
1320
1503
  var COMPLETED_LABEL = "Completed";
1321
1504
  var REMAINING_LABEL = "Remaining";
@@ -1384,22 +1567,20 @@ var { PRE_GATE_PHASE: PRE_GATE_PHASE2, IMPLEMENTATION_PHASE: IMPLEMENTATION_PHAS
1384
1567
  var IMPLEMENTATION_CONTEXT_THRESHOLD = CONTEXT_CUT_THRESHOLD;
1385
1568
  var CUT_MAX_AGE_MS = backlog_budget.WHOLE_RUN_BUDGET_MS;
1386
1569
  var CUT_MAX_AGE_HOURS = backlog_budget.WHOLE_RUN_BUDGET_HOURS;
1387
- var GIT_READ_TIMEOUT_MS = 5e3;
1388
- var WORKTREE_DIRECTORY_INDEX = 0;
1389
1570
  var END_COMMAND = "pnpm josh run:cut --end";
1390
1571
  var READ_COMMAND = "pnpm josh run:cut --json";
1391
1572
  var MAX_HANDOFF_BYTES = 8192;
1392
1573
  var NONE_READ = { kind: "none" };
1393
1574
  var UNREADABLE_READ2 = { kind: "unreadable" };
1394
- var run_cut_schema = z4.object({
1395
- invocation: z4.string(),
1396
- issue: z4.string(),
1397
- branch: z4.string(),
1398
- phase: z4.string(),
1399
- cut_at: z4.string(),
1400
- 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(),
1401
1582
  handoff: run_cut_handoff.handoff_schema.optional(),
1402
- is_merge_relaunched: z4.boolean().optional()
1583
+ is_merge_relaunched: z5.boolean().optional()
1403
1584
  });
1404
1585
  function cut_path(git_directory) {
1405
1586
  return stamp_file.stamp_path(CUT_PREFIX, git_directory);
@@ -1430,36 +1611,8 @@ function classify2(raw, now) {
1430
1611
  function read_cut(target, now = /* @__PURE__ */ new Date()) {
1431
1612
  return classify2(stamp_file.read_stamp_text(target), now);
1432
1613
  }
1433
- function git_directories_sync(cwd) {
1434
- try {
1435
- const output = execFileSync(
1436
- git_utilities.get_git_command_for_spawn(),
1437
- [...git_command.GIT_DIRECTORY_ARGUMENTS],
1438
- // git's own diagnostics are discarded: a checkout it cannot describe is already the
1439
- // `undefined` below, and a hook that let `fatal: not a git repository` through would print it
1440
- // in front of a tool call that is about to be allowed.
1441
- // A `PreToolUse` hook holds the tool call while it runs, so the read is bounded rather than
1442
- // left to whatever git does.
1443
- // `cwd` is set only by `lane_cut_sync`, which reads another lane's record from the parent.
1444
- // With a `cwd`, the git location variables are cleared so it is what git answers for: a hook
1445
- // exports `GIT_DIR`, which beats `cwd` and would name the hook's checkout instead
1446
- // (joshuafolkken/kit#2515). Without one the environment is kept, so this read resolves the same
1447
- // checkout as the asynchronous `git_directories` that writes the record.
1448
- {
1449
- cwd,
1450
- env: cwd === void 0 ? process.env : { ...process.env, ...git_location_environment.location_free_environment() },
1451
- encoding: "utf8",
1452
- stdio: ["ignore", "pipe", "ignore"],
1453
- timeout: GIT_READ_TIMEOUT_MS
1454
- }
1455
- );
1456
- return output.split("\n").filter((line) => line !== "");
1457
- } catch {
1458
- return [];
1459
- }
1460
- }
1461
1614
  function worktree_git_directory_sync() {
1462
- return git_directories_sync()[WORKTREE_DIRECTORY_INDEX];
1615
+ return git_common_directory.own();
1463
1616
  }
1464
1617
  function carried_cut_sync(now = /* @__PURE__ */ new Date(), directory = worktree_git_directory_sync()) {
1465
1618
  if (directory === void 0) return void 0;
@@ -1467,7 +1620,7 @@ function carried_cut_sync(now = /* @__PURE__ */ new Date(), directory = worktree
1467
1620
  return read2.kind === "carried" ? read2.cut : void 0;
1468
1621
  }
1469
1622
  function lane_cut_sync(lane_directory) {
1470
- const directory = git_directories_sync(lane_directory)[WORKTREE_DIRECTORY_INDEX];
1623
+ const directory = git_common_directory.own(lane_directory);
1471
1624
  if (directory === void 0) return void 0;
1472
1625
  const cut = carried_cut_sync(/* @__PURE__ */ new Date(), directory);
1473
1626
  return cut === void 0 ? void 0 : { target: cut_path(directory), cut };
@@ -1658,7 +1811,7 @@ var run_tail = {
1658
1811
 
1659
1812
  // scripts/rules/lane-background.ts
1660
1813
  var LONG_RUNNING_COMMANDS = new Set(
1661
- ["gate", "git", "followup"].map((name) => `${time_shell.JOSH_PREFIX}${name}`)
1814
+ ["gate", "git", "followup", "ship"].map((name) => `${time_shell.JOSH_PREFIX}${name}`)
1662
1815
  );
1663
1816
  var BACKGROUND_KEY2 = "run_in_background";
1664
1817
  function is_backgrounded2(input) {
@@ -1679,8 +1832,8 @@ function unfinished_ids(tail, launched_ids) {
1679
1832
  const lines = tail.split("\n").map((line) => time_transcript_line.parse_line(line));
1680
1833
  const finished = new Set(lines.map((line) => line?.finished_background ?? ""));
1681
1834
  const blocks = lines.flatMap((line) => line?.blocks ?? []);
1682
- const launched = blocks.flatMap((block) => launched_ids(block));
1683
- 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));
1684
1837
  }
1685
1838
  function pending_background_ids(tail) {
1686
1839
  return unfinished_ids(tail, (block) => [block.background_id, block.agent_id]);
@@ -1688,7 +1841,7 @@ function pending_background_ids(tail) {
1688
1841
  function pending_agent_ids(tail) {
1689
1842
  return unfinished_ids(tail, (block) => [block.agent_id]);
1690
1843
  }
1691
- 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.';
1692
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.";
1693
1846
  var ROW2 = {
1694
1847
  id: "lane-background",
@@ -1705,1234 +1858,1960 @@ var lane_background = {
1705
1858
  pending_background_ids
1706
1859
  };
1707
1860
 
1708
- // scripts/git/issue-labels.ts
1709
- var EPIC_LABEL = "epic";
1710
- var ENHANCEMENT_LABEL = "enhancement";
1711
- var BREAKING_CHANGE_LABEL = "breaking-change";
1712
- var IN_PROGRESS_LABEL = "in-progress";
1713
- var NEEDS_DECISION_LABEL = "needs-decision";
1714
- var RUN_SOLO_LABEL = "run:solo";
1715
- var RUN_LANE_LABEL = "run:lane";
1716
- var PRIORITY_HIGH_LABEL = "priority:high";
1717
- var NEEDS_HUMAN_REVIEW_LABEL = "needs-human-review";
1718
- var ALREADY_DONE_LABEL = "already-done";
1719
- var INTERRUPT_ROUTE_LABEL = "route:interrupt";
1720
- var REVIEW_CAP_ROUTE_LABEL = "route:review-cap";
1721
- var SPLIT_ROUTE_LABEL = "route:split";
1722
- var TIER_A_ROUTE_LABEL = "route:tier-a";
1723
- var FILING_ROUTE_LABELS = [
1724
- {
1725
- name: INTERRUPT_ROUTE_LABEL,
1726
- // Not `5319e7`, which is the `epic` label's purple: in a listing an interrupt would render as
1727
- // an epic, and the two are read at a glance rather than by name.
1728
- color: "1d76db",
1729
- // Keyed to the tests rather than to severity. "A serious defect" is the self-assessment
1730
- // `wip-cap.md` bans as a criterion, and this string is what a person reads in `gh label list`.
1731
- description: "Filed past the WIP cap \u2014 meets one of the three interrupt tests (wip-cap.md)"
1732
- },
1733
- {
1734
- name: REVIEW_CAP_ROUTE_LABEL,
1735
- color: "eab308",
1736
- description: "Filed by the review round cap's carry-forward (prompts/review.md \u2192 Review round cap)"
1737
- },
1738
- {
1739
- name: SPLIT_ROUTE_LABEL,
1740
- color: "0e8a16",
1741
- description: "A child issue created by a split (split-assessment.md)"
1742
- },
1743
- {
1744
- name: TIER_A_ROUTE_LABEL,
1745
- color: "d93f0b",
1746
- description: "Filed Tier A during implementation \u2014 an upstream defect or a prerequisite"
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;
1747
1892
  }
1748
- ];
1749
- var DEPTH_0_LABEL = "depth:0";
1750
- var DEPTH_1_LABEL = "depth:1";
1751
- var DEPTH_2_LABEL = "depth:2";
1752
- var DEPTH_LABELS = [
1753
- {
1754
- name: DEPTH_0_LABEL,
1755
- color: "0e8a16",
1756
- description: "Depth 0 \u2014 what a consumer of this package touches (SKILL.md \xA72i)"
1757
- },
1758
- {
1759
- name: DEPTH_1_LABEL,
1760
- color: "fbc02d",
1761
- description: "Depth 1 \u2014 the run orchestration that executes an Issue (SKILL.md \xA72i)"
1762
- },
1763
- {
1764
- name: DEPTH_2_LABEL,
1765
- color: "c5def5",
1766
- description: "Depth 2 \u2014 what measures a run (SKILL.md \xA72i)"
1893
+ }
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;
1767
1902
  }
1768
- ];
1769
- var WORKFLOW_LABELS = [
1770
- {
1771
- name: EPIC_LABEL,
1772
- color: "5319e7",
1773
- description: "Tracks a batch of child issues from one split"
1774
- },
1775
- { name: IN_PROGRESS_LABEL, color: "0075ca", description: "Work is actively in progress" },
1776
- {
1777
- name: RUN_SOLO_LABEL,
1778
- color: "b60205",
1779
- description: "Runs alone in a backlogrun: nothing starts beside it"
1780
- },
1781
- {
1782
- name: RUN_LANE_LABEL,
1783
- color: "0e8a16",
1784
- description: "Judged safe to run beside others in a backlogrun"
1785
- },
1786
- {
1787
- name: PRIORITY_HIGH_LABEL,
1788
- color: "e99695",
1789
- description: "Offered first in a backlogrun; only a person removes it"
1790
- },
1791
- ...FILING_ROUTE_LABELS,
1792
- ...DEPTH_LABELS
1793
- ];
1794
- var BUGFIX_LABEL = "bugfix";
1795
- var OTHER_CHANGE_LABEL = "other-change";
1796
- var IGNORE_FOR_RELEASE_LABEL = "ignore-for-release";
1797
- var RELEASE_CLASSIFICATION_NAMES = [
1798
- BREAKING_CHANGE_LABEL,
1799
- ENHANCEMENT_LABEL,
1800
- BUGFIX_LABEL,
1801
- OTHER_CHANGE_LABEL,
1802
- IGNORE_FOR_RELEASE_LABEL
1803
- ];
1804
- var RELEASE_CLASSIFICATION_METADATA = {
1805
- [BREAKING_CHANGE_LABEL]: { color: "d93f0b", description: "Release: a breaking change (major)" },
1806
- [ENHANCEMENT_LABEL]: { color: "84b6eb", description: "Release: a new feature (minor)" },
1807
- [BUGFIX_LABEL]: { color: "d73a4a", description: "Release: a bug fix (patch)" },
1808
- [OTHER_CHANGE_LABEL]: { color: "c2e0c6", description: "Release: any other change (patch)" },
1809
- [IGNORE_FOR_RELEASE_LABEL]: { color: "cfd3d7", description: "Release: left out of the notes" }
1810
- };
1811
- var RELEASE_CLASSIFICATION_LABELS = RELEASE_CLASSIFICATION_NAMES.map((name) => ({ name, ...RELEASE_CLASSIFICATION_METADATA[name] }));
1812
- var REPOSITORY_LABELS = [
1813
- ...WORKFLOW_LABELS,
1814
- ...RELEASE_CLASSIFICATION_LABELS
1815
- ];
1816
- function has_any_label(labels, wanted) {
1817
- return (labels ?? []).some((label2) => wanted.has(label2.name.toLowerCase()));
1818
1903
  }
1819
- function label_name_of(labels, wanted) {
1820
- const target = wanted.toLowerCase();
1821
- return labels.find((label2) => label2.toLowerCase() === target);
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
+ }
1822
1920
  }
1823
- function has_label_name(labels, wanted) {
1824
- return label_name_of(labels, wanted) !== void 0;
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
+ }
1825
1928
  }
1826
-
1827
- // scripts/rules/lane-park.ts
1828
- var NOTIFY_COMMANDS = /* @__PURE__ */ new Set(["notify"]);
1829
- var CONFIRMATION_TASK_TYPE = /(?:^|\s)--task-type[=\s]+["']?confirmation\b/u;
1830
- function is_confirmation_notify(command) {
1831
- return shell_segments.segments_of(command).some(
1832
- (segment) => shell_segments.is_josh_command(segment, NOTIFY_COMMANDS) && CONFIRMATION_TASK_TYPE.test(segment)
1833
- );
1834
- }
1835
- var NOTIFY_INVOCATION = /josh\s+(?:notify|nf)\b/u;
1836
- function mentions_confirmation_notify(text) {
1837
- return NOTIFY_INVOCATION.test(text) && CONFIRMATION_TASK_TYPE.test(text);
1838
- }
1839
- function is_unparked_stop(command, is_lane_child = lane_child_marker.is_child_of(process.cwd())) {
1840
- if (!is_confirmation_notify(command)) return false;
1841
- return is_lane_child;
1842
- }
1843
- var APPLIES_NEEDS_DECISION = new RegExp(
1844
- String.raw`(?:labels\[\]=|--add-label[= ]'?)${NEEDS_DECISION_LABEL}\b`,
1845
- "u"
1846
- );
1847
- function records_the_park(command) {
1848
- 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);
1849
1931
  }
1850
- 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".';
1851
- var lane_park = {
1852
- LANE_PARK_REASON,
1853
- is_confirmation_notify,
1854
- is_unparked_stop,
1855
- mentions_confirmation_notify,
1856
- records_the_park
1857
- };
1858
-
1859
- // scripts/discovery/repo-discovery.ts
1860
- import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
1861
- import path from "node:path";
1862
-
1863
- // scripts/discovery/repo-origin.ts
1864
- var GITHUB_HOST = "github.com";
1865
- var SSH_ALIAS_PATTERN = /^github(?:-\w+)*$/u;
1866
- var GIT_SUFFIX = ".git";
1867
- var PATH_SEPARATOR2 = "/";
1868
- var USER_SEPARATOR = "@";
1869
- var HOST_SEPARATOR = ":";
1870
- var OWNER_INDEX = 0;
1871
- var REPO_INDEX = 1;
1872
- var REPO_PATH_SEGMENTS = 2;
1873
- var NOT_FOUND = -1;
1874
- var SECTION_PREFIX = "[";
1875
- var ORIGIN_SECTION = /^\[remote\s+"origin"\]$/u;
1876
- var URL_KEY = "url";
1877
- function is_github_hostname(host) {
1878
- 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 });
1879
1938
  }
1880
- function is_github_ssh_host(host) {
1881
- const normalized = host.toLowerCase();
1882
- return is_github_hostname(normalized) || SSH_ALIAS_PATTERN.test(normalized);
1939
+ function close_beacons() {
1940
+ for (const token of beacons.keys()) close_beacon(token);
1883
1941
  }
1884
- function strip_git_suffix(name) {
1885
- 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);
1886
1946
  }
1887
- function to_identity(repository_path) {
1888
- const segments = repository_path.split(PATH_SEPARATOR2).filter((segment) => segment !== "");
1889
- if (segments.length !== REPO_PATH_SEGMENTS) return void 0;
1890
- const owner = segments[OWNER_INDEX] ?? "";
1891
- const repo = strip_git_suffix(segments[REPO_INDEX] ?? "");
1892
- return repo === "" ? void 0 : { owner, repo };
1947
+ function ignore_beacon_error() {
1893
1948
  }
1894
- function parse_scp_like(url) {
1895
- const user_end = url.indexOf(USER_SEPARATOR);
1896
- const authority = user_end === NOT_FOUND ? url : url.slice(user_end + 1);
1897
- const host_end = authority.indexOf(HOST_SEPARATOR);
1898
- if (host_end === NOT_FOUND) return void 0;
1899
- if (!is_github_ssh_host(authority.slice(0, host_end))) return void 0;
1900
- return to_identity(authority.slice(host_end + 1));
1949
+ function bind_beacon(target) {
1950
+ return createServer().on("error", ignore_beacon_error).listen(target);
1901
1951
  }
1902
- function parse_url_like(url) {
1952
+ function try_bind_beacon(bind, target) {
1903
1953
  try {
1904
- const parsed = new URL(url);
1905
- return is_github_hostname(parsed.hostname) ? to_identity(parsed.pathname) : void 0;
1954
+ const server = bind(target);
1955
+ return server.listening ? server : void 0;
1906
1956
  } catch {
1907
1957
  return void 0;
1908
1958
  }
1909
1959
  }
1910
- function parse_origin_url(url) {
1911
- const trimmed2 = url.trim();
1912
- if (trimmed2 === "") return void 0;
1913
- 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;
1914
1969
  }
1915
- function is_section_header(line) {
1916
- 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();
1917
1974
  }
1918
- function origin_section_lines(lines) {
1919
- const start = lines.findIndex((line) => ORIGIN_SECTION.test(line));
1920
- if (start === NOT_FOUND) return [];
1921
- const rest = lines.slice(start + 1);
1922
- const end = rest.findIndex((line) => is_section_header(line));
1923
- 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);
1924
1979
  }
1925
- function entry_value(line, key) {
1926
- const separator = line.indexOf("=");
1927
- if (separator === NOT_FOUND) return void 0;
1928
- if (line.slice(0, separator).trim() !== key) return void 0;
1929
- 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;
1930
1985
  }
1931
- function parse_origin_from_config(content) {
1932
- const lines = content.split("\n").map((line) => line.trim());
1933
- 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;
1934
1991
  }
1935
- function format_identity(identity) {
1936
- 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;
1937
1995
  }
1938
- var repo_origin = {
1939
- GITHUB_HOST,
1940
- is_github_hostname,
1941
- is_github_ssh_host,
1942
- parse_origin_from_config,
1943
- parse_origin_url,
1944
- format_identity
1945
- };
1946
-
1947
- // scripts/discovery/repo-map-logic.ts
1948
- var OVERRIDE_ENTRY_SEPARATOR = ",";
1949
- var OVERRIDE_PAIR_SEPARATOR = "=";
1950
- var OVERRIDE_PAIR_PARTS = 2;
1951
- var KEY_INDEX = 0;
1952
- var PATH_INDEX = 1;
1953
- function is_same_owner(identity, current_owner2) {
1954
- 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);
1955
1999
  }
1956
- function add_entry(target, origin_url, repository_path, current_owner2) {
1957
- if (origin_url === void 0) return;
1958
- const identity = repo_origin.parse_origin_url(origin_url);
1959
- if (identity === void 0 || !is_same_owner(identity, current_owner2)) return;
1960
- 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);
1961
2004
  }
1962
- function parse_override_entry(entry) {
1963
- const parts = entry.split(OVERRIDE_PAIR_SEPARATOR).map((part) => part.trim());
1964
- if (parts.length !== OVERRIDE_PAIR_PARTS) return void 0;
1965
- const key = parts[KEY_INDEX] ?? "";
1966
- const repository_path = parts[PATH_INDEX] ?? "";
1967
- if (repository_path === "") return void 0;
1968
- 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 } };
1969
2008
  }
1970
- function parse_overrides(raw) {
1971
- if (raw === void 0) return [];
1972
- 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);
1973
2013
  }
1974
- function build_repository_map(discovered, current_owner2, override_value2) {
1975
- const result = /* @__PURE__ */ new Map();
1976
- for (const entry of discovered) add_entry(result, entry.origin_url, entry.path, current_owner2);
1977
- for (const entry of parse_overrides(override_value2)) {
1978
- add_entry(result, entry.origin_url, entry.path, current_owner2);
1979
- }
1980
- return result;
2014
+ function is_own_process(pid, recorded_start) {
2015
+ return pid === process.pid && recorded_start === own_start();
1981
2016
  }
1982
- var repo_map_logic = {
1983
- is_same_owner,
1984
- parse_overrides,
1985
- 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
1986
2027
  };
1987
2028
 
1988
- // scripts/discovery/repo-discovery.ts
1989
- var GIT_DIRECTORY = ".git";
1990
- var GIT_CONFIG = "config";
1991
- var GITDIR_PREFIX = "gitdir:";
1992
- var COMMON_DIRECTORY_FILE = "commondir";
1993
- var OVERRIDE_ENV_KEY = "JOSH_REPO_PATHS";
1994
- function resolve_common_directory(git_directory) {
1995
- const pointer = path.join(git_directory, COMMON_DIRECTORY_FILE);
1996
- if (!existsSync(pointer)) return git_directory;
1997
- try {
1998
- return path.resolve(git_directory, readFileSync2(pointer, "utf8").trim());
1999
- } catch {
2000
- return git_directory;
2001
- }
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;
2002
2037
  }
2003
- function follow_git_file(repository_path, git_file) {
2004
- const pointer = readFileSync2(git_file, "utf8").trim();
2005
- if (!pointer.startsWith(GITDIR_PREFIX)) return void 0;
2006
- 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);
2007
2041
  }
2008
- function resolve_git_directory(repository_path) {
2009
- const candidate = path.join(repository_path, GIT_DIRECTORY);
2010
- if (!existsSync(candidate)) return void 0;
2011
- try {
2012
- if (statSync(candidate).isDirectory()) return candidate;
2013
- const linked = follow_git_file(repository_path, candidate);
2014
- return linked === void 0 ? void 0 : resolve_common_directory(linked);
2015
- } catch {
2016
- return void 0;
2017
- }
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;
2018
2047
  }
2019
- function read_origin_url(repository_path) {
2020
- const git_directory = resolve_git_directory(repository_path);
2021
- if (git_directory === void 0) return void 0;
2022
- try {
2023
- return repo_origin.parse_origin_from_config(
2024
- readFileSync2(path.join(git_directory, GIT_CONFIG), "utf8")
2025
- );
2026
- } catch {
2027
- return void 0;
2028
- }
2048
+ function folded_outage(carry) {
2049
+ return { outages: carry.outages, last_outage_at: carry.last_outage_at };
2029
2050
  }
2030
- function sibling_directories(parent) {
2031
- try {
2032
- return readdirSync(parent, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path.join(parent, entry.name)).toSorted((left, right) => left.localeCompare(right));
2033
- } catch {
2034
- return [];
2051
+ function counted_outage(carry, change, now) {
2052
+ const outages = carry.outages + (change.outages ?? NO_INCREMENT);
2053
+ return { outages, last_outage_at: now.toISOString() };
2054
+ }
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);
2035
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);
2036
2061
  }
2037
- function scan_siblings(parent) {
2038
- return sibling_directories(parent).map((repository_path) => ({
2039
- path: repository_path,
2040
- origin_url: read_origin_url(repository_path)
2041
- }));
2042
- }
2043
- function resolve_current_owner(repository_path) {
2044
- const origin_url = read_origin_url(repository_path);
2045
- if (origin_url === void 0) return void 0;
2046
- 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
+ };
2047
2069
  }
2048
- function main_worktree(repository_path) {
2049
- const git_directory = resolve_git_directory(repository_path);
2050
- if (git_directory === void 0) return repository_path;
2051
- if (path.basename(git_directory) !== GIT_DIRECTORY) return repository_path;
2052
- 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];
2053
2078
  }
2054
- function discover_repositories(repository_path, environment2 = process.env) {
2055
- const main2 = main_worktree(repository_path);
2056
- const current_owner2 = resolve_current_owner(main2);
2057
- if (current_owner2 === void 0) return /* @__PURE__ */ new Map();
2058
- return repo_map_logic.build_repository_map(
2059
- scan_siblings(path.dirname(main2)),
2060
- current_owner2,
2061
- environment2[OVERRIDE_ENV_KEY]
2062
- );
2079
+ function next_merged_issues(carry, issue) {
2080
+ if (issue === void 0) return carry.merged_issues;
2081
+ return [...carry.merged_issues ?? [], issue];
2063
2082
  }
2064
- var repo_discovery = {
2065
- OVERRIDE_ENV_KEY,
2066
- main_worktree,
2067
- read_origin_url,
2068
- resolve_current_owner,
2069
- scan_siblings,
2070
- discover_repositories
2071
- };
2072
-
2073
- // scripts/discovery/repo-party.ts
2074
- var FIRST_PARTY = "first-party";
2075
- var THIRD_PARTY = "third-party";
2076
- var UNKNOWN = "unknown";
2077
- function classify3(session_owner, target) {
2078
- if (session_owner === void 0 || target === void 0) return UNKNOWN;
2079
- 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;
2080
2086
  }
2081
- function target_owner(owner_repo) {
2082
- 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
+ };
2083
2095
  }
2084
- function current_owner(repository_path = process.cwd()) {
2085
- 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;
2086
2111
  }
2087
- var repo_party = { FIRST_PARTY, THIRD_PARTY, UNKNOWN, classify: classify3, current_owner, target_owner };
2112
+ var run_carry_change = { apply_change };
2088
2113
 
2089
- // scripts/lane/lane-reap.ts
2090
- import { execFileSync as execFileSync2 } from "node:child_process";
2091
- var PGREP_COMMAND = "pgrep";
2092
- var MATCH_FULL_COMMAND_LINE = "-f";
2093
- var CHILDREN_OF_FLAG = "-P";
2094
- var PS_COMMAND = "/bin/ps";
2095
- var PARENT_FORMAT = ["-o", "ppid=", "-p"];
2096
- var TERMINATE_SIGNAL = "SIGTERM";
2097
- var PROBE_TIMEOUT_MS = 2e3;
2098
- var LOWEST_CHILD_PID = 2;
2099
- function parse_pids(printed) {
2100
- return printed.split("\n").map((line) => Number(line.trim())).filter((pid) => Number.isSafeInteger(pid) && pid >= LOWEST_CHILD_PID);
2101
- }
2102
- function run_probe(command, args) {
2103
- try {
2104
- return execFileSync2(command, [...args], {
2105
- encoding: "utf8",
2106
- timeout: PROBE_TIMEOUT_MS,
2107
- stdio: ["ignore", "pipe", "ignore"]
2108
- });
2109
- } catch {
2110
- return "";
2111
- }
2114
+ // scripts/backlog/backlog-budget-cli.ts
2115
+ import { fileURLToPath } from "node:url";
2116
+
2117
+ // scripts/git/changed-paths.ts
2118
+ function to_paths(raw) {
2119
+ return raw.split("\n").map((line) => line.trim()).filter((line) => line !== "");
2112
2120
  }
2113
- function parent_of(pid) {
2114
- return parse_pids(run_probe(PS_COMMAND, [...PARENT_FORMAT, String(pid)]))[0];
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];
2115
2126
  }
2116
- function terminate(pid) {
2117
- try {
2118
- process.kill(pid, TERMINATE_SIGNAL);
2119
- return true;
2120
- } catch {
2121
- return false;
2122
- }
2127
+ var changed_paths = { read_changed_paths, to_paths };
2128
+
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));
2123
2137
  }
2124
- var SYSTEM_PROBES = {
2125
- matching: (pattern) => parse_pids(run_probe(PGREP_COMMAND, [MATCH_FULL_COMMAND_LINE, pattern])),
2126
- children_of: (pid) => parse_pids(run_probe(PGREP_COMMAND, [CHILDREN_OF_FLAG, String(pid)])),
2127
- parent_of,
2128
- terminate
2129
- };
2130
- function ancestry_of(pid, probes) {
2131
- const ancestry = /* @__PURE__ */ new Set([pid]);
2132
- let current = probes.parent_of(pid);
2133
- while (current !== void 0 && !ancestry.has(current)) {
2134
- ancestry.add(current);
2135
- current = probes.parent_of(current);
2136
- }
2137
- return ancestry;
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) };
2138
2141
  }
2139
- function own_ancestry(probes = SYSTEM_PROBES) {
2140
- return ancestry_of(process.pid, probes);
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`;
2141
2146
  }
2142
- function tree_of(roots, probes) {
2143
- const tree = [];
2144
- const pending = [...roots];
2145
- let next = pending.shift();
2146
- while (next !== void 0) {
2147
- if (!tree.includes(next)) {
2148
- tree.push(next);
2149
- pending.push(...probes.children_of(next));
2150
- }
2151
- next = pending.shift();
2147
+ function print_decision(key2, answer, reason, is_json) {
2148
+ if (is_json) {
2149
+ console.info(JSON.stringify({ [key2]: answer, reason }));
2150
+ return;
2152
2151
  }
2153
- return tree;
2152
+ console.info(answer);
2153
+ console.error(reason);
2154
2154
  }
2155
- function reap_child(issue, probes = SYSTEM_PROBES) {
2156
- const own = own_ancestry(probes);
2157
- const pattern = lane_child_invocation.process_pattern(issue);
2158
- const roots = probes.matching(pattern).filter((pid) => !own.has(pid));
2159
- const doomed = tree_of(roots, probes).filter((pid) => !own.has(pid));
2160
- return doomed.filter((pid) => probes.terminate(pid));
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;
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;
2161
2165
  }
2162
- var lane_reap = {
2163
- own_ancestry,
2164
- parse_pids,
2165
- reap_child
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
2166
2173
  };
2167
2174
 
2168
- // scripts/josh/process-identity.ts
2169
- import { execFileSync as execFileSync3, spawnSync } from "node:child_process";
2170
- import { randomUUID } from "node:crypto";
2171
- import { readFileSync as readFileSync3, rmSync } from "node:fs";
2172
- import { createServer } from "node:net";
2173
- import path2 from "node:path";
2174
- var PS_COMMAND2 = "/bin/ps";
2175
- var FORMAT_FLAG = "-o";
2176
- var START_TIME_FORMAT = "lstart=";
2177
- var PID_FLAG = "-p";
2178
- var PROBE_ENVIRONMENT = { TZ: "UTC", LC_ALL: "C" };
2179
- var PROBE_TIMEOUT_MS2 = 2e3;
2180
- var LIVENESS_SIGNAL = 0;
2181
- var LOWEST_REAL_PID = 1;
2182
- var PROC_START_FIELD_INDEX = 19;
2183
- var BEACON_SCHEME = "socket:";
2184
- var BEACON_SUFFIX = ".sock";
2185
- var BEACON_PREFIX = `josh-process-identity-${String(process.getuid?.() ?? "")}-`;
2186
- var BEACON_ID_LENGTH = 36;
2187
- var BEACON_ID_PATTERN = /^[\da-f-]+$/u;
2188
- var WINDOWS_PLATFORM = "win32";
2189
- 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));";
2190
- var beacons = /* @__PURE__ */ new Map();
2191
- var exit_cleanup = { is_registered: false };
2192
- function is_live_pid(pid) {
2193
- if (pid < LOWEST_REAL_PID) return false;
2194
- try {
2195
- process.kill(pid, LIVENESS_SIGNAL);
2196
- return true;
2197
- } catch {
2198
- return false;
2199
- }
2200
- }
2201
- function read_proc_start(pid) {
2202
- try {
2203
- const stat = readFileSync3(`/proc/${String(pid)}/stat`, "utf8");
2204
- const fields = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/u);
2205
- const start = fields[PROC_START_FIELD_INDEX];
2206
- return start === void 0 ? void 0 : `proc:${start}`;
2207
- } catch {
2208
- return void 0;
2209
- }
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}`;
2210
2183
  }
2211
- function read_ps_start(pid) {
2184
+ function parse_or_undefined(config) {
2212
2185
  try {
2213
- const printed = execFileSync3(
2214
- PS_COMMAND2,
2215
- [FORMAT_FLAG, START_TIME_FORMAT, PID_FLAG, String(pid)],
2216
- {
2217
- encoding: "utf8",
2218
- timeout: PROBE_TIMEOUT_MS2,
2219
- stdio: ["ignore", "pipe", "ignore"],
2220
- env: PROBE_ENVIRONMENT
2221
- }
2222
- ).trim();
2223
- return printed.length > 0 ? printed : void 0;
2186
+ return parseArgs(config);
2224
2187
  } catch {
2225
2188
  return void 0;
2226
2189
  }
2227
2190
  }
2228
- var START_PROBES = { read_proc_start, read_ps_start };
2229
- function safe_probe(probe, pid) {
2230
- try {
2231
- return probe(pid);
2232
- } catch {
2233
- return void 0;
2234
- }
2235
- }
2236
- function read_start(pid, probes = START_PROBES) {
2237
- return safe_probe(probes.read_proc_start, pid) ?? safe_probe(probes.read_ps_start, pid);
2238
- }
2239
- function close_beacon(token) {
2240
- const server = beacons.get(token);
2241
- if (server === void 0) return;
2242
- beacons.delete(token);
2243
- server.close();
2244
- rmSync(token.slice(BEACON_SCHEME.length), { force: true });
2245
- }
2246
- function close_beacons() {
2247
- for (const token of beacons.keys()) close_beacon(token);
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;
2248
2211
  }
2249
- function ensure_exit_cleanup() {
2250
- if (exit_cleanup.is_registered) return;
2251
- exit_cleanup.is_registered = true;
2252
- process.once("exit", close_beacons);
2212
+ function text_of(value) {
2213
+ return typeof value === "string" ? value : void 0;
2253
2214
  }
2254
- function ignore_beacon_error() {
2215
+ function to_answer(raw) {
2216
+ return backlog_budget.ANSWERS.find((answer) => answer === raw);
2255
2217
  }
2256
- function bind_beacon(target) {
2257
- return createServer().on("error", ignore_beacon_error).listen(target);
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;
2258
2222
  }
2259
- function try_bind_beacon(bind, target) {
2260
- try {
2261
- const server = bind(target);
2262
- return server.listening ? server : void 0;
2263
- } catch {
2264
- return void 0;
2265
- }
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;
2266
2228
  }
2267
- function open_beacon(bind = bind_beacon) {
2268
- const target = path2.join(PLATFORM_TEMP_ROOT, `${BEACON_PREFIX}${randomUUID()}${BEACON_SUFFIX}`);
2269
- const token = `${BEACON_SCHEME}${target}`;
2270
- const server = try_bind_beacon(bind, target);
2271
- if (server === void 0) return void 0;
2272
- server.unref();
2273
- beacons.set(token, server);
2274
- ensure_exit_cleanup();
2275
- return token;
2229
+ function is_readable(raw, parsed) {
2230
+ return raw === void 0 || parsed !== void 0;
2276
2231
  }
2277
- function resolve_own_start(probes = START_PROBES, platform2 = process.platform) {
2278
- const start = read_start(process.pid, probes);
2279
- if (start !== void 0 || platform2 === WINDOWS_PLATFORM) return start;
2280
- return open_beacon();
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
+ };
2281
2240
  }
2282
- function is_valid_beacon_name(name) {
2283
- if (!name.startsWith(BEACON_PREFIX) || !name.endsWith(BEACON_SUFFIX)) return false;
2284
- const id = name.slice(BEACON_PREFIX.length, -BEACON_SUFFIX.length);
2285
- return id.length === BEACON_ID_LENGTH && BEACON_ID_PATTERN.test(id);
2241
+ function are_counts_readable(values, counts) {
2242
+ return COUNT_NAMES.every((name) => is_readable(text_of(values[name]), counts[name]));
2286
2243
  }
2287
- function beacon_target(token) {
2288
- if (!token.startsWith(BEACON_SCHEME)) return void 0;
2289
- const target = token.slice(BEACON_SCHEME.length);
2290
- if (path2.dirname(target) !== PLATFORM_TEMP_ROOT) return void 0;
2291
- return is_valid_beacon_name(path2.basename(target)) ? target : void 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;
2292
2249
  }
2293
- function is_live_beacon(target) {
2294
- return spawnSync(process.execPath, ["-e", SOCKET_PROBE_SOURCE, target], {
2295
- stdio: "ignore",
2296
- timeout: PROBE_TIMEOUT_MS2
2297
- }).status === 0;
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;
2298
2253
  }
2299
- function has_matching_start(pid, recorded_start, read2) {
2300
- const observed = read2(pid);
2301
- return observed === void 0 ? void 0 : observed === recorded_start;
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));
2302
2256
  }
2303
- function matches_recorded_start(pid, recorded_start, read2) {
2304
- const target = beacon_target(recorded_start);
2305
- return target === void 0 ? has_matching_start(pid, recorded_start, read2) : is_live_beacon(target);
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
+ };
2306
2267
  }
2307
- var own_probe = /* @__PURE__ */ new Map();
2308
- function own_start() {
2309
- if (!own_probe.has(process.pid)) own_probe.set(process.pid, resolve_own_start());
2310
- return own_probe.get(process.pid);
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 });
2311
2275
  }
2312
- function own_fields() {
2313
- const start = own_start();
2314
- return { pid: process.pid, ...start !== void 0 && { process_start: start } };
2276
+ function refuse() {
2277
+ console.error(USAGE);
2278
+ return FAILURE_EXIT_CODE2;
2315
2279
  }
2316
- function is_same_process(pid, recorded_start, read2 = read_start) {
2317
- if (pid === void 0 || !is_live_pid(pid)) return false;
2318
- if (recorded_start === void 0) return void 0;
2319
- return matches_recorded_start(pid, recorded_start, read2);
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;
2320
2294
  }
2321
- function is_own_process(pid, recorded_start) {
2322
- return pid === process.pid && recorded_start === own_start();
2295
+ function main(argv) {
2296
+ process.exitCode = run(argv);
2323
2297
  }
2324
- var process_identity = {
2325
- close_beacon,
2326
- is_live_pid,
2327
- is_own_process,
2328
- is_same_process,
2329
- open_beacon,
2330
- own_fields,
2331
- own_start,
2332
- read_start,
2333
- resolve_own_start
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
2334
2310
  };
2311
+ if (process.argv[1] === fileURLToPath(import.meta.url)) main(process.argv.slice(ARGV_OFFSET));
2335
2312
 
2336
- // scripts/run/run-carry.ts
2337
- import { z as z5 } from "zod";
2338
-
2339
- // scripts/run/run-carry-streak.ts
2340
- var NO_INCREMENT = 0;
2341
- var OUTAGE_FOLD_WINDOW_MS = 12e4;
2342
- function has_increment(value) {
2343
- return (value ?? NO_INCREMENT) > NO_INCREMENT;
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);
2344
2328
  }
2345
- function next_failures(carry, change) {
2346
- if (has_increment(change.merged)) return NO_INCREMENT;
2347
- return carry.failures + (change.failures ?? NO_INCREMENT);
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)}`;
2348
2334
  }
2349
- function is_within_fold_window(last_outage_at, now) {
2350
- if (last_outage_at === void 0) return false;
2351
- const last = Date.parse(last_outage_at);
2352
- if (Number.isNaN(last)) return false;
2353
- return now.getTime() - last <= OUTAGE_FOLD_WINDOW_MS;
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 };
2354
2340
  }
2355
- function folded_outage(carry) {
2356
- return { outages: carry.outages, last_outage_at: carry.last_outage_at };
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;
2349
+ }
2350
+ return rendered;
2357
2351
  }
2358
- function counted_outage(carry, change, now) {
2359
- const outages = carry.outages + (change.outages ?? NO_INCREMENT);
2360
- return { outages, last_outage_at: now.toISOString() };
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;
2361
2358
  }
2362
- function next_outage(carry, change, now) {
2363
- if (has_increment(change.outages)) {
2364
- return is_within_fold_window(carry.last_outage_at, now) ? folded_outage(carry) : counted_outage(carry, change, now);
2365
- }
2366
- const is_reachable = has_increment(change.merged) || has_increment(change.failures);
2367
- return is_reachable ? { outages: NO_INCREMENT, last_outage_at: void 0 } : folded_outage(carry);
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);
2368
2362
  }
2369
- function next_state(carry, change, now) {
2370
- const outage = next_outage(carry, change, now);
2371
- return {
2372
- failures: next_failures(carry, change),
2373
- outages: outage.outages,
2374
- last_outage_at: outage.last_outage_at
2375
- };
2376
- }
2377
- var run_carry_streak = { OUTAGE_FOLD_WINDOW_MS, next_state };
2378
-
2379
- // scripts/run/run-carry-change.ts
2380
- var NO_INCREMENT2 = 0;
2381
- function next_done(carry, done) {
2382
- if (done === void 0) return carry.done;
2383
- const current = carry.done ?? [];
2384
- return current.includes(done) ? current : [...current, done];
2385
- }
2386
- function next_merged_issues(carry, issue) {
2387
- if (issue === void 0) return carry.merged_issues;
2388
- return [...carry.merged_issues ?? [], issue];
2389
- }
2390
- function is_duplicate_merge(carry, change) {
2391
- const issue = change.merged_issue;
2392
- return issue !== void 0 && carry.merged_issues?.includes(issue) === true;
2393
- }
2394
- function next_counts(carry, change) {
2395
- const cuts = change.cuts ?? NO_INCREMENT2;
2396
- return {
2397
- merged: carry.merged + (change.merged ?? NO_INCREMENT2),
2398
- filed: carry.filed + (change.filed ?? NO_INCREMENT2),
2399
- cuts: carry.cuts + cuts,
2400
- is_handed_off: cuts > NO_INCREMENT2
2401
- };
2402
- }
2403
- function apply_change(target, carry, change, now = /* @__PURE__ */ new Date()) {
2404
- if (is_duplicate_merge(carry, change)) return carry;
2405
- const streak = run_carry_streak.next_state(carry, change, now);
2406
- const next = {
2407
- ...carry,
2408
- ...next_counts(carry, change),
2409
- merged_issues: next_merged_issues(carry, change.merged_issue),
2410
- failures: streak.failures,
2411
- outages: streak.outages,
2412
- last_outage_at: streak.last_outage_at,
2413
- done: next_done(carry, change.done),
2414
- retrospective: change.retrospective === true || carry.retrospective
2415
- };
2416
- stamp_file.write_stamp(target, next);
2417
- return next;
2418
- }
2419
- var run_carry_change = { apply_change };
2420
-
2421
- // scripts/backlog/backlog-budget-cli.ts
2422
- import { fileURLToPath } from "node:url";
2423
-
2424
- // scripts/git/changed-paths.ts
2425
- function to_paths(raw) {
2426
- return raw.split("\n").map((line) => line.trim()).filter((line) => line !== "");
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);
2370
+ }
2371
+ return issues;
2427
2372
  }
2428
- async function read_changed_paths(is_staged) {
2429
- if (is_staged) return to_paths(await git_command.diff_cached_names());
2430
- const tracked = to_paths(await git_command.diff_main_names());
2431
- const untracked = to_paths(await git_command.untracked_names());
2432
- return [...tracked, ...untracked];
2373
+ function issue_token(issue) {
2374
+ return `${ISSUE_PREFIX}${String(issue)}`;
2433
2375
  }
2434
- var changed_paths = { read_changed_paths, to_paths };
2435
-
2436
- // scripts/josh/path-decision.ts
2437
- var STAGED_FLAG = "--staged";
2438
- var JSON_FLAG = "--json";
2439
- var KNOWN_FLAGS = [STAGED_FLAG, JSON_FLAG];
2440
- var FAILURE_EXIT_CODE = 1;
2441
- var MAX_LISTED_PATHS = 5;
2442
- function has_unknown_flag(argv, known) {
2443
- return argv.some((argument) => !known.includes(argument));
2376
+ function joined(command, rendered) {
2377
+ return [command, ...rendered].join(TOKEN_SEPARATOR);
2444
2378
  }
2445
- function parse_options(argv) {
2446
- if (has_unknown_flag(argv, KNOWN_FLAGS)) return void 0;
2447
- return { is_staged: argv.includes(STAGED_FLAG), is_json: argv.includes(JSON_FLAG) };
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]);
2448
2385
  }
2449
- function format_path_list(paths) {
2450
- const listed = paths.slice(0, MAX_LISTED_PATHS).join(", ");
2451
- if (paths.length <= MAX_LISTED_PATHS) return listed;
2452
- return `${listed}, +${String(paths.length - MAX_LISTED_PATHS)} more`;
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);
2453
2390
  }
2454
- function print_decision(key, answer, reason, is_json) {
2455
- if (is_json) {
2456
- console.info(JSON.stringify({ [key]: answer, reason }));
2457
- return;
2458
- }
2459
- console.info(answer);
2460
- console.error(reason);
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;
2461
2397
  }
2462
- async function run_path_decision(argv, command) {
2463
- const options = parse_options(argv);
2464
- if (options === void 0) {
2465
- console.error(command.usage);
2466
- return FAILURE_EXIT_CODE;
2467
- }
2468
- const paths = await changed_paths.read_changed_paths(options.is_staged);
2469
- const answer = command.decide(paths);
2470
- print_decision(command.key, answer, command.explain(paths, answer), options.is_json);
2471
- return 0;
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);
2472
2402
  }
2473
- var path_decision = {
2474
- format_path_list,
2475
- has_unknown_flag,
2476
- JSON_FLAG,
2477
- KNOWN_FLAGS,
2478
- MAX_LISTED_PATHS,
2479
- parse_options,
2480
- print_decision,
2481
- run_path_decision,
2482
- STAGED_FLAG
2403
+ var run_invocation = {
2404
+ has_only,
2405
+ issue_numbers,
2406
+ rebuild
2483
2407
  };
2484
2408
 
2485
- // scripts/lib/cli-flags.ts
2486
- import { parseArgs } from "node:util";
2487
- function refuse_unknown_flags(argv, known_flags, command) {
2488
- const unknown = argv.filter((argument) => !known_flags.includes(argument));
2489
- if (unknown.length === 0) return void 0;
2490
- const usage = `Usage: josh ${command} [${known_flags.join("] [")}]`;
2491
- return `Unknown argument(s): ${unknown.join(" ")}
2492
- ${usage}`;
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);
2493
2448
  }
2494
- function parse_or_undefined(config) {
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) {
2495
2454
  try {
2496
- return parseArgs(config);
2455
+ const parsed = run_carry_schema.safeParse(JSON.parse(raw));
2456
+ return parsed.success ? parsed.data : void 0;
2497
2457
  } catch {
2498
2458
  return void 0;
2499
2459
  }
2500
2460
  }
2501
- var cli_flags = { refuse_unknown_flags, parse_or_undefined };
2502
-
2503
- // scripts/backlog/backlog-budget-cli.ts
2504
- var ARGV_OFFSET = 2;
2505
- var SUCCESS_EXIT_CODE = 0;
2506
- var FAILURE_EXIT_CODE2 = 1;
2507
- var JSON_KEY = "budget";
2508
- 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]`;
2509
- var OPTIONS = {
2510
- active: { type: "string" },
2511
- answer: { type: "string" },
2512
- idle: { type: "string" },
2513
- json: { type: "boolean" },
2514
- max: { type: "string" },
2515
- merged: { type: "string" },
2516
- running: { type: "string" },
2517
- started: { type: "string" }
2518
- };
2519
- function read_arguments(argv) {
2520
- return cli_flags.parse_or_undefined({ args: [...argv], options: OPTIONS, strict: true })?.values;
2521
- }
2522
- function text_of(value) {
2523
- return typeof value === "string" ? value : void 0;
2524
- }
2525
- function to_answer(raw) {
2526
- return backlog_budget.ANSWERS.find((answer) => answer === raw);
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;
2527
2465
  }
2528
- function to_time_ms(raw) {
2529
- if (raw === void 0) return void 0;
2530
- const parsed = Date.parse(raw);
2531
- return Number.isNaN(parsed) ? void 0 : parsed;
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 };
2532
2471
  }
2533
- var COUNT_PATTERN = /^\d+$/u;
2534
- function to_count(raw) {
2535
- if (raw === void 0 || !COUNT_PATTERN.test(raw)) return void 0;
2536
- const parsed = Number(raw);
2537
- return Number.isSafeInteger(parsed) ? parsed : void 0;
2472
+ function read_carry(target, now = /* @__PURE__ */ new Date()) {
2473
+ return classify3(stamp_file.read_stamp_text(target), now);
2538
2474
  }
2539
- function is_readable(raw, parsed) {
2540
- return raw === void 0 || parsed !== void 0;
2475
+ function owner_of(pid, read2 = process_identity.read_start) {
2476
+ return { pid, start: read2(pid) };
2541
2477
  }
2542
- var COUNT_NAMES = ["idle", "max", "merged", "running"];
2543
- function counts_of(values) {
2478
+ function fresh_carry(invocation, owner, now) {
2544
2479
  return {
2545
- idle: to_count(text_of(values.idle)),
2546
- max: to_count(text_of(values.max)),
2547
- merged: to_count(text_of(values.merged)),
2548
- running: to_count(text_of(values.running))
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
2549
2489
  };
2550
2490
  }
2551
- function are_counts_readable(values, counts) {
2552
- return COUNT_NAMES.every((name) => is_readable(text_of(values[name]), counts[name]));
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;
2553
2494
  }
2554
- var NO_IDLE_MINUTES = 0;
2555
- function to_idle_ms(idle_minutes) {
2556
- if (idle_minutes === void 0) return backlog_budget.DEFAULT_IDLE_MS;
2557
- if (idle_minutes === NO_IDLE_MINUTES) return void 0;
2558
- return idle_minutes * backlog_budget.MS_PER_MINUTE;
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);
2559
2498
  }
2560
- function is_watch_readable(values) {
2561
- if (to_idle_ms(to_count(text_of(values.idle))) === void 0) return true;
2562
- return text_of(values.active) !== void 0;
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;
2563
2508
  }
2564
- function are_values_readable(values) {
2565
- 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));
2509
+ function is_owner_live(carry) {
2510
+ return process_identity.is_same_process(carry.owner_pid, carry.owner_start) !== false;
2566
2511
  }
2567
- function input_of(values, base) {
2568
- const counts = counts_of(values);
2512
+ function is_owned_by(carry, owner) {
2513
+ return carry.owner_pid === owner.pid && carry.owner_start === owner.start;
2514
+ }
2515
+ function is_foreign_live_owner(carry, owner) {
2516
+ return is_owner_live(carry) && !is_owned_by(carry, owner);
2517
+ }
2518
+ function is_handed_off_to(carry, invocation) {
2519
+ return carry.is_handed_off === true && carry.invocation === invocation;
2520
+ }
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";
2526
+ }
2527
+ function is_count_refused(carry, owner) {
2528
+ return carry.is_handed_off === true || is_foreign_live_owner(carry, owner);
2529
+ }
2530
+ function is_at_cut_cap(carry) {
2531
+ return carry.cuts >= MAX_CUTS;
2532
+ }
2533
+ function end_carry(target) {
2534
+ stamp_file.remove_stamp(target);
2535
+ }
2536
+ function retrospective_done_of(read2) {
2537
+ if (read2.kind !== "carried" && read2.kind !== "expired") return false;
2538
+ return read2.carry.retrospective === true;
2539
+ }
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));
2546
+ }
2547
+ function done_note(carry) {
2548
+ const done = carry.done ?? [];
2549
+ return done.length === 0 ? "" : `, issues ${done.join(", ")} done`;
2550
+ }
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)}`;
2553
+ }
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}\`.`;
2556
+ }
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.`;
2559
+ }
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.`;
2562
+ }
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.`;
2565
+ }
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.`;
2571
+ }
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) {
2569
2749
  return {
2570
- ...base,
2571
- active_at_ms: to_time_ms(text_of(values.active)) ?? base.started_at_ms,
2572
- merged: counts.merged ?? 0,
2573
- running: counts.running ?? 0,
2574
- max_issues: counts.max,
2575
- idle_budget_ms: to_idle_ms(counts.idle)
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 }
2576
2754
  };
2577
2755
  }
2578
- function build_input(values, now_ms) {
2579
- const answer = to_answer(text_of(values.answer));
2580
- if (answer === void 0) return void 0;
2581
- const started_at_ms = to_time_ms(text_of(values.started));
2582
- if (started_at_ms === void 0) return void 0;
2583
- if (!are_values_readable(values)) return void 0;
2584
- return input_of(values, { answer, now_ms, started_at_ms });
2585
- }
2586
- function refuse() {
2587
- console.error(USAGE);
2588
- return FAILURE_EXIT_CODE2;
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
+ }
2589
2763
  }
2590
- function run(argv, now_ms = Date.now()) {
2591
- const values = read_arguments(argv);
2592
- if (values === void 0) return refuse();
2593
- const input = build_input(values, now_ms);
2594
- if (input === void 0) return refuse();
2595
- const { verdict, reason } = backlog_budget.decide(input);
2596
- if (values.json === true) {
2597
- console.info(
2598
- JSON.stringify({ [JSON_KEY]: verdict, reason, is_finish: backlog_budget.is_finish(input) })
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)
2599
2771
  );
2600
- return SUCCESS_EXIT_CODE;
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);
2780
+ });
2781
+ }
2782
+ function ignore_spawn(_pid) {
2783
+ }
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);
2793
+ }
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);
2601
2804
  }
2602
- path_decision.print_decision(JSON_KEY, verdict, reason, false);
2603
- return SUCCESS_EXIT_CODE;
2604
2805
  }
2605
- function main(argv) {
2606
- process.exitCode = run(argv);
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
+ }
2607
2817
  }
2608
- var backlog_budget_cli = {
2609
- JSON_KEY,
2610
- OPTIONS,
2611
- SUCCESS_EXIT_CODE,
2612
- USAGE,
2613
- build_input,
2614
- main,
2615
- read_arguments,
2616
- run,
2617
- to_answer,
2618
- to_count,
2619
- to_time_ms
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
2620
2827
  };
2621
- if (process.argv[1] === fileURLToPath(import.meta.url)) main(process.argv.slice(ARGV_OFFSET));
2622
2828
 
2623
- // scripts/run/run-invocation.ts
2624
- var BACKLOG_COMMAND = "backlogrun";
2625
- var KNOWN_FLAGS2 = ["--max", "--idle"];
2626
- var ONLY_FLAG = "--only";
2627
- var ISSUE_PREFIX = "#";
2628
- var TOKEN_SEPARATOR = " ";
2629
- var FIRST_TOKEN = 0;
2630
- var FIRST_ARGUMENT_INDEX = 1;
2631
- var VALUE_OFFSET = 1;
2632
- var FLAG_PAIR_LENGTH = 2;
2633
- var ONLY_STEP = 1;
2634
- var EMPTY_LENGTH2 = 0;
2635
- var NOT_FOUND2 = -1;
2636
- function tokens_of(invocation) {
2637
- return invocation.split(TOKEN_SEPARATOR).filter((token) => token.length > EMPTY_LENGTH2);
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);
2638
2903
  }
2639
- function flag_token(token, raw) {
2640
- const flag = KNOWN_FLAGS2.find((known) => known === token);
2641
- const value = backlog_budget_cli.to_count(raw);
2642
- if (flag === void 0 || value === void 0) return void 0;
2643
- return `${flag} ${String(value)}`;
2904
+ function target_of(repository2) {
2905
+ return stamp_file.stamp_path(EVENT_PREFIX, repository2, EVENT_SUFFIX);
2644
2906
  }
2645
- function read_flag(tokens, index) {
2646
- const token = tokens[index] ?? "";
2647
- if (token === ONLY_FLAG) return { part: ONLY_FLAG, advance: ONLY_STEP };
2648
- const part = flag_token(token, tokens[index + VALUE_OFFSET]);
2649
- return part === void 0 ? void 0 : { part, advance: FLAG_PAIR_LENGTH };
2907
+ function is_present(event) {
2908
+ return event !== void 0;
2650
2909
  }
2651
- function flag_tokens(tokens, start) {
2652
- const rendered = [];
2653
- let index = start;
2654
- while (index < tokens.length) {
2655
- const read2 = read_flag(tokens, index);
2656
- if (read2 === void 0) return void 0;
2657
- rendered.push(read2.part);
2658
- index += read2.advance;
2910
+ function parse_line(line) {
2911
+ return event_schema.safeParse(json_value.parse_or_undefined(line)).data;
2912
+ }
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);
2917
+ }
2918
+ function last_position(events) {
2919
+ return events[events.length - POSITION_INCREMENT]?.pos ?? FIRST_POSITION;
2920
+ }
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);
2934
+ return {
2935
+ events: events.filter((event) => event.pos > position),
2936
+ next_position: last_position(events)
2937
+ };
2938
+ }
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;
2971
+ }
2972
+ function is_within(event, floor) {
2973
+ const at = moment_of(event.at);
2974
+ return at !== void 0 && at >= floor;
2975
+ }
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));
2981
+ }
2982
+ function scope_of(read2) {
2983
+ if (read2.kind === "carried" || read2.kind === "expired") {
2984
+ return { kind: SINCE_SCOPE, started_at: read2.carry.started_at };
2659
2985
  }
2660
- return rendered;
2986
+ return UNKNOWN_EVENT_SCOPE;
2661
2987
  }
2662
- function issue_of(token) {
2663
- if (!token.startsWith(ISSUE_PREFIX)) return void 0;
2664
- const digits = token.slice(ISSUE_PREFIX.length);
2665
- if (!run_issue_number.ISSUE_NUMBER_PATTERN.test(digits)) return void 0;
2666
- const issue = Number(digits);
2667
- return Number.isSafeInteger(issue) ? issue : void 0;
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
+ );
2668
2992
  }
2669
- function take_leading_prefixed(args) {
2670
- const stop = args.findIndex((token) => !token.startsWith(ISSUE_PREFIX));
2671
- return args.slice(FIRST_TOKEN, stop === NOT_FOUND2 ? args.length : stop);
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();
2672
3045
  }
2673
- function leading_issues(tokens) {
2674
- const issues = [];
2675
- const prefixed = take_leading_prefixed(tokens.slice(FIRST_ARGUMENT_INDEX));
2676
- for (const token of prefixed) {
2677
- const issue = issue_of(token);
2678
- if (issue === void 0) return void 0;
2679
- issues.push(issue);
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);
3049
+ }
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 {
2680
3056
  }
2681
- return issues;
2682
3057
  }
2683
- function issue_token(issue) {
2684
- return `${ISSUE_PREFIX}${String(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
+ }
2685
3066
  }
2686
- function joined(command, rendered) {
2687
- return [command, ...rendered].join(TOKEN_SEPARATOR);
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;
2688
3071
  }
2689
- function rebuilt_backlog(tokens) {
2690
- const issues = leading_issues(tokens);
2691
- if (issues === void 0) return void 0;
2692
- const flags = flag_tokens(tokens, FIRST_ARGUMENT_INDEX + issues.length);
2693
- if (flags === void 0) return void 0;
2694
- return joined(BACKLOG_COMMAND, [...issues.map((issue) => issue_token(issue)), ...flags]);
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
+ }
2695
3084
  }
2696
- function rebuild(invocation) {
2697
- const tokens = tokens_of(invocation);
2698
- if (tokens[FIRST_TOKEN] !== BACKLOG_COMMAND) return void 0;
2699
- return rebuilt_backlog(tokens);
3085
+ async function emit_heartbeat(line) {
3086
+ await emit(run_event_stream.EVENT_KIND.HEARTBEAT, line);
2700
3087
  }
2701
- function issue_numbers(invocation) {
2702
- const tokens = tokens_of(invocation);
2703
- if (tokens[FIRST_TOKEN] !== BACKLOG_COMMAND) return void 0;
2704
- const issues = leading_issues(tokens);
2705
- if (issues === void 0 || issues.length === EMPTY_LENGTH2) return void 0;
2706
- return issues;
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
3107
+ };
3108
+
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);
3143
+ }
3144
+ function log_path(repository2, number) {
3145
+ return stamp_file.stamp_path(`${LOG_PREFIX}${number}${KEY_SEPARATOR}`, repository2, LOG_SUFFIX);
3146
+ }
3147
+ function result_path(repository2, number) {
3148
+ return stamp_file.stamp_path(`${RESULT_PREFIX}${number}${KEY_SEPARATOR}`, repository2);
3149
+ }
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
+ }
3160
+ }
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
+ });
3170
+ }
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);
3184
+ }
3185
+ return process_identity.is_same_process(pid, record.process_start) !== false;
3186
+ }
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";
3191
+ }
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];
3242
+ }
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
3333
+ };
3334
+
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";
3339
+ }
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);
3344
+ }
3345
+ var lane_handoff = { is_handed_off, is_ship_running };
3346
+
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" }
3451
+ };
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()));
3459
+ }
3460
+ function label_name_of(labels, wanted) {
3461
+ const target = wanted.toLowerCase();
3462
+ return labels.find((label2) => label2.toLowerCase() === target);
3463
+ }
3464
+ function has_label_name(labels, wanted) {
3465
+ return label_name_of(labels, wanted) !== void 0;
3466
+ }
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
+ );
3475
+ }
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);
2707
3479
  }
2708
- function has_only(invocation) {
2709
- const tokens = tokens_of(invocation);
2710
- if (tokens[FIRST_TOKEN] !== BACKLOG_COMMAND) return false;
2711
- return tokens.slice(FIRST_ARGUMENT_INDEX).includes(ONLY_FLAG);
3480
+ function is_parking_child(directory = process.cwd()) {
3481
+ return lane_child_marker.is_child_of(directory) && !lane_handoff.is_handed_off(directory);
2712
3482
  }
2713
- var run_invocation = {
2714
- has_only,
2715
- issue_numbers,
2716
- rebuild
3483
+ function is_unparked_stop(command, is_child = is_parking_child) {
3484
+ if (!is_confirmation_notify(command)) return false;
3485
+ return is_child();
3486
+ }
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);
3493
+ }
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
2717
3502
  };
2718
3503
 
2719
- // scripts/run/run-carry.ts
2720
- var CARRY_PREFIX = "josh-run-carry-";
2721
- var REPOSITORY_DIRECTORY_INDEX = 1;
2722
- var CARRY_MAX_AGE_HOURS = backlog_budget.WHOLE_RUN_BUDGET_HOURS;
2723
- var CARRY_MAX_AGE_MS = backlog_budget.WHOLE_RUN_BUDGET_MS;
2724
- var NO_INCREMENT3 = 0;
2725
- var MAX_CUTS = 6;
2726
- var END_COMMAND2 = "pnpm josh run:carry --end";
2727
- var RESUME_COMMAND = "pnpm josh run:carry --resume";
2728
- var READ_COMMAND2 = "pnpm josh run:carry --json";
2729
- var OWNER_ARGUMENT = '--owner "$PPID"';
2730
- var NONE_READ2 = { kind: "none" };
2731
- var UNREADABLE_READ3 = { kind: "unreadable" };
2732
- var NO_OWNER = {};
2733
- var run_carry_schema = z5.object({
2734
- invocation: z5.string(),
2735
- started_at: z5.string(),
2736
- merged: z5.number(),
2737
- merged_issues: z5.array(z5.number()).optional(),
2738
- filed: z5.number(),
2739
- cuts: z5.number(),
2740
- // Defaulted, so a record written before this field existed parses with a zero streak rather than
2741
- // failing to read — the same backward-compatibility the optional owner fields below carry.
2742
- failures: z5.number().default(0),
2743
- // Defaulted for the same backward-compatibility as `failures` (joshuafolkken/kit#2240).
2744
- outages: z5.number().default(0),
2745
- // Optional, so a record written before the outage fold existed still parses (joshuafolkken/kit#2317).
2746
- last_outage_at: z5.string().optional(),
2747
- // Optional, so a record written by the previous shape still parses. Read as "no owner declared",
2748
- // which is the not-provably-live answer rather than a live one.
2749
- owner_pid: z5.number().optional(),
2750
- owner_start: z5.string().optional(),
2751
- is_handed_off: z5.boolean().optional(),
2752
- done: z5.array(z5.number()).optional(),
2753
- // Optional, so a record written before the retrospective existed still parses (joshuafolkken/kit#2328).
2754
- retrospective: z5.boolean().optional()
2755
- });
2756
- function carry_path(git_directory) {
2757
- return stamp_file.stamp_path(CARRY_PREFIX, git_directory);
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;
2758
3524
  }
2759
- async function repository_directory() {
2760
- const directories = await git_command.git_directories();
2761
- return directories[REPOSITORY_DIRECTORY_INDEX];
3525
+ function is_github_ssh_host(host) {
3526
+ const normalized = host.toLowerCase();
3527
+ return is_github_hostname(normalized) || SSH_ALIAS_PATTERN.test(normalized);
2762
3528
  }
2763
- function parse_carry(raw) {
3529
+ function strip_git_suffix(name) {
3530
+ return name.endsWith(GIT_SUFFIX) ? name.slice(0, -GIT_SUFFIX.length) : name;
3531
+ }
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 };
3538
+ }
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));
3546
+ }
3547
+ function parse_url_like(url) {
2764
3548
  try {
2765
- const parsed = run_carry_schema.safeParse(JSON.parse(raw));
2766
- return parsed.success ? parsed.data : void 0;
3549
+ const parsed = new URL(url);
3550
+ return is_github_hostname(parsed.hostname) ? to_identity(parsed.pathname) : void 0;
2767
3551
  } catch {
2768
3552
  return void 0;
2769
3553
  }
2770
3554
  }
2771
- function is_expired2(carry, now) {
2772
- const started = Date.parse(carry.started_at);
2773
- if (Number.isNaN(started)) return true;
2774
- return now.getTime() - started > CARRY_MAX_AGE_MS;
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);
2775
3559
  }
2776
- function classify4(raw, now) {
2777
- if (raw === void 0) return NONE_READ2;
2778
- const carry = parse_carry(raw);
2779
- if (carry === void 0) return UNREADABLE_READ3;
2780
- return { kind: is_expired2(carry, now) ? "expired" : "carried", carry };
3560
+ function is_section_header(line) {
3561
+ return line.startsWith(SECTION_PREFIX);
2781
3562
  }
2782
- function read_carry(target, now = /* @__PURE__ */ new Date()) {
2783
- return classify4(stamp_file.read_stamp_text(target), now);
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);
2784
3569
  }
2785
- function owner_of(pid, read2 = process_identity.read_start) {
2786
- return { pid, start: read2(pid) };
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();
2787
3575
  }
2788
- function fresh_carry(invocation, owner, now) {
2789
- return {
2790
- invocation,
2791
- started_at: now.toISOString(),
2792
- merged: NO_INCREMENT3,
2793
- filed: NO_INCREMENT3,
2794
- cuts: NO_INCREMENT3,
2795
- failures: NO_INCREMENT3,
2796
- outages: NO_INCREMENT3,
2797
- owner_pid: owner.pid,
2798
- owner_start: owner.start
2799
- };
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);
2800
3579
  }
2801
- function begin_carry(target, invocation, owner = NO_OWNER, now = /* @__PURE__ */ new Date()) {
2802
- const carry = fresh_carry(invocation, owner, now);
2803
- return stamp_file.create_stamp(target, carry) ? carry : void 0;
3580
+ function format_identity(identity) {
3581
+ return `${identity.owner}${PATH_SEPARATOR2}${identity.repo}`.toLowerCase();
2804
3582
  }
2805
- function replace_carry(target, invocation, owner = NO_OWNER, now = /* @__PURE__ */ new Date()) {
2806
- stamp_file.remove_stamp(target);
2807
- return begin_carry(target, invocation, owner, now);
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
3590
+ };
3591
+
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();
2808
3600
  }
2809
- function adopt_carry(target, carry, owner = NO_OWNER) {
2810
- const next = {
2811
- ...carry,
2812
- owner_pid: owner.pid,
2813
- owner_start: owner.start,
2814
- is_handed_off: false
2815
- };
2816
- stamp_file.remove_stamp(target);
2817
- return stamp_file.create_stamp(target, next) ? next : void 0;
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);
2818
3606
  }
2819
- function is_owner_live(carry) {
2820
- return process_identity.is_same_process(carry.owner_pid, carry.owner_start) !== false;
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}` };
2821
3614
  }
2822
- function is_owned_by(carry, owner) {
2823
- return carry.owner_pid === owner.pid && carry.owner_start === owner.start;
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);
2824
3618
  }
2825
- function is_foreign_live_owner(carry, owner) {
2826
- return is_owner_live(carry) && !is_owned_by(carry, owner);
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);
3624
+ }
3625
+ return result;
2827
3626
  }
2828
- function is_handed_off_to(carry, invocation) {
2829
- return carry.is_handed_off === true && carry.invocation === invocation;
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
+ }
2830
3646
  }
2831
- function classify_claim(carry, request) {
2832
- if (is_handed_off_to(carry, request.invocation)) return "resume";
2833
- if (is_foreign_live_owner(carry, request.owner)) return "busy";
2834
- if (carry.invocation !== request.invocation) return "mismatch";
2835
- return request.is_adoption ? "resume" : "standing";
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());
2836
3651
  }
2837
- function is_count_refused(carry, owner) {
2838
- return carry.is_handed_off === true || is_foreign_live_owner(carry, owner);
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;
3661
+ }
2839
3662
  }
2840
- function is_at_cut_cap(carry) {
2841
- return carry.cuts >= MAX_CUTS;
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
+ }
2842
3673
  }
2843
- function end_carry(target) {
2844
- stamp_file.remove_stamp(target);
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
+ }
3680
+ }
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
+ }));
3686
+ }
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;
2845
3691
  }
2846
- function retrospective_done_of(read2) {
2847
- if (read2.kind !== "carried" && read2.kind !== "expired") return false;
2848
- return read2.carry.retrospective === true;
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);
2849
3697
  }
2850
- function remaining_of(carry) {
2851
- if (carry === void 0) return void 0;
2852
- const declared = run_invocation.issue_numbers(carry.invocation);
2853
- if (declared === void 0) return void 0;
2854
- const done = carry.done ?? [];
2855
- return declared.filter((issue) => !done.includes(issue));
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
+ );
2856
3707
  }
2857
- function done_note(carry) {
2858
- const done = carry.done ?? [];
2859
- return done.length === 0 ? "" : `, issues ${done.join(", ")} done`;
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
3715
+ };
3716
+
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;
2860
3724
  }
2861
- function describe_carry(carry) {
2862
- 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)}`;
3725
+ function target_owner(owner_repo) {
3726
+ return repo_origin.parse_origin_url(`https://${repo_origin.GITHUB_HOST}/${owner_repo}`)?.owner;
2863
3727
  }
2864
- function expired_message(carry) {
2865
- return `The ${String(CARRY_MAX_AGE_HOURS)}-hour whole-run bound is spent: ${describe_carry(carry)}. Clear it with \`${END_COMMAND2}\`.`;
3728
+ function current_owner(repository_path = process.cwd()) {
3729
+ return repo_discovery.resolve_current_owner(repository_path);
2866
3730
  }
2867
- function mismatch_message(carry, invocation) {
2868
- 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.`;
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);
2869
3745
  }
2870
- function busy_message2(carry) {
2871
- 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.`;
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
+ }
2872
3756
  }
2873
- function standing_message(carry) {
2874
- 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.`;
3757
+ function parent_of(pid) {
3758
+ return parse_pids(run_probe(PS_COMMAND2, [...PARENT_FORMAT, String(pid)]))[0];
2875
3759
  }
2876
- function count_refused_message(carry) {
2877
- if (carry.is_handed_off === true) {
2878
- 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.`;
3760
+ function terminate(pid) {
3761
+ try {
3762
+ process.kill(pid, TERMINATE_SIGNAL);
3763
+ return true;
3764
+ } catch {
3765
+ return false;
2879
3766
  }
2880
- 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.`;
2881
3767
  }
2882
- function lost_message() {
2883
- return `Another process established a run record here first, so nothing was established. Read it with \`${READ_COMMAND2}\`.`;
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;
2884
3782
  }
2885
- function nothing_to_resume_message() {
2886
- return `Nothing is carried here, so there is no budget to resume. Begin one with \`pnpm josh run:carry --begin "<invocation>"\`.`;
3783
+ function own_ancestry(probes = SYSTEM_PROBES) {
3784
+ return ancestry_of(process.pid, probes);
2887
3785
  }
2888
- function unreadable_message3() {
2889
- return `A run record is here but could not be read; clear it with \`${END_COMMAND2}\` once you know no run is using it.`;
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;
2890
3798
  }
2891
- function unknown_message3() {
2892
- return "This repository\u2019s git directory could not be read, so no run record was established.";
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));
2893
3805
  }
2894
- var run_carry = {
2895
- CARRY_MAX_AGE_HOURS,
2896
- CARRY_MAX_AGE_MS,
2897
- END_COMMAND: END_COMMAND2,
2898
- MAX_CUTS,
2899
- NO_OWNER,
2900
- RESUME_COMMAND,
2901
- adopt_carry,
2902
- apply_change: run_carry_change.apply_change,
2903
- begin_carry,
2904
- busy_message: busy_message2,
2905
- carry_path,
2906
- classify: classify4,
2907
- classify_claim,
2908
- count_refused_message,
2909
- is_at_cut_cap,
2910
- describe_carry,
2911
- end_carry,
2912
- expired_message,
2913
- mismatch_message,
2914
- fresh_carry,
2915
- is_count_refused,
2916
- is_expired: is_expired2,
2917
- is_foreign_live_owner,
2918
- is_owner_live,
2919
- lost_message,
2920
- nothing_to_resume_message,
2921
- owner_of,
2922
- parse_carry,
2923
- read_carry,
2924
- remaining_of,
2925
- replace_carry,
2926
- repository_directory,
2927
- retrospective_done_of,
2928
- standing_message,
2929
- unknown_message: unknown_message3,
2930
- unreadable_message: unreadable_message3
3806
+ var lane_reap = {
3807
+ own_ancestry,
3808
+ parse_pids,
3809
+ reap_child
2931
3810
  };
2932
3811
 
2933
3812
  // scripts/lane/lane-registry.ts
2934
- import { existsSync as existsSync2 } from "node:fs";
2935
- import path3 from "node:path";
3813
+ import { existsSync as existsSync3 } from "node:fs";
3814
+ import path5 from "node:path";
2936
3815
 
2937
3816
  // scripts/git/git-ls-remote.ts
2938
3817
  var LS_REMOTE_COMMAND = "ls-remote";
@@ -2944,58 +3823,23 @@ function ls_remote_branch_arguments(branch_name) {
2944
3823
  }
2945
3824
 
2946
3825
  // scripts/git/repository-lock.ts
2947
- import { randomUUID as randomUUID2 } from "node:crypto";
3826
+ import { randomUUID as randomUUID3 } from "node:crypto";
2948
3827
  import { linkSync, renameSync, rmSync as rmSync2 } from "node:fs";
2949
- import { setTimeout as sleep } from "node:timers/promises";
3828
+ import { setTimeout as sleep2 } from "node:timers/promises";
2950
3829
 
2951
- // scripts/git/git-common-directory.ts
2952
- import { execFileSync as execFileSync4 } from "node:child_process";
2953
- var OWN_GIT_DIRECTORY = 0;
2954
- var COMMON_GIT_DIRECTORY = 1;
2955
- var PROBE_TIMEOUT_MS3 = 2e3;
2956
- function read_directories(cwd) {
2957
- const git = git_utilities.get_git_command_for_spawn();
2958
- const output = execFileSync4(git, [...git_command.GIT_DIRECTORY_ARGUMENTS], {
2959
- cwd,
2960
- encoding: "utf8",
2961
- stdio: ["ignore", "pipe", "ignore"],
2962
- timeout: PROBE_TIMEOUT_MS3
2963
- });
2964
- return output.trim().split("\n");
2965
- }
2966
- function select_linked(directories) {
2967
- const own = directories[OWN_GIT_DIRECTORY];
2968
- const common = directories[COMMON_GIT_DIRECTORY];
2969
- return own !== void 0 && common !== void 0 && own !== common ? common : void 0;
2970
- }
2971
- function resolve2(cwd) {
2972
- try {
2973
- return select_linked(read_directories(cwd));
2974
- } catch {
2975
- return void 0;
2976
- }
2977
- }
2978
- function repository(cwd) {
2979
- try {
2980
- return read_directories(cwd)[COMMON_GIT_DIRECTORY];
2981
- } catch {
2982
- return void 0;
2983
- }
2984
- }
2985
- var git_common_directory = { repository, resolve: resolve2, select_linked };
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() });
2986
3833
 
2987
3834
  // scripts/git/repository-lock.ts
2988
3835
  var POLL_INTERVAL_MS = 200;
2989
- function lock_path(prefix, cwd = process.cwd()) {
2990
- return stamp_file.stamp_path(prefix, git_common_directory.repository(cwd) ?? cwd);
3836
+ function lock_path(prefix, cwd) {
3837
+ return stamp_file.stamp_path(prefix, git_common_directory.repository(cwd) ?? cwd ?? process.cwd());
2991
3838
  }
2992
3839
  function parse_owner(raw) {
2993
3840
  if (raw === void 0) return void 0;
2994
- try {
2995
- return JSON.parse(raw);
2996
- } catch {
2997
- return void 0;
2998
- }
3841
+ const parsed = process_owner_schema.safeParse(json_value.parse_or_undefined(raw));
3842
+ return parsed.success ? parsed.data : void 0;
2999
3843
  }
3000
3844
  function read_owner(target) {
3001
3845
  return parse_owner(stamp_file.read_stamp_text(target));
@@ -3015,7 +3859,7 @@ function stale_record(target, is_gone2) {
3015
3859
  return owner !== void 0 && is_gone2(owner) ? raw : void 0;
3016
3860
  }
3017
3861
  function move_aside(target) {
3018
- const aside = `${target}.${String(process.pid)}.${randomUUID2()}`;
3862
+ const aside = `${target}.${String(process.pid)}.${randomUUID3()}`;
3019
3863
  try {
3020
3864
  renameSync(target, aside);
3021
3865
  return aside;
@@ -3045,7 +3889,7 @@ async function acquire(target, max_wait_ms) {
3045
3889
  const deadline = Date.now() + max_wait_ms;
3046
3890
  while (!claim(target)) {
3047
3891
  if (Date.now() >= deadline) return false;
3048
- await sleep(POLL_INTERVAL_MS);
3892
+ await sleep2(POLL_INTERVAL_MS);
3049
3893
  }
3050
3894
  return true;
3051
3895
  }
@@ -3125,13 +3969,10 @@ function strip_export(line) {
3125
3969
  const trimmed2 = line.trimStart();
3126
3970
  return trimmed2.startsWith(EXPORT_PREFIX) ? trimmed2.slice(EXPORT_PREFIX.length).trimStart() : trimmed2;
3127
3971
  }
3128
- function is_key_line(line, key) {
3972
+ function is_key_line(line, key2) {
3129
3973
  const body = strip_export(line);
3130
- if (!body.startsWith(key)) return false;
3131
- return body.slice(key.length).trimStart().startsWith(ASSIGNMENT_SEPARATOR);
3132
- }
3133
- function is_seed_line(line) {
3134
- return is_key_line(line, PORT_SEED_KEY);
3974
+ if (!body.startsWith(key2)) return false;
3975
+ return body.slice(key2.length).trimStart().startsWith(ASSIGNMENT_SEPARATOR);
3135
3976
  }
3136
3977
  function is_seat_line(line) {
3137
3978
  return is_key_line(line, LANE_SEAT_KEY);
@@ -3154,8 +3995,8 @@ function unquote(raw) {
3154
3995
  function assignment_value(line) {
3155
3996
  return unquote(line.slice(line.indexOf(ASSIGNMENT_SEPARATOR) + FIRST_CHARACTER_END).trim());
3156
3997
  }
3157
- function read_assignment(content, key) {
3158
- const line = split_lines(content).findLast((entry) => is_key_line(entry, key));
3998
+ function read_assignment(content, key2) {
3999
+ const line = split_lines(content).findLast((entry) => is_key_line(entry, key2));
3159
4000
  return line === void 0 ? void 0 : assignment_value(line);
3160
4001
  }
3161
4002
  function read_root_seed(content) {
@@ -3188,17 +4029,17 @@ function read_lane_profile(content) {
3188
4029
  effort: read_assignment(content, LANE_EFFORT_KEY)
3189
4030
  });
3190
4031
  }
3191
- function replace_assignment(lines, keep, assignment, key) {
4032
+ function replace_assignment(lines, keep, assignment, key2) {
3192
4033
  return lines.flatMap((entry, index) => {
3193
4034
  if (index === keep) return [assignment];
3194
- return is_key_line(entry, key) ? [] : [entry];
4035
+ return is_key_line(entry, key2) ? [] : [entry];
3195
4036
  });
3196
4037
  }
3197
- function upsert_assignment(content, key, value) {
3198
- const assignment = `${key}${ASSIGNMENT_SEPARATOR}${value}`;
4038
+ function upsert_assignment(content, key2, value) {
4039
+ const assignment = `${key2}${ASSIGNMENT_SEPARATOR}${value}`;
3199
4040
  const lines = split_lines(content);
3200
- const last = lines.findLastIndex((entry) => is_key_line(entry, key));
3201
- const body = last === -1 ? [...lines, assignment] : replace_assignment(lines, last, assignment, key);
4041
+ const last = lines.findLastIndex((entry) => is_key_line(entry, key2));
4042
+ const body = last === -1 ? [...lines, assignment] : replace_assignment(lines, last, assignment, key2);
3202
4043
  return `${body.join("\n")}
3203
4044
  `;
3204
4045
  }
@@ -3214,12 +4055,7 @@ function with_lane_output(content, output, profile) {
3214
4055
  return upsert_assignment(with_model, LANE_EFFORT_KEY, profile.effort);
3215
4056
  }
3216
4057
  var lane_environment = {
3217
- LANE_OUTPUT_KEY,
3218
- LANE_PROVIDER_KEY,
3219
- LANE_ROLE_KEY,
3220
- is_output_line,
3221
4058
  is_seat_line,
3222
- is_seed_line,
3223
4059
  lane_file_content,
3224
4060
  read_lane_output,
3225
4061
  read_lane_profile,
@@ -3253,7 +4089,7 @@ function held_seat(content) {
3253
4089
  return seat_from_content(content) ?? EMPTY_SEAT;
3254
4090
  }
3255
4091
  function read_environment(directory) {
3256
- return file_reader.read_if_readable(path3.join(directory, ENV_FILE_NAME));
4092
+ return file_reader.read_if_readable(path5.join(directory, ENV_FILE_NAME));
3257
4093
  }
3258
4094
  function branch_issue(branch2) {
3259
4095
  if (!branch2?.endsWith(lane_paths.LANE_BRANCH_SUFFIX)) return void 0;
@@ -3261,7 +4097,7 @@ function branch_issue(branch2) {
3261
4097
  return ISSUE_PATTERN.test(issue) ? issue : void 0;
3262
4098
  }
3263
4099
  function is_gone(directory) {
3264
- return !existsSync2(directory);
4100
+ return !existsSync3(directory);
3265
4101
  }
3266
4102
  function build_lane(issue, branch2, directory, is_stranded) {
3267
4103
  const content = is_stranded ? void 0 : read_environment(directory);
@@ -3308,6 +4144,10 @@ async function list_lanes() {
3308
4144
  const lanes = blocks.map((block) => parse_block(block, root)).filter(is_lane);
3309
4145
  return lanes.toSorted((left, right) => Number(left.issue) - Number(right.issue));
3310
4146
  }
4147
+ async function has_lanes_in_flight() {
4148
+ const lanes = await list_lanes();
4149
+ return lanes.some((lane) => !lane.is_stranded);
4150
+ }
3311
4151
  function used_seats(lanes) {
3312
4152
  return lanes.map((lane) => lane.seat).filter((seat) => seat !== void 0);
3313
4153
  }
@@ -3323,6 +4163,7 @@ async function find_open_lane(issue) {
3323
4163
  var lane_registry = {
3324
4164
  find_lane,
3325
4165
  find_open_lane,
4166
+ has_lanes_in_flight,
3326
4167
  list_lanes,
3327
4168
  main_repository_root,
3328
4169
  parse_block,
@@ -3331,171 +4172,6 @@ var lane_registry = {
3331
4172
  used_seats
3332
4173
  };
3333
4174
 
3334
- // scripts/run/run-progress-clock.ts
3335
- import { execaSync } from "execa";
3336
- import { z as z6 } from "zod";
3337
- var PROGRESS_PREFIX = "josh-run-progress-";
3338
- var LIFE_PREFIX = "josh-run-progress-life-";
3339
- var LOG_SUFFIX = ".log";
3340
- var LOG_KEEP = 12;
3341
- var MS_PER_MINUTE2 = 6e4;
3342
- var LOG_STALE_MINUTES = 90;
3343
- var LOG_STALE_MS = LOG_STALE_MINUTES * MS_PER_MINUTE2;
3344
- var BLOCK_SEPARATOR2 = "\n\n";
3345
- var GIT_TIMEOUT_MS = 5e3;
3346
- var GIT_DIRECTORY_ARGUMENTS2 = ["rev-parse", "--absolute-git-dir"];
3347
- var report_stamp_schema = z6.object({ reported_at: z6.string(), line: z6.string().optional() });
3348
- function parse_stamp(raw) {
3349
- const parsed = report_stamp_schema.safeParse(JSON.parse(raw));
3350
- if (!parsed.success) return void 0;
3351
- const reported_at = Date.parse(parsed.data.reported_at);
3352
- return Number.isNaN(reported_at) ? void 0 : reported_at;
3353
- }
3354
- function read_last_line(target) {
3355
- const raw = stamp_file.read_stamp_text(target);
3356
- if (raw === void 0) return void 0;
3357
- try {
3358
- return report_stamp_schema.safeParse(JSON.parse(raw)).data?.line;
3359
- } catch {
3360
- return void 0;
3361
- }
3362
- }
3363
- function read_last_report(target) {
3364
- const raw = stamp_file.read_stamp_text(target);
3365
- if (raw === void 0) return void 0;
3366
- try {
3367
- return parse_stamp(raw);
3368
- } catch {
3369
- return void 0;
3370
- }
3371
- }
3372
- function log_path_of(target) {
3373
- return `${target}${LOG_SUFFIX}`;
3374
- }
3375
- function is_log_stale(target, now_ms) {
3376
- const previous_ms = read_last_report(target);
3377
- return previous_ms === void 0 || now_ms - previous_ms > LOG_STALE_MS;
3378
- }
3379
- function recent_blocks(log_target, is_fresh) {
3380
- if (is_fresh) return [];
3381
- const raw = stamp_file.read_stamp_text(log_target);
3382
- if (raw === void 0) return [];
3383
- return raw.split(BLOCK_SEPARATOR2).map((block) => block.trimEnd()).filter((block) => block.length > 0);
3384
- }
3385
- function append_line(log_target, reported_at, line, is_fresh) {
3386
- const block = `${reported_at}
3387
- ${line}`;
3388
- const blocks = [...recent_blocks(log_target, is_fresh), block].slice(-LOG_KEEP);
3389
- stamp_file.write_text_stamp(log_target, `${blocks.join(BLOCK_SEPARATOR2)}
3390
- `);
3391
- }
3392
- function mark(target, now_ms, line) {
3393
- const reported_at = new Date(now_ms).toISOString();
3394
- const kept = line ?? read_last_line(target);
3395
- if (line !== void 0) {
3396
- append_line(log_path_of(target), reported_at, line, is_log_stale(target, now_ms));
3397
- }
3398
- stamp_file.write_stamp(target, kept === void 0 ? { reported_at } : { reported_at, line: kept });
3399
- }
3400
- function stamp_target_of(git_directory) {
3401
- return stamp_file.stamp_path(PROGRESS_PREFIX, git_directory);
3402
- }
3403
- function life_target_of(git_directory) {
3404
- return stamp_file.stamp_path(LIFE_PREFIX, git_directory);
3405
- }
3406
- var life_schema = z6.object({ alive: z6.literal(true), pinged_at: z6.string().optional() });
3407
- function begin_life(target) {
3408
- stamp_file.write_stamp(target, { alive: true, pinged_at: (/* @__PURE__ */ new Date()).toISOString() });
3409
- }
3410
- function ping_life(target) {
3411
- begin_life(target);
3412
- }
3413
- function parse_pinged_at(raw) {
3414
- const parsed = life_schema.safeParse(JSON.parse(raw));
3415
- if (!parsed.success || parsed.data.pinged_at === void 0) return void 0;
3416
- const pinged_ms = Date.parse(parsed.data.pinged_at);
3417
- return Number.isNaN(pinged_ms) ? void 0 : pinged_ms;
3418
- }
3419
- function is_life_fresh(target, threshold_ms) {
3420
- const raw = stamp_file.read_stamp_text(target);
3421
- if (raw === void 0) return false;
3422
- try {
3423
- const pinged_ms = parse_pinged_at(raw);
3424
- return pinged_ms !== void 0 && Date.now() - pinged_ms < threshold_ms;
3425
- } catch {
3426
- return false;
3427
- }
3428
- }
3429
- function is_life_ended(target) {
3430
- return stamp_file.read_stamp_text(target) === void 0;
3431
- }
3432
- function end_life(target) {
3433
- stamp_file.remove_stamp(target);
3434
- }
3435
- function git_directory_sync() {
3436
- const git_binary = git_utilities.get_git_command_for_spawn();
3437
- const result = execaSync(git_binary, GIT_DIRECTORY_ARGUMENTS2, {
3438
- reject: false,
3439
- timeout: GIT_TIMEOUT_MS
3440
- });
3441
- if (result.exitCode !== 0) return void 0;
3442
- const output = result.stdout.trim();
3443
- return output === "" ? void 0 : output;
3444
- }
3445
- function stamp_target_sync() {
3446
- const directory = git_directory_sync();
3447
- return directory === void 0 ? void 0 : stamp_target_of(directory);
3448
- }
3449
- function read_last_report_sync() {
3450
- const target = stamp_target_sync();
3451
- return target === void 0 ? void 0 : read_last_report(target);
3452
- }
3453
- var run_progress_clock = {
3454
- LIFE_PREFIX,
3455
- LOG_KEEP,
3456
- LOG_STALE_MS,
3457
- PROGRESS_PREFIX,
3458
- append_line,
3459
- begin_life,
3460
- end_life,
3461
- is_life_ended,
3462
- is_life_fresh,
3463
- life_target_of,
3464
- log_path_of,
3465
- mark,
3466
- parse_stamp,
3467
- ping_life,
3468
- read_last_line,
3469
- read_last_report,
3470
- read_last_report_sync,
3471
- stamp_target_of,
3472
- stamp_target_sync
3473
- };
3474
-
3475
- // scripts/run/run-watcher-guard.ts
3476
- var WATCHER_TICK_MS = 3e4;
3477
- var TICK_MULTIPLIER = 3;
3478
- var MS_PER_SECOND3 = 1e3;
3479
- var STALE_THRESHOLD_MS = WATCHER_TICK_MS * TICK_MULTIPLIER;
3480
- var STALE_THRESHOLD_S = STALE_THRESHOLD_MS / MS_PER_SECOND3;
3481
- 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`;
3482
- var OK_RESULT = { kind: "ok" };
3483
- async function has_lanes_in_flight() {
3484
- const all_lanes = await lane_registry.list_lanes();
3485
- return all_lanes.some((lane) => !lane.is_stranded);
3486
- }
3487
- async function check(life_target) {
3488
- if (!await has_lanes_in_flight()) return OK_RESULT;
3489
- if (run_progress_clock.is_life_fresh(life_target, STALE_THRESHOLD_MS)) return OK_RESULT;
3490
- return { kind: "stale", note: STALE_NOTE };
3491
- }
3492
- var run_watcher_guard = {
3493
- STALE_NOTE,
3494
- STALE_THRESHOLD_MS,
3495
- check,
3496
- has_lanes_in_flight
3497
- };
3498
-
3499
4175
  // scripts/run/run-headless.ts
3500
4176
  var HEADLESS_ENV_KEY = "JOSH_RUN_HEADLESS";
3501
4177
  var HEADLESS_VALUE = "1";
@@ -3514,7 +4190,7 @@ async function read_carry_here() {
3514
4190
  }
3515
4191
  async function must_keep_waiting(source = process.env) {
3516
4192
  const is_candidate2 = is_headless(source) && lane_child_marker.marked_issue(source) === void 0;
3517
- if (!is_candidate2 || !await run_watcher_guard.has_lanes_in_flight()) return false;
4193
+ if (!is_candidate2 || !await lane_registry.has_lanes_in_flight()) return false;
3518
4194
  return is_driving(await read_carry_here());
3519
4195
  }
3520
4196
  async function current_carry() {
@@ -3593,18 +4269,16 @@ function no_repo_line(target) {
3593
4269
  return `\u2716 ${label(target)}: could not read this repository \u2014 check gh auth and the origin remote`;
3594
4270
  }
3595
4271
  var issue_cite = {
3596
- SUMMARY_SEPARATOR,
3597
4272
  citation_line,
3598
4273
  citer,
3599
4274
  issue_url,
3600
- label,
3601
4275
  missing_line,
3602
4276
  no_repo_line,
3603
4277
  parse_target,
3604
4278
  unreadable_line
3605
4279
  };
3606
4280
 
3607
- // scripts/git/git-gh-api-path.ts
4281
+ // scripts/gh/git-gh-api-path.ts
3608
4282
  var FULL_PAGE_QUERY = "?per_page=100";
3609
4283
  function repo_api_path(repo) {
3610
4284
  return `repos/${repo ?? "{owner}/{repo}"}`;
@@ -3666,22 +4340,26 @@ var git_gh_api_path = {
3666
4340
  commit_status_api_path
3667
4341
  };
3668
4342
 
4343
+ // scripts/lib/timeouts.ts
4344
+ var FETCH_TIMEOUT_MS = 1e4;
4345
+ var COMMAND_TIMEOUT_MS = 3e4;
4346
+
3669
4347
  // scripts/lib/error-message.ts
3670
4348
  function message_of(error) {
3671
4349
  return error instanceof Error ? error.message : String(error);
3672
4350
  }
3673
4351
  var error_text = { message_of };
3674
4352
 
3675
- // scripts/git/git-gh-exec.ts
4353
+ // scripts/gh/git-gh-exec.ts
3676
4354
  import { execa as execa5, execaSync as execaSync2 } from "execa";
3677
4355
 
3678
- // scripts/git/git-gh-check.ts
4356
+ // scripts/gh/git-gh-check.ts
3679
4357
  import { execa as execa4 } from "execa";
3680
4358
  var GH_NOT_INSTALLED_MSG = "gh CLI is not installed. Install it from https://cli.github.com/";
3681
4359
  var gh_check = {};
3682
4360
  async function run_gh_check() {
3683
4361
  try {
3684
- await execa4("gh", ["--version"]);
4362
+ await execa4("gh", ["--version"], { timeout: COMMAND_TIMEOUT_MS });
3685
4363
  } catch {
3686
4364
  delete gh_check.promise;
3687
4365
  throw new Error(GH_NOT_INSTALLED_MSG);
@@ -3692,9 +4370,6 @@ async function check_gh_installed() {
3692
4370
  await gh_check.promise;
3693
4371
  }
3694
4372
 
3695
- // scripts/git/git-gh-failure.ts
3696
- import { z as z7 } from "zod";
3697
-
3698
4373
  // scripts/git/parse-json-array.ts
3699
4374
  var MALFORMED_JSON = /* @__PURE__ */ Symbol("malformed-json");
3700
4375
  function parse_json_loose(raw_json) {
@@ -3718,25 +4393,18 @@ function read_json_listing(raw_json, element_schema) {
3718
4393
  return { kind: "unexpected_shape" };
3719
4394
  }
3720
4395
  }
3721
- function parse_json_array_safe(raw_json, element_schema) {
3722
- const value = parse_json_loose(raw_json);
3723
- if (value === MALFORMED_JSON) return [];
3724
- return element_schema.array().parse(value);
3725
- }
3726
4396
  function parse_json_object_safe(raw_json, schema) {
3727
4397
  const value = parse_json_loose(raw_json);
3728
4398
  if (value === MALFORMED_JSON) return void 0;
3729
4399
  return schema.parse(value);
3730
4400
  }
3731
4401
  var parse_json = {
3732
- parse_json_array_safe,
3733
- parse_json_array_or_undefined,
3734
- parse_json_object_safe,
3735
- read_json_listing
4402
+ parse_json_object_safe
3736
4403
  };
3737
4404
 
3738
- // scripts/git/git-gh-failure.ts
3739
- 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({});
3740
4408
  var GH_FAILURE_FIELD = "gh_failure";
3741
4409
  function has_gh_failure(error) {
3742
4410
  return error instanceof Error && Object.hasOwn(error, GH_FAILURE_FIELD);
@@ -3763,7 +4431,7 @@ function failure_of(error) {
3763
4431
  }
3764
4432
  var gh_failure = { attach, classify_stdout, failure_of, parse_response_status };
3765
4433
 
3766
- // scripts/git/git-gh-exec.ts
4434
+ // scripts/gh/git-gh-exec.ts
3767
4435
  var BODY_FROM_STDIN = "-";
3768
4436
  var GH_REQUEST_TIMEOUT_MS = 6e4;
3769
4437
  function to_timeout_option(timeout_ms) {
@@ -3829,20 +4497,20 @@ function parse_status_line(output) {
3829
4497
  function status_of_failure(error) {
3830
4498
  return has_stdout_field(error) ? parse_status_line(error.stdout) : void 0;
3831
4499
  }
3832
- async function exec_gh_api_status(path4, timeout_ms) {
4500
+ async function exec_gh_api_status(path6, timeout_ms) {
3833
4501
  const options = to_spawn_options(timeout_ms);
3834
4502
  try {
3835
4503
  await check_gh_installed();
3836
- const { stdout } = await execa5("gh", ["api", "--include", "--silent", path4], options);
4504
+ const { stdout } = await execa5("gh", ["api", "--include", "--silent", path6], options);
3837
4505
  return parse_status_line(stdout);
3838
4506
  } catch (error) {
3839
4507
  return status_of_failure(error);
3840
4508
  }
3841
4509
  }
3842
- function exec_gh_api_status_sync(path4, timeout_ms) {
4510
+ function exec_gh_api_status_sync(path6, timeout_ms) {
3843
4511
  const options = to_spawn_options(timeout_ms);
3844
4512
  try {
3845
- const { stdout } = execaSync2("gh", ["api", "--include", "--silent", path4], options);
4513
+ const { stdout } = execaSync2("gh", ["api", "--include", "--silent", path6], options);
3846
4514
  return parse_status_line(stdout);
3847
4515
  } catch (error) {
3848
4516
  return status_of_failure(error);
@@ -3915,7 +4583,8 @@ export {
3915
4583
  shell_segments,
3916
4584
  bash_triggers,
3917
4585
  issue_cite,
3918
- run_progress_clock,
4586
+ git_command,
4587
+ git_common_directory,
3919
4588
  tail_commands,
3920
4589
  filing_cap,
3921
4590
  CONTEXT_CUT_THRESHOLD,
@@ -3923,7 +4592,6 @@ export {
3923
4592
  agent_session_environment,
3924
4593
  agent_role_profile,
3925
4594
  backlog_budget,
3926
- git_command,
3927
4595
  run_hold,
3928
4596
  run_cut,
3929
4597
  run_tail,
@@ -3940,18 +4608,22 @@ export {
3940
4608
  RELEASE_CLASSIFICATION_NAMES,
3941
4609
  has_any_label,
3942
4610
  has_label_name,
3943
- lane_park,
3944
- repo_origin,
3945
- repo_party,
3946
4611
  process_identity,
4612
+ run_event_stream,
3947
4613
  run_invocation,
3948
4614
  run_carry,
4615
+ run_event_stream_emit,
4616
+ lane_handoff,
4617
+ lane_park,
4618
+ repo_origin,
4619
+ repo_party,
4620
+ process_owner_schema,
3949
4621
  lane_registry,
3950
- run_watcher_guard,
3951
4622
  run_headless,
3952
4623
  FULL_PAGE_QUERY,
3953
4624
  git_gh_api_path,
3954
4625
  error_text,
4626
+ FETCH_TIMEOUT_MS,
3955
4627
  parse_json_array_or_undefined,
3956
4628
  read_json_listing,
3957
4629
  parse_json_object_safe,