@joshuafolkken/kit 1.1784.0 → 1.2116.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 (690) hide show
  1. package/.claude/agents/reviewer-verification.md +19 -0
  2. package/.claude/agents/reviewer.md +19 -0
  3. package/.claude/settings.json +5 -6
  4. package/.claude/skills/dependency-update/SKILL.md +9 -10
  5. package/.claude/skills/workflow-commands/SKILL.md +6 -6
  6. package/.claude/skills/workflow-commands/background-commands.md +8 -5
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +3 -1
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +1 -1
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +5 -2
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +2 -3
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +1 -1
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +3 -2
  13. package/.claude/skills/workflow-commands/backlogrun.md +1 -1
  14. package/.claude/skills/workflow-commands/chain-rule.md +41 -32
  15. package/.claude/skills/workflow-commands/delegation.md +6 -6
  16. package/.claude/skills/workflow-commands/entry-sequence.md +2 -2
  17. package/.claude/skills/workflow-commands/followup.md +7 -5
  18. package/.claude/skills/workflow-commands/fullrun-steps.md +6 -6
  19. package/.claude/skills/workflow-commands/fullrun.md +1 -1
  20. package/.claude/skills/workflow-commands/issue-comments.md +9 -4
  21. package/.claude/skills/workflow-commands/issue-fold-existing.md +2 -1
  22. package/.claude/skills/workflow-commands/kickoff.md +2 -2
  23. package/.claude/skills/workflow-commands/observation-filing.md +1 -1
  24. package/.claude/skills/workflow-commands/observation-ledger.md +6 -4
  25. package/.claude/skills/workflow-commands/pre-gate-cut.md +9 -7
  26. package/.claude/skills/workflow-commands/prerequisite.md +3 -2
  27. package/.claude/skills/workflow-commands/retrospective.md +2 -2
  28. package/.claude/skills/workflow-commands/split-assessment.md +7 -5
  29. package/.codex/hooks.json +5 -5
  30. package/.github/actions/setup-pnpm/action.yml +9 -16
  31. package/.github/dependabot.yml +16 -0
  32. package/.github/workflows/auto-tag.yml +45 -0
  33. package/.github/workflows/ci.yml +155 -15
  34. package/.github/workflows/dependabot-auto-merge.yml +63 -4
  35. package/.github/workflows/production.yml +8 -0
  36. package/.github/workflows/publish.yml +60 -45
  37. package/.vscode/settings.json +19 -0
  38. package/CLAUDE.md +5 -12
  39. package/SECURITY.md +2 -2
  40. package/dist/CLAUDE.md +5 -12
  41. package/dist/commands/document-read-cli.js +2 -1
  42. package/dist/commands/document-section-main.js +2 -1
  43. package/dist/commands/read-set-cli.js +194 -134
  44. package/dist/commands/run-prep-main.js +9694 -10173
  45. package/dist/commands/run-step-cli.js +22138 -18285
  46. package/dist/hooks/audit-provision.js +23 -5
  47. package/dist/hooks/chunk-3GKE653K.js +6204 -0
  48. package/dist/hooks/chunk-45CJJOO7.js +1243 -0
  49. package/dist/hooks/{chunk-CWMBGKYW.js → chunk-5PM2JRGV.js} +2153 -856
  50. package/dist/hooks/{chunk-WR2KQAYZ.js → chunk-7SIUNQXK.js} +1207 -1470
  51. package/dist/hooks/{chunk-O2YBIDSI.js → chunk-CQD4LJE6.js} +1682 -1130
  52. package/dist/hooks/{chunk-VWWCFGWK.js → chunk-OPSRGWFU.js} +1 -1
  53. package/dist/hooks/{chunk-XGBNSWEI.js → chunk-UJ3FPMMM.js} +132 -49
  54. package/dist/hooks/chunk-VVNYJWEI.js +30 -0
  55. package/dist/hooks/chunk-Z6DQJKH3.js +2806 -0
  56. package/dist/hooks/codex-hook-adapter.js +8 -8
  57. package/dist/hooks/format-edited.js +4 -5
  58. package/dist/hooks/inputs.json +386 -360
  59. package/dist/hooks/pretool-guard.js +7 -6
  60. package/dist/hooks/session-lang.js +101 -258
  61. package/dist/hooks/stop-guard.js +1744 -2576
  62. package/dist/josh.js +7133 -6368
  63. package/dist/version/index.js +161 -1
  64. package/eslint/rules/comment-why-only.js +108 -0
  65. package/package.json +16 -9
  66. package/prompts/coding-standards.md +1 -1
  67. package/prompts/collaboration-workflow/file-edits.md +14 -11
  68. package/prompts/collaboration-workflow/issue-citation.md +0 -4
  69. package/prompts/collaboration-workflow/issue-template.md +40 -21
  70. package/prompts/collaboration-workflow/lane-instructions.md +3 -1
  71. package/prompts/collaboration-workflow/measurement.md +8 -1
  72. package/prompts/collaboration-workflow/operating-rules.md +11 -2
  73. package/prompts/collaboration-workflow/output-bounds.md +6 -2
  74. package/prompts/collaboration-workflow/plan-comment.md +1 -1
  75. package/prompts/collaboration-workflow/principles.md +9 -0
  76. package/prompts/collaboration-workflow/residency.md +3 -2
  77. package/prompts/collaboration-workflow/rule-delivery.md +13 -1
  78. package/prompts/collaboration-workflow/upstream-interrupt.md +2 -2
  79. package/prompts/collaboration-workflow/wip-cap.md +3 -1
  80. package/prompts/review-rubric.md +8 -0
  81. package/prompts/review.md +31 -22
  82. package/prompts/testing-guide.md +1 -0
  83. package/scripts/agent/agent-argv.ts +14 -16
  84. package/scripts/agent/agent-provider.ts +138 -0
  85. package/scripts/agent/agent-role-profile.ts +108 -156
  86. package/scripts/agent/agent-safe-text.ts +20 -5
  87. package/scripts/agent/api-usage-limit.ts +65 -0
  88. package/scripts/agent/claude-agent-argv.ts +19 -9
  89. package/scripts/agent/codex-agent-argv.ts +118 -22
  90. package/scripts/agent/usage-limit-reset.ts +179 -0
  91. package/scripts/auth/claude-token-output.ts +2 -2
  92. package/scripts/auto-ok/auto-ok-author.ts +53 -0
  93. package/scripts/auto-ok/auto-ok-cli.ts +20 -9
  94. package/scripts/backlog/backlog-defect-priority.ts +1 -1
  95. package/scripts/backlog/backlog-drive-cli.ts +6 -1
  96. package/scripts/backlog/backlog-drive-hold.ts +3 -3
  97. package/scripts/backlog/backlog-drive-idle.ts +10 -0
  98. package/scripts/backlog/backlog-drive-launch-pass.ts +90 -0
  99. package/scripts/backlog/backlog-drive-launch.ts +8 -2
  100. package/scripts/backlog/backlog-drive-named-offer.ts +73 -30
  101. package/scripts/backlog/backlog-drive-named.ts +28 -7
  102. package/scripts/backlog/backlog-drive-paused.ts +5 -4
  103. package/scripts/backlog/backlog-drive-restore.ts +11 -3
  104. package/scripts/backlog/backlog-drive.ts +21 -60
  105. package/scripts/backlog/backlog-gate-scope.ts +61 -0
  106. package/scripts/backlog/backlog-measure-close.ts +112 -0
  107. package/scripts/backlog/backlog-next.ts +39 -49
  108. package/scripts/backlog/backlog-occupied.ts +2 -2
  109. package/scripts/backlog/backlog-overlap.ts +42 -8
  110. package/scripts/backlog/backlog-plan-cli.ts +4 -1
  111. package/scripts/backlog/backlog-plan-comment.ts +81 -0
  112. package/scripts/backlog/backlog-plan-read.ts +19 -6
  113. package/scripts/backlog/backlog-pool.ts +50 -12
  114. package/scripts/backlog/backlog-rank.ts +60 -13
  115. package/scripts/backlog/backlog-stalled-detect.ts +2 -1
  116. package/scripts/backlog/backlog-waves.ts +17 -2
  117. package/scripts/backlog/backlogrun-launch.ts +7 -4
  118. package/scripts/cost-runtime/context-cut-payback.ts +27 -25
  119. package/scripts/cost-runtime/cost-cli.ts +37 -16
  120. package/scripts/cost-runtime/cost-pricing.ts +9 -0
  121. package/scripts/cost-runtime/cost-verdict.ts +4 -1
  122. package/scripts/delegation/delegation-policy.ts +2 -2
  123. package/scripts/delegation/duplicate-read-guard.ts +1 -0
  124. package/scripts/delegation/investigation-guard.ts +14 -2
  125. package/scripts/delegation/investigation-reads.ts +45 -23
  126. package/scripts/dependabot/dependabot-cooldown.ts +3 -2
  127. package/scripts/discovery/repo-party.ts +80 -20
  128. package/scripts/discovery/session-owner-pin.ts +51 -0
  129. package/scripts/document/capped-print.ts +1 -1
  130. package/scripts/document/command-reference-line-budget.ts +42 -0
  131. package/scripts/document/document-byte-budget.ts +17 -7
  132. package/scripts/document/document-checks.ts +20 -0
  133. package/scripts/document/document-ratchet-check.ts +101 -0
  134. package/scripts/document/document-section.ts +1 -0
  135. package/scripts/document/entry-read-budget.ts +7 -3
  136. package/scripts/document/issue-citation-budget.ts +94 -0
  137. package/scripts/epic/epic-cli-argv.ts +2 -2
  138. package/scripts/epic/epic-fetch.ts +4 -2
  139. package/scripts/epic/epic-graph.ts +7 -2
  140. package/scripts/epic/epic-issue.ts +3 -1
  141. package/scripts/epic/epic-rank.ts +5 -0
  142. package/scripts/exports/unused-members-check.ts +62 -0
  143. package/scripts/exports/unused-members-cli.ts +6 -51
  144. package/scripts/followup/followup-ci-path.ts +67 -0
  145. package/scripts/followup/followup-coderabbit-retrigger.ts +261 -0
  146. package/scripts/followup/followup-coderabbit-wait.ts +13 -10
  147. package/scripts/followup/followup-failed-tests.ts +160 -0
  148. package/scripts/followup/followup-job-needs.ts +100 -0
  149. package/scripts/followup/followup-job-rerun.ts +303 -0
  150. package/scripts/followup/followup-main-move.ts +113 -0
  151. package/scripts/followup/git-followup-finish.ts +43 -11
  152. package/scripts/followup/git-followup-pending.ts +3 -3
  153. package/scripts/followup/git-followup-stages.ts +41 -11
  154. package/scripts/followup/git-pr-followup-checks.ts +55 -16
  155. package/scripts/followup/git-pr-followup-wrapup.ts +24 -5
  156. package/scripts/followup/git-pr-followup.ts +62 -27
  157. package/scripts/fresh-machine/fresh-machine-script.ts +5 -9
  158. package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +2 -2
  159. package/scripts/fresh-machine/specs/kit-test-backlogrun.json +2 -2
  160. package/scripts/gate/busy-average.ts +34 -2
  161. package/scripts/gate/check-related-pinned.ts +43 -0
  162. package/scripts/gate/check-related-reuse.ts +64 -0
  163. package/scripts/gate/check-related-section.ts +57 -0
  164. package/scripts/gate/check-related-unplanned.ts +34 -0
  165. package/scripts/gate/check-related.ts +265 -0
  166. package/scripts/gate/command-memory.ts +1 -1
  167. package/scripts/gate/core-admission.ts +24 -6
  168. package/scripts/gate/core-budget-timing.ts +20 -6
  169. package/scripts/gate/core-budget.ts +40 -11
  170. package/scripts/gate/core-wait-record.ts +3 -2
  171. package/scripts/gate/core-wait.ts +25 -2
  172. package/scripts/gate/core-weights.ts +15 -7
  173. package/scripts/gate/gate-labels.ts +46 -0
  174. package/scripts/gate/gate-ledger.ts +14 -6
  175. package/scripts/gate/gate-plan.ts +103 -58
  176. package/scripts/gate/gate-report.ts +9 -7
  177. package/scripts/gate/gate-run-steps.ts +26 -3
  178. package/scripts/gate/gate-skip.ts +45 -6
  179. package/scripts/gate/gate-static-group.ts +60 -0
  180. package/scripts/gate/gate-steps.ts +2 -1
  181. package/scripts/gate/gate-tree.ts +4 -2
  182. package/scripts/gate/hook-gate-reuse.ts +19 -2
  183. package/scripts/gate/metrics-totals.ts +59 -0
  184. package/scripts/gate/scoped-green.ts +68 -15
  185. package/scripts/gate/since-green.ts +14 -6
  186. package/scripts/gate/structural-checks.ts +96 -0
  187. package/scripts/gate/verification-gate.ts +51 -16
  188. package/scripts/gate/verification-inputs.ts +21 -6
  189. package/scripts/gate/verification-tail.ts +52 -0
  190. package/scripts/gh/gh-author-association.ts +23 -2
  191. package/scripts/gh/git-ai-review-scan.ts +1 -1
  192. package/scripts/gh/git-gh-api-path.ts +12 -3
  193. package/scripts/gh/git-gh-comments-unreadable.ts +36 -0
  194. package/scripts/gh/git-gh-exec.ts +32 -2
  195. package/scripts/gh/git-gh-issue-read.ts +4 -2
  196. package/scripts/gh/git-gh-issue-rest.ts +1 -0
  197. package/scripts/gh/git-gh-issue-write.ts +8 -2
  198. package/scripts/gh/git-gh-issue.ts +12 -5
  199. package/scripts/gh/git-gh-pr-auto-merge.ts +13 -6
  200. package/scripts/gh/git-gh-pr-graphql-state.ts +56 -0
  201. package/scripts/gh/git-gh-pr-read.ts +74 -13
  202. package/scripts/gh/git-gh-pr-rest.ts +3 -1
  203. package/scripts/gh/git-gh-pr-snapshot.ts +22 -7
  204. package/scripts/gh/git-gh-pr-too-large.ts +20 -0
  205. package/scripts/gh/git-gh-pr.ts +21 -7
  206. package/scripts/gh/git-gh-retry.ts +141 -0
  207. package/scripts/gh/git-pr-ai-review.ts +15 -6
  208. package/scripts/gh/git-pr-checks-eval.ts +3 -0
  209. package/scripts/gh/git-pr-checks.ts +5 -5
  210. package/scripts/git/base-tree.ts +30 -0
  211. package/scripts/git/change-base.ts +4 -10
  212. package/scripts/git/changed-paths-sync.ts +63 -0
  213. package/scripts/git/changed-paths.ts +16 -1
  214. package/scripts/git/git-common-directory.ts +11 -1
  215. package/scripts/git/git-diff-reads.ts +66 -20
  216. package/scripts/git/git-error.ts +9 -2
  217. package/scripts/git/git-schemas.ts +13 -0
  218. package/scripts/git/git-worktree.ts +13 -0
  219. package/scripts/git/gone-branch.ts +4 -1
  220. package/scripts/git/main-merge-budget-conflict.ts +89 -0
  221. package/scripts/git/main-merge-guard.ts +55 -18
  222. package/scripts/git/main-merge-regenerate.ts +59 -25
  223. package/scripts/git/main-merge.ts +53 -5
  224. package/scripts/git/no-base-checkout.ts +127 -0
  225. package/scripts/git/stash/git-stash.ts +11 -2
  226. package/scripts/git/stash/stash-push-cli.ts +10 -0
  227. package/scripts/hooks/batch-guard.ts +40 -21
  228. package/scripts/hooks/edited-cspell.ts +37 -11
  229. package/scripts/hooks/format-edited-file.ts +22 -2
  230. package/scripts/hooks/lane-plan-guard.ts +50 -0
  231. package/scripts/hooks/pre-commit-lint-switch.ts +9 -0
  232. package/scripts/hooks/pre-commit-lint.ts +14 -1
  233. package/scripts/hooks/pre-push-related-switch.ts +10 -0
  234. package/scripts/hooks/pre-push-unit.ts +76 -10
  235. package/scripts/hooks/pretool-guard.ts +2 -0
  236. package/scripts/hooks/reserved-run.ts +14 -1
  237. package/scripts/hooks/run-hook.sh +23 -8
  238. package/scripts/hooks/step-zero-notice.ts +6 -1
  239. package/scripts/hooks/stop-actions.ts +3 -15
  240. package/scripts/hooks/stop-guard.ts +7 -6
  241. package/scripts/init/distributed-paths.ts +1 -0
  242. package/scripts/init/hook-command-rewrite.ts +5 -1
  243. package/scripts/init/hook-launch.ts +4 -2
  244. package/scripts/issue/aggregation-split.ts +52 -0
  245. package/scripts/issue/defect-origin-line.ts +19 -1
  246. package/scripts/issue/defect-rate-cli.ts +5 -102
  247. package/scripts/issue/defect-report.ts +4 -4
  248. package/scripts/issue/effect-audit-cli.ts +178 -0
  249. package/scripts/issue/effect-audit.ts +185 -0
  250. package/scripts/issue/expected-effect-lint.ts +64 -9
  251. package/scripts/issue/git-next-issues.ts +5 -0
  252. package/scripts/issue/issue-body-or-comment.ts +38 -4
  253. package/scripts/issue/issue-cite-cli.ts +1 -3
  254. package/scripts/issue/issue-classification.ts +1 -0
  255. package/scripts/issue/issue-closing-pr.ts +8 -1
  256. package/scripts/issue/issue-evidence-exit.ts +4 -2
  257. package/scripts/issue/issue-file-cap.ts +58 -0
  258. package/scripts/issue/issue-file-cli.ts +111 -38
  259. package/scripts/issue/issue-file-deferral.ts +52 -0
  260. package/scripts/issue/issue-file-fold.ts +29 -10
  261. package/scripts/issue/issue-file-interrupt.ts +85 -0
  262. package/scripts/issue/issue-file-lane-hint.ts +30 -0
  263. package/scripts/issue/issue-file.ts +43 -6
  264. package/scripts/issue/issue-labels.ts +19 -2
  265. package/scripts/issue/issue-lint-cli.ts +27 -2
  266. package/scripts/issue/issue-measured-cli.ts +13 -35
  267. package/scripts/issue/issue-measured-close.ts +58 -0
  268. package/scripts/issue/issue-not-before-label.ts +2 -2
  269. package/scripts/issue/issue-not-before.ts +4 -1
  270. package/scripts/issue/issue-priority-cli.ts +60 -0
  271. package/scripts/issue/issue-priority-raise.ts +55 -0
  272. package/scripts/issue/issue-priority.ts +174 -43
  273. package/scripts/issue/issue-rank.ts +21 -4
  274. package/scripts/issue/issue-read-cli.ts +32 -6
  275. package/scripts/issue/issue-read.ts +37 -12
  276. package/scripts/issue/issue-sample-gate.ts +95 -74
  277. package/scripts/issue/issue-sample-prime.ts +127 -0
  278. package/scripts/issue/issue-sample-required.ts +57 -0
  279. package/scripts/issue/issue-sample-source.ts +12 -11
  280. package/scripts/issue/issue-search.ts +124 -0
  281. package/scripts/issue/issue-state.ts +13 -1
  282. package/scripts/issue/issue-template-section.ts +43 -0
  283. package/scripts/issue/issue-watch-diff.ts +31 -1
  284. package/scripts/issue/issue-watch-hook.ts +11 -2
  285. package/scripts/issue/issue-watch.ts +55 -12
  286. package/scripts/issue/markdown-section.ts +1 -0
  287. package/scripts/issue/measure-declaration.ts +121 -0
  288. package/scripts/issue/measure-rerun-cli.ts +7 -70
  289. package/scripts/issue/plan-consumers-section.ts +22 -1
  290. package/scripts/issue/reproduction-measure.ts +3 -1
  291. package/scripts/issue/size-estimate.ts +90 -0
  292. package/scripts/issue/trusted-measure.ts +84 -0
  293. package/scripts/josh/command-usage.ts +3 -2
  294. package/scripts/josh/durable-state-migrate.ts +105 -0
  295. package/scripts/josh/durable-state.ts +112 -0
  296. package/scripts/josh/josh-aliases.ts +95 -0
  297. package/scripts/josh/josh-command-map.ts +1 -82
  298. package/scripts/josh/josh-commands-agent-hooks.ts +6 -6
  299. package/scripts/josh/josh-commands-ai.ts +5 -5
  300. package/scripts/josh/josh-commands-development.ts +59 -58
  301. package/scripts/josh/josh-commands-issue-write.ts +23 -4
  302. package/scripts/josh/josh-commands-issue.ts +32 -0
  303. package/scripts/josh/josh-commands-lane.ts +87 -4
  304. package/scripts/josh/josh-commands-lint.ts +16 -3
  305. package/scripts/josh/josh-commands-maintenance.ts +15 -0
  306. package/scripts/josh/josh-commands-review.ts +41 -6
  307. package/scripts/josh/josh-commands-ship.ts +56 -8
  308. package/scripts/josh/josh-commands-test-check.ts +80 -0
  309. package/scripts/josh/josh-commands-workflow.ts +1 -1
  310. package/scripts/josh/josh-environment-file.ts +43 -3
  311. package/scripts/josh/josh-in-process.ts +2 -2
  312. package/scripts/josh/josh-logic.ts +5 -3
  313. package/scripts/josh/josh-run.ts +19 -1
  314. package/scripts/josh/run-record-path.ts +15 -0
  315. package/scripts/josh/stamp-file.ts +11 -2
  316. package/scripts/lane/lane-adopt-cli.ts +98 -0
  317. package/scripts/lane/lane-adopt.ts +208 -0
  318. package/scripts/lane/lane-await.ts +5 -8
  319. package/scripts/lane/lane-baseline.ts +123 -0
  320. package/scripts/lane/lane-breakdown-cli.ts +64 -0
  321. package/scripts/lane/lane-breakdown-implement.ts +170 -0
  322. package/scripts/lane/lane-breakdown-laps.ts +107 -0
  323. package/scripts/lane/lane-breakdown-pinned.ts +76 -0
  324. package/scripts/lane/lane-breakdown-text.ts +145 -0
  325. package/scripts/lane/lane-breakdown.ts +239 -0
  326. package/scripts/lane/lane-check-stats.ts +29 -8
  327. package/scripts/lane/lane-child-invocation.ts +9 -2
  328. package/scripts/lane/lane-child-mark-keys.ts +12 -0
  329. package/scripts/lane/lane-child-marker.ts +20 -1
  330. package/scripts/lane/lane-ci-path-stats.ts +73 -0
  331. package/scripts/lane/lane-claim-stats.ts +84 -0
  332. package/scripts/lane/lane-close.ts +8 -3
  333. package/scripts/lane/lane-dispatch-log.ts +3 -3
  334. package/scripts/lane/lane-dispatch.ts +2 -2
  335. package/scripts/lane/lane-gap-stats.ts +7 -6
  336. package/scripts/lane/lane-guard-policy.ts +7 -1
  337. package/scripts/lane/lane-implement-stats.ts +207 -0
  338. package/scripts/lane/lane-implement-subagents.ts +90 -0
  339. package/scripts/lane/lane-investigation-switch.ts +16 -0
  340. package/scripts/lane/lane-ledger-kind.ts +31 -0
  341. package/scripts/lane/lane-ledger-schema-ship.ts +49 -0
  342. package/scripts/lane/lane-ledger-schema.ts +119 -36
  343. package/scripts/lane/lane-ledger-write.ts +257 -0
  344. package/scripts/lane/lane-ledger.ts +31 -177
  345. package/scripts/lane/lane-load.ts +1 -1
  346. package/scripts/lane/lane-measure-cli.ts +176 -59
  347. package/scripts/lane/lane-net-read.ts +44 -0
  348. package/scripts/lane/lane-net-time.ts +212 -0
  349. package/scripts/lane/lane-open.ts +16 -7
  350. package/scripts/lane/lane-output.ts +2 -4
  351. package/scripts/lane/lane-overrun-prompt.ts +147 -0
  352. package/scripts/lane/lane-overrun.ts +274 -0
  353. package/scripts/lane/lane-paths.ts +24 -1
  354. package/scripts/lane/lane-pause-cli.ts +4 -1
  355. package/scripts/lane/lane-paused-spans.ts +97 -0
  356. package/scripts/lane/lane-plan-check.ts +207 -0
  357. package/scripts/lane/lane-plan-cli.ts +41 -0
  358. package/scripts/lane/lane-plan.ts +144 -0
  359. package/scripts/lane/lane-registry.ts +14 -1
  360. package/scripts/lane/lane-relaunch.ts +1 -1
  361. package/scripts/lane/lane-round-one-stats.ts +51 -0
  362. package/scripts/lane/lane-sampler.ts +23 -4
  363. package/scripts/lane/lane-session-action.ts +93 -18
  364. package/scripts/lane/lane-session-broker.ts +49 -21
  365. package/scripts/lane/lane-session-reread-main.ts +14 -0
  366. package/scripts/lane/lane-session-reread.ts +51 -0
  367. package/scripts/lane/lane-ship-lap-stats.ts +74 -0
  368. package/scripts/lane/lane-sleep.ts +58 -0
  369. package/scripts/lane/lane-spent.ts +1 -1
  370. package/scripts/lane/lane-split-stats.ts +28 -0
  371. package/scripts/lane/lane-stage-stats.ts +13 -1
  372. package/scripts/lane/lane-state.ts +41 -9
  373. package/scripts/lane/lane-stats-tables.ts +58 -0
  374. package/scripts/lane/lane-stats.ts +21 -5
  375. package/scripts/lane/lane-stop-stats.ts +51 -19
  376. package/scripts/lane/lane-tail-stats.ts +32 -0
  377. package/scripts/lane/lane-transcript-calls.ts +67 -10
  378. package/scripts/lane/lane-transcript-stats.ts +44 -12
  379. package/scripts/lane/lane-value-cell.ts +72 -0
  380. package/scripts/lane/lane-value.ts +118 -0
  381. package/scripts/lane/lane-withdraw-closed.ts +124 -0
  382. package/scripts/lane/lane-withdraw.ts +108 -31
  383. package/scripts/lane/lane-yield.ts +85 -0
  384. package/scripts/lane/openai-lane-ship.ts +12 -11
  385. package/scripts/lane/openai-lane-supervisor-decision.ts +6 -8
  386. package/scripts/lane/openai-lane-supervisor-owner.ts +5 -8
  387. package/scripts/lane/openai-lane-supervisor.ts +5 -4
  388. package/scripts/lib/poll.ts +38 -9
  389. package/scripts/lib/settled-together.ts +10 -0
  390. package/scripts/lib/shell-quote.ts +21 -0
  391. package/scripts/lib/token-shape.ts +111 -0
  392. package/scripts/lint/eslint-daemon-sources.ts +79 -0
  393. package/scripts/lint/eslint-daemon.ts +45 -6
  394. package/scripts/lint/eslint-warm-cli.ts +7 -0
  395. package/scripts/lint/eslint-warm.ts +66 -0
  396. package/scripts/lint/import-graph.ts +141 -11
  397. package/scripts/lint/lint-gate-scope.ts +2 -2
  398. package/scripts/lint/lint-parallel.ts +54 -6
  399. package/scripts/lint/lint-related.ts +154 -33
  400. package/scripts/lint/rename-cli.ts +179 -0
  401. package/scripts/lint/rename-identifier.ts +192 -0
  402. package/scripts/lint/type-only-imports.ts +58 -0
  403. package/scripts/notify/telegram-notify.ts +12 -3
  404. package/scripts/observations/observation-record.ts +42 -16
  405. package/scripts/observations/observations-flush.ts +32 -9
  406. package/scripts/overrides/overrides-check.ts +9 -1
  407. package/scripts/release/release-approval-prune.ts +7 -1
  408. package/scripts/repo/required-checks-logic.ts +4 -1
  409. package/scripts/repo/ruleset-check.ts +4 -3
  410. package/scripts/report/markdown-shape.ts +28 -0
  411. package/scripts/report/report-format-reference.ts +15 -1
  412. package/scripts/report/report-labels.ts +12 -1
  413. package/scripts/report/report-lint-child-summary.ts +94 -0
  414. package/scripts/report/report-lint-cli.ts +26 -7
  415. package/scripts/report/report-lint.ts +2 -20
  416. package/scripts/review/prose-stale-cli.ts +88 -0
  417. package/scripts/review/prose-stale-scan.ts +152 -0
  418. package/scripts/review/review-attest.ts +73 -23
  419. package/scripts/review/review-brief-cli.ts +68 -19
  420. package/scripts/review/review-brief-paths.ts +22 -0
  421. package/scripts/review/review-brief.ts +14 -7
  422. package/scripts/review/review-comment-carry.ts +47 -0
  423. package/scripts/review/review-comment-round.ts +85 -22
  424. package/scripts/review/review-finding-ledger.ts +109 -7
  425. package/scripts/review/review-finding-text.ts +43 -0
  426. package/scripts/review/review-interaction.ts +11 -0
  427. package/scripts/review/review-lane-handover.ts +68 -0
  428. package/scripts/review/review-lint-cli.ts +57 -0
  429. package/scripts/review/review-lint-findings.ts +92 -0
  430. package/scripts/review/review-lint-sections.ts +80 -0
  431. package/scripts/review/review-lint.ts +221 -0
  432. package/scripts/review/review-prior-findings.ts +102 -0
  433. package/scripts/review/review-readers-scan.ts +4 -2
  434. package/scripts/review/review-record-cli.ts +177 -54
  435. package/scripts/review/review-record-code-round.ts +55 -0
  436. package/scripts/review/review-record-comment.ts +60 -0
  437. package/scripts/review/review-record-interaction.ts +35 -0
  438. package/scripts/review/review-record-pinned.ts +108 -0
  439. package/scripts/review/review-record.ts +25 -1
  440. package/scripts/review/review-reviewed-tree.ts +307 -0
  441. package/scripts/review/review-round-one-closure.ts +62 -0
  442. package/scripts/review/review-round-reuse.ts +106 -0
  443. package/scripts/review/review-round2-cli.ts +91 -29
  444. package/scripts/review/review-round2-skip-comment.ts +55 -0
  445. package/scripts/review/review-round2.ts +24 -8
  446. package/scripts/review/review-stamps.ts +7 -3
  447. package/scripts/review/review-stop-log.ts +20 -6
  448. package/scripts/review/review-stops-cli.ts +2 -1
  449. package/scripts/review/review-submit-cli.ts +101 -0
  450. package/scripts/rules/agent-model.ts +1 -1
  451. package/scripts/rules/bash-triggers.ts +3 -3
  452. package/scripts/rules/concise-reply.ts +88 -0
  453. package/scripts/rules/credential-body.ts +77 -0
  454. package/scripts/rules/decision-oracle.ts +1 -1
  455. package/scripts/rules/delivered-rules.ts +36 -17
  456. package/scripts/rules/destructive-command.ts +3 -11
  457. package/scripts/rules/direct-issue-comment.ts +34 -12
  458. package/scripts/rules/direct-issue-read.ts +191 -0
  459. package/scripts/rules/direct-pr-create.ts +4 -24
  460. package/scripts/rules/early-heartbeat.ts +2 -0
  461. package/scripts/rules/edit-tool.ts +27 -0
  462. package/scripts/rules/file-edit-rows.ts +17 -0
  463. package/scripts/rules/file-rewrite.ts +146 -0
  464. package/scripts/rules/firing-point.ts +3 -2
  465. package/scripts/rules/gh-api.ts +128 -7
  466. package/scripts/rules/gh-repo-selector.ts +84 -0
  467. package/scripts/rules/git-force.ts +2 -2
  468. package/scripts/rules/guard-remedy.ts +14 -5
  469. package/scripts/rules/implementation-cut.ts +61 -28
  470. package/scripts/rules/investigator-foreground.ts +231 -0
  471. package/scripts/rules/issue-citation.ts +15 -60
  472. package/scripts/rules/issue-comments.ts +9 -4
  473. package/scripts/rules/josh-git-bare.ts +1 -1
  474. package/scripts/rules/lane-background.ts +41 -13
  475. package/scripts/rules/lane-direct-tool.ts +107 -9
  476. package/scripts/rules/lane-interactive-ask.ts +4 -1
  477. package/scripts/rules/lane-park.ts +4 -2
  478. package/scripts/rules/lane-spilled-read.ts +90 -0
  479. package/scripts/rules/lane-split-park.ts +1 -1
  480. package/scripts/rules/lane-switch-main.ts +4 -4
  481. package/scripts/rules/last-prompt.ts +20 -47
  482. package/scripts/rules/oracle-consulted.ts +1 -1
  483. package/scripts/rules/permission-guards.ts +5 -1
  484. package/scripts/rules/piped-verification-shapes.ts +110 -0
  485. package/scripts/rules/piped-verification.ts +67 -17
  486. package/scripts/rules/poll-loop.ts +72 -9
  487. package/scripts/rules/pr-merge.ts +5 -5
  488. package/scripts/rules/pre-gate-cut.ts +3 -3
  489. package/scripts/rules/protected-files.ts +88 -24
  490. package/scripts/rules/reply-language.ts +3 -17
  491. package/scripts/rules/reply-prose.ts +34 -0
  492. package/scripts/rules/repo-admin.ts +132 -0
  493. package/scripts/rules/reviewer-foreground.ts +88 -0
  494. package/scripts/rules/round-one-check-related.ts +120 -0
  495. package/scripts/rules/rule-body-guard.ts +5 -15
  496. package/scripts/rules/rule-value-cache.ts +53 -5
  497. package/scripts/rules/rule-value-candidates.ts +84 -0
  498. package/scripts/rules/rule-value-cli.ts +59 -11
  499. package/scripts/rules/rule-value.ts +17 -4
  500. package/scripts/rules/run-tail-rule.ts +1 -1
  501. package/scripts/rules/sed-in-place.ts +196 -0
  502. package/scripts/rules/shell-segments.ts +10 -2
  503. package/scripts/rules/stop-rule-rows.ts +1 -13
  504. package/scripts/rules/stop-rules.ts +33 -58
  505. package/scripts/rules/test-declared-commit.ts +1 -1
  506. package/scripts/rules/third-party-write.ts +110 -23
  507. package/scripts/rules/timed-rerun.ts +1 -1
  508. package/scripts/rules/turn-filings.ts +90 -0
  509. package/scripts/rules/worktree-guard.ts +22 -7
  510. package/scripts/run/board/run-board-ending-reason.ts +6 -2
  511. package/scripts/run/board/run-board-fit.ts +24 -1
  512. package/scripts/run/board/run-board-found.ts +134 -0
  513. package/scripts/run/board/run-board-header.ts +8 -0
  514. package/scripts/run/board/run-board-labels.ts +11 -1
  515. package/scripts/run/board/run-board-lane-time.ts +1 -0
  516. package/scripts/run/board/run-board-link.ts +15 -2
  517. package/scripts/run/board/run-board-notes.ts +7 -1
  518. package/scripts/run/board/run-board-operation.ts +4 -2
  519. package/scripts/run/board/run-board-phase.ts +2 -5
  520. package/scripts/run/board/run-board-plan.ts +1 -1
  521. package/scripts/run/board/run-board-read.ts +46 -1
  522. package/scripts/run/board/run-board-reason.ts +20 -8
  523. package/scripts/run/board/run-board-render-checks.ts +3 -2
  524. package/scripts/run/board/run-board-render-drawn.ts +11 -0
  525. package/scripts/run/board/run-board-render-found.ts +157 -0
  526. package/scripts/run/board/run-board-render-lead.ts +9 -0
  527. package/scripts/run/board/run-board-render-legend.ts +19 -0
  528. package/scripts/run/board/run-board-render-tail.ts +1 -1
  529. package/scripts/run/board/run-board-render.ts +82 -18
  530. package/scripts/run/board/run-board-spin.ts +1 -3
  531. package/scripts/run/board/run-board-status.ts +2 -0
  532. package/scripts/run/board/run-board-tick.ts +39 -10
  533. package/scripts/run/carry/run-carry-ended.ts +3 -2
  534. package/scripts/run/carry/run-carry.ts +3 -2
  535. package/scripts/run/cut/run-cut-cli.ts +33 -133
  536. package/scripts/run/cut/run-cut-digest.ts +152 -0
  537. package/scripts/run/cut/run-cut-handoff.ts +52 -8
  538. package/scripts/run/cut/run-cut-report.ts +24 -3
  539. package/scripts/run/cut/run-cut-resume.ts +129 -0
  540. package/scripts/run/cut/run-cut-review.ts +105 -0
  541. package/scripts/run/cut/run-cut.ts +18 -7
  542. package/scripts/run/detached-launch.ts +1 -1
  543. package/scripts/run/entry/run-entry-cli.ts +88 -19
  544. package/scripts/run/entry/run-entry-spill.ts +83 -0
  545. package/scripts/run/entry/run-entry-stop.ts +5 -1
  546. package/scripts/run/entry/run-entry-trust.ts +91 -0
  547. package/scripts/run/event/run-event-baseline.ts +56 -0
  548. package/scripts/run/event/run-event-filed.ts +9 -3
  549. package/scripts/run/event/run-event-overrun.ts +197 -0
  550. package/scripts/run/event/run-event-render.ts +4 -0
  551. package/scripts/run/event/run-event-stream.ts +25 -3
  552. package/scripts/run/hold/run-stop-notify.ts +3 -8
  553. package/scripts/run/merge/run-merge-cap.ts +57 -0
  554. package/scripts/run/merge/run-merge-cli.ts +66 -23
  555. package/scripts/run/merge/run-merge-outcome.ts +2 -0
  556. package/scripts/run/merge/run-merge-ship.ts +13 -3
  557. package/scripts/run/merge/run-merge-signals.ts +40 -23
  558. package/scripts/run/merge/run-merge-steps.ts +56 -36
  559. package/scripts/run/merge/run-merge-usage-limit.ts +53 -0
  560. package/scripts/run/merge/run-merge-withdrawn.ts +53 -0
  561. package/scripts/run/merge/run-merge.ts +41 -14
  562. package/scripts/run/review/run-review-cli.ts +306 -20
  563. package/scripts/run/review/run-review-early-push.ts +121 -0
  564. package/scripts/run/review/run-review-join-read.ts +94 -0
  565. package/scripts/run/review/run-review-pinned-accepted.ts +126 -0
  566. package/scripts/run/review/run-review-pinned-join.ts +216 -0
  567. package/scripts/run/review/run-review-pinned-steps.ts +383 -0
  568. package/scripts/run/review/run-review-pinned-tree.ts +37 -0
  569. package/scripts/run/review/run-review-pinned.ts +53 -0
  570. package/scripts/run/review/run-review-steps.ts +70 -34
  571. package/scripts/run/review/run-review.ts +26 -6
  572. package/scripts/run/run-parent-cut-hook.ts +3 -4
  573. package/scripts/run/run-prep.ts +1 -0
  574. package/scripts/run/run-stage-read.ts +2 -6
  575. package/scripts/run/run-step-cli.ts +17 -5
  576. package/scripts/run/run-step-lane-plan.ts +27 -0
  577. package/scripts/run/run-step-section.ts +26 -7
  578. package/scripts/run/run-step.ts +5 -2
  579. package/scripts/run/ship/run-ship-args.ts +19 -0
  580. package/scripts/run/ship/run-ship-brokered-review.ts +106 -0
  581. package/scripts/run/ship/run-ship-cli.ts +131 -39
  582. package/scripts/run/ship/run-ship-conflict-probe.ts +59 -0
  583. package/scripts/run/ship/run-ship-detach.ts +10 -4
  584. package/scripts/run/ship/run-ship-handover.ts +106 -0
  585. package/scripts/run/ship/run-ship-infra-retry.ts +16 -1
  586. package/scripts/run/ship/run-ship-lap.ts +63 -0
  587. package/scripts/run/ship/run-ship-next.ts +27 -26
  588. package/scripts/run/ship/run-ship-overlap.ts +8 -3
  589. package/scripts/run/ship/run-ship-place.ts +9 -2
  590. package/scripts/run/ship/run-ship-planned.ts +92 -0
  591. package/scripts/run/ship/run-ship-pre-detach-pinned.ts +197 -0
  592. package/scripts/run/ship/run-ship-pre-detach-timed.ts +7 -8
  593. package/scripts/run/ship/run-ship-pre-detach.ts +77 -23
  594. package/scripts/run/ship/run-ship-preflight.ts +16 -4
  595. package/scripts/run/ship/run-ship-probe.ts +23 -9
  596. package/scripts/run/ship/run-ship-relaunch-cap.ts +43 -7
  597. package/scripts/run/ship/run-ship-relaunch.ts +51 -0
  598. package/scripts/run/ship/run-ship-return.ts +103 -29
  599. package/scripts/run/ship/run-ship-review-reason.ts +24 -0
  600. package/scripts/run/ship/run-ship-review-steps.ts +230 -59
  601. package/scripts/run/ship/run-ship-review.ts +171 -51
  602. package/scripts/run/ship/run-ship-reviewer.ts +138 -42
  603. package/scripts/run/ship/run-ship-scoped.ts +39 -10
  604. package/scripts/run/ship/run-ship-stage.ts +49 -13
  605. package/scripts/run/ship/run-ship-steps.ts +24 -4
  606. package/scripts/run/ship/run-ship-stop-cause.ts +43 -2
  607. package/scripts/run/ship/run-ship-stop-context.ts +50 -13
  608. package/scripts/run/ship/run-ship-sync.ts +171 -30
  609. package/scripts/run/ship/run-ship-synced-scope.ts +178 -0
  610. package/scripts/run/ship/run-ship-wait-cli.ts +145 -0
  611. package/scripts/run/ship/run-ship-waiter.ts +114 -0
  612. package/scripts/run/ship/run-ship.ts +16 -2
  613. package/scripts/run/wake/run-wake-caffeinate.ts +48 -0
  614. package/scripts/run/wake/run-wake-cli.ts +6 -113
  615. package/scripts/run/wake/run-wake-code.ts +53 -11
  616. package/scripts/run/wake/run-wake-driver.ts +5 -3
  617. package/scripts/run/wake/run-wake-finish.ts +137 -0
  618. package/scripts/run/wake/run-wake-loop.ts +5 -3
  619. package/scripts/run/wake/run-wake-ports.ts +11 -3
  620. package/scripts/run/wake/run-wake-session.ts +2 -0
  621. package/scripts/run/wake/run-wake.ts +3 -2
  622. package/scripts/safe-chain/ci-installer-pin.ts +49 -8
  623. package/scripts/safe-chain/project-config.ts +3 -2
  624. package/scripts/security/secretlint-scan.ts +42 -9
  625. package/scripts/security/security-audit-provision-main.ts +9 -1
  626. package/scripts/security/security-audit-provision.ts +1 -1
  627. package/scripts/security/security-audit.ts +21 -1
  628. package/scripts/security/sonar-file.ts +10 -5
  629. package/scripts/split/split-assess.ts +12 -3
  630. package/scripts/split/split-early-notice.ts +116 -0
  631. package/scripts/split/split-measure.ts +20 -2
  632. package/scripts/split/split-resolution.ts +107 -0
  633. package/scripts/test/direct-importers.ts +185 -0
  634. package/scripts/test/pure-files-entries.ts +83 -0
  635. package/scripts/test/related-heavy-tests.ts +81 -0
  636. package/scripts/test/test-no-base.ts +88 -0
  637. package/scripts/test/test-pinned-cache-logic.ts +363 -0
  638. package/scripts/test/test-pinned-cache.ts +337 -0
  639. package/scripts/test/test-pinned-emit.ts +49 -0
  640. package/scripts/test/test-pinned-functions.ts +116 -0
  641. package/scripts/test/test-pinned-ledger.ts +80 -0
  642. package/scripts/test/test-pinned-logic.ts +208 -20
  643. package/scripts/test/test-pinned-main.ts +84 -18
  644. package/scripts/test/test-pinned-pool.ts +73 -0
  645. package/scripts/test/test-pinned-probe.ts +254 -0
  646. package/scripts/test/test-pinned-prune-logic.ts +118 -0
  647. package/scripts/test/test-pinned-reach.ts +194 -0
  648. package/scripts/test/test-pinned-record.ts +196 -0
  649. package/scripts/test/test-pinned-rewire.ts +148 -0
  650. package/scripts/test/test-pinned-spawn.ts +81 -0
  651. package/scripts/test/test-pinned-stage.ts +70 -0
  652. package/scripts/test/test-pinned.ts +258 -47
  653. package/scripts/test/test-reach-cli.ts +61 -0
  654. package/scripts/test/test-read-map.ts +35 -11
  655. package/scripts/test/test-read-recording.ts +17 -5
  656. package/scripts/test/test-read-trace.ts +26 -8
  657. package/scripts/test/test-red-logic.ts +46 -7
  658. package/scripts/test/test-red.ts +34 -14
  659. package/scripts/test/test-related-claim.ts +37 -0
  660. package/scripts/test/test-related-main.ts +4 -1
  661. package/scripts/test/test-related-moved.ts +91 -0
  662. package/scripts/test/test-related-record.ts +43 -23
  663. package/scripts/test/test-related-scope.ts +99 -34
  664. package/scripts/test/test-related-synced.ts +135 -0
  665. package/scripts/test/test-related-touched.ts +84 -0
  666. package/scripts/test/test-related.ts +167 -83
  667. package/scripts/test/test-unit-guard.ts +28 -5
  668. package/scripts/test/unit-durations.ts +41 -0
  669. package/scripts/test/unit-worker-share.ts +5 -4
  670. package/scripts/test/vitest-cache-directory.ts +19 -0
  671. package/scripts/test/vitest-cli-excludes.ts +14 -0
  672. package/scripts/test/vitest-failure-summary.ts +111 -0
  673. package/scripts/test/whole-suite-files.ts +12 -1
  674. package/scripts/time-runtime/time-batch-guard.ts +42 -4
  675. package/scripts/time-runtime/time-bundle-call.ts +1 -1
  676. package/scripts/time-runtime/time-bundles.ts +36 -5
  677. package/scripts/time-runtime/time-markers.ts +2 -2
  678. package/scripts/time-runtime/time-search-call.ts +38 -0
  679. package/scripts/time-runtime/time-shell.ts +1 -1
  680. package/scripts/time-runtime/time-transcript-line.ts +17 -5
  681. package/scripts/version/development-engines-verdict.ts +124 -0
  682. package/scripts/version/latest-corepack.ts +2 -0
  683. package/templates/gitignore +2 -0
  684. package/templates/workflows/ci.yml +41 -0
  685. package/dist/hooks/chunk-AZX4FGQ3.js +0 -207
  686. package/dist/hooks/chunk-PT5R6UJD.js +0 -7131
  687. package/dist/hooks/chunk-TUIFS5E7.js +0 -72
  688. package/dist/hooks/chunk-YCDOIR3I.js +0 -3456
  689. package/scripts/cost-runtime/context-cut-threshold.ts +0 -11
  690. package/scripts/rules/filing-cap.ts +0 -139
@@ -0,0 +1,114 @@
1
+ import { setTimeout as sleep } from 'node:timers/promises'
2
+ import { stamp_file } from '#scripts/josh/stamp-file'
3
+ import { json_value } from '#scripts/lib/json-value'
4
+ import { z } from 'zod'
5
+
6
+ // How a stopped detached ship goes back to the lane session that launched it, rather than to a fresh
7
+ // one that re-reads the diff and the findings (joshuafolkken/kit#4711).
8
+ //
9
+ // **The session proves it is alive by waiting**: `josh ship:wait` refreshes a heartbeat on every poll,
10
+ // and keeps it for one session gap after a chunk ends `pending`, so the session can re-issue the wait
11
+ // without the supervisor reading it as gone. A wait that ends for good removes it.
12
+ //
13
+ // **One side decides who answers a stop, by an exclusive create**: the supervisor writes the stop note
14
+ // and waits for the waiter to take it; past the gap it withdraws it and relaunches as before. Taking and
15
+ // withdrawing are the same `create_stamp` on one decision record, so exactly one of them wins and a stop
16
+ // is never answered twice nor dropped.
17
+
18
+ const HEARTBEAT_PREFIX = 'josh-ship-waiter-'
19
+ const HANDOFF_PREFIX = 'josh-ship-handoff-'
20
+ const DECISION_PREFIX = 'josh-ship-handoff-taken-'
21
+ const KEY_SEPARATOR = '-'
22
+ const TAKEN = 'taken'
23
+ const WITHDRAWN = 'withdrawn'
24
+ // Long enough for a session to answer a `pending` chunk with the next wait, short enough that a session
25
+ // which ended delays its fresh successor by no more than this.
26
+ const SESSION_GAP_MS = 120_000
27
+ const OFFER_POLL_MS = 1000
28
+
29
+ const heartbeat_schema = z.object({ at: z.number() })
30
+ const offer_schema = z.object({ at: z.number(), note: z.string() })
31
+
32
+ function path_of(prefix: string, repository: string, issue: string): string {
33
+ return stamp_file.stamp_path(`${prefix}${issue}${KEY_SEPARATOR}`, repository)
34
+ }
35
+
36
+ function beat(repository: string, issue: string, at: number = Date.now()): void {
37
+ stamp_file.replace_stamp(path_of(HEARTBEAT_PREFIX, repository, issue), { at })
38
+ }
39
+
40
+ function leave(repository: string, issue: string): void {
41
+ stamp_file.remove_stamp(path_of(HEARTBEAT_PREFIX, repository, issue))
42
+ }
43
+
44
+ function is_waited(repository: string, issue: string, now: number = Date.now()): boolean {
45
+ const raw = stamp_file.read_stamp_text(path_of(HEARTBEAT_PREFIX, repository, issue))
46
+ const heartbeat = raw === undefined ? undefined : json_value.parse_with(raw, heartbeat_schema)
47
+
48
+ return heartbeat !== undefined && now - heartbeat.at < SESSION_GAP_MS
49
+ }
50
+
51
+ function decide(repository: string, issue: string, decision: string): boolean {
52
+ return stamp_file.create_stamp(path_of(DECISION_PREFIX, repository, issue), decision)
53
+ }
54
+
55
+ function clear_offer(repository: string, issue: string): void {
56
+ stamp_file.remove_stamp(path_of(HANDOFF_PREFIX, repository, issue))
57
+ stamp_file.remove_stamp(path_of(DECISION_PREFIX, repository, issue))
58
+ }
59
+
60
+ // An offer is never held past the session gap, so an older one was left by a supervisor that died
61
+ // mid-offer and belongs to an earlier attempt, not to the ship this session waits on.
62
+ function offered_note(repository: string, issue: string, now: number): string | undefined {
63
+ const raw = stamp_file.read_stamp_text(path_of(HANDOFF_PREFIX, repository, issue))
64
+ const offered = raw === undefined ? undefined : json_value.parse_with(raw, offer_schema)
65
+
66
+ return offered !== undefined && now - offered.at < SESSION_GAP_MS ? offered.note : undefined
67
+ }
68
+
69
+ // The waiter's side: the stop note, once this call won it; `undefined` where none is offered or the
70
+ // supervisor already withdrew it.
71
+ function take(repository: string, issue: string, now: number = Date.now()): string | undefined {
72
+ const note = offered_note(repository, issue, now)
73
+
74
+ if (note === undefined || !decide(repository, issue, TAKEN)) return undefined
75
+
76
+ stamp_file.remove_stamp(path_of(HANDOFF_PREFIX, repository, issue))
77
+
78
+ return note
79
+ }
80
+
81
+ async function await_taken(repository: string, issue: string): Promise<boolean> {
82
+ const decision = path_of(DECISION_PREFIX, repository, issue)
83
+
84
+ for (let waited = 0; waited < SESSION_GAP_MS; waited += OFFER_POLL_MS) {
85
+ if (stamp_file.read_stamp_text(decision) !== undefined) return true
86
+
87
+ // eslint-disable-next-line no-await-in-loop -- polling: each read waits on the waiter's next poll
88
+ await sleep(OFFER_POLL_MS)
89
+ }
90
+
91
+ return !decide(repository, issue, WITHDRAWN)
92
+ }
93
+
94
+ // The supervisor's side: whether a waiting session took `note`. A session that is not waiting is not
95
+ // offered anything, so a lane whose child ended relaunches with no delay.
96
+ async function offer(repository: string, issue: string, note: string): Promise<boolean> {
97
+ if (!is_waited(repository, issue)) return false
98
+
99
+ clear_offer(repository, issue)
100
+ // Renamed into place whole: a waiter polling between an exclusive create and its write would read an
101
+ // empty note, win the decision on it and drop the stop.
102
+ stamp_file.replace_stamp(path_of(HANDOFF_PREFIX, repository, issue), { at: Date.now(), note })
103
+ const is_taken = await await_taken(repository, issue)
104
+
105
+ // The decision stays until the next offer clears it: a waiter that read the note just before the
106
+ // withdrawal would otherwise win a fresh decision record and answer the stop a second time.
107
+ stamp_file.remove_stamp(path_of(HANDOFF_PREFIX, repository, issue))
108
+
109
+ return is_taken
110
+ }
111
+
112
+ const run_ship_waiter = { SESSION_GAP_MS, beat, is_waited, leave, offer, take }
113
+
114
+ export { run_ship_waiter }
@@ -37,10 +37,23 @@ type ShipSection = Section & {
37
37
  preconditions?: ReadonlyArray<string>
38
38
  // The cause a stopped review round tagged its stop with, recorded with the stop.
39
39
  cause?: Cause
40
+ // The stage found nothing to do, and why it ran or was skipped — both read onto its event line.
41
+ is_skipped?: boolean
42
+ reason?: string
40
43
  }
41
44
 
42
- // A stage's result under its header, carrying the conflicts, the preconditions and the cause only where
43
- // it has them.
45
+ // The skip and the reason a stage's event line carries, only where the stage gave them.
46
+ function mark_of(result: StepResult): Pick<ShipSection, 'is_skipped' | 'reason'> {
47
+ const { is_skipped, reason } = result
48
+
49
+ return {
50
+ ...(is_skipped !== undefined && { is_skipped }),
51
+ ...(reason !== undefined && { reason }),
52
+ }
53
+ }
54
+
55
+ // A stage's result under its header, carrying the conflicts, the preconditions, the cause and the mark
56
+ // only where it has them.
44
57
  function section_of(header: string, result: StepResult): ShipSection {
45
58
  const { conflicts, preconditions, cause } = result
46
59
 
@@ -52,6 +65,7 @@ function section_of(header: string, result: StepResult): ShipSection {
52
65
  ...(conflicts !== undefined && { conflicts }),
53
66
  ...(preconditions !== undefined && { preconditions }),
54
67
  ...(cause !== undefined && { cause }),
68
+ ...mark_of(result),
55
69
  }
56
70
  }
57
71
 
@@ -0,0 +1,48 @@
1
+ import { spawn, type SpawnOptions } from 'node:child_process'
2
+ import { existsSync } from 'node:fs'
3
+
4
+ // **The host stays awake while a supervisor runs** (#5173). `lane-sleep.ts` takes host sleep out of a
5
+ // lane's net time but cannot prevent it, and an idle sleep stops every lane, CI wait and relaunch at
6
+ // once. `caffeinate -w <pid>` holds the assertion for exactly the supervisor's lifetime: a `--stop`, a
7
+ // crash or a reload's exit ends it with no bookkeeping, and the successor of a reload starts its own
8
+ // from this same call, so the hand-off needs no re-attach. `-i` blocks idle sleep, `-s` system sleep —
9
+ // lid-close sleep included, but only on AC power (`docs/how-to/run-backlog.md`).
10
+ const CAFFEINATE_PATH = '/usr/bin/caffeinate'
11
+ const CAFFEINATE_FLAGS = ['-i', '-s', '-w']
12
+
13
+ interface Started {
14
+ on: (event: 'error', listener: () => void) => unknown
15
+ unref: () => unknown
16
+ }
17
+
18
+ type Spawner = (command: string, args: Array<string>, options: SpawnOptions) => Started
19
+
20
+ function argv(pid: number): Array<string> {
21
+ return [...CAFFEINATE_FLAGS, String(pid)]
22
+ }
23
+
24
+ // **A host without `caffeinate` (Linux, CI) runs the supervisor as before** — nothing is started and
25
+ // nothing is printed; a spawn that fails late is swallowed for the same reason.
26
+ function keep_awake(
27
+ pid: number,
28
+ is_present: (path: string) => boolean = existsSync,
29
+ spawner: Spawner = spawn,
30
+ ): void {
31
+ if (!is_present(CAFFEINATE_PATH)) return
32
+
33
+ const child = spawner(CAFFEINATE_PATH, argv(pid), { detached: true, stdio: 'ignore' })
34
+
35
+ child.on('error', () => {
36
+ // the supervisor runs without the assertion, as on a host without caffeinate
37
+ })
38
+ child.unref()
39
+ }
40
+
41
+ const run_wake_caffeinate = {
42
+ CAFFEINATE_PATH,
43
+ argv,
44
+ keep_awake,
45
+ }
46
+
47
+ export type { Spawner, Started }
48
+ export { run_wake_caffeinate }
@@ -4,15 +4,14 @@ import { backlog_budget } from '#scripts/backlog/backlog-budget'
4
4
  import { lane_session_broker } from '#scripts/lane/lane-session-broker'
5
5
  import { cli_flags } from '#scripts/lib/cli-flags'
6
6
  import { MS_PER_SECOND } from '#scripts/lib/timeouts'
7
- import { telegram_notify } from '#scripts/notify/telegram-notify'
8
7
  import { run_carry, type CarryRead } from '#scripts/run/carry/run-carry'
9
8
  import { run_event_stream } from '#scripts/run/event/run-event-stream'
10
9
  import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
11
- import { run_stranded_detect, type WatchedRun } from '#scripts/run/run-stranded-detect'
12
- import { run_wake, type WakeStopReason, type WakeTidyResult } from './run-wake'
10
+ import { run_wake, type WakeTidyResult } from './run-wake'
13
11
  import { run_wake_await } from './run-wake-await'
14
12
  import { run_wake_code } from './run-wake-code'
15
13
  import { run_wake_describe, type WakeContext } from './run-wake-describe'
14
+ import { run_wake_finish } from './run-wake-finish'
16
15
  import { run_wake_loop, type LoopStop } from './run-wake-loop'
17
16
  import { run_wake_ports } from './run-wake-ports'
18
17
  import { run_wake_session, type LaunchResult } from './run-wake-session'
@@ -54,29 +53,6 @@ const NONE_VERDICT = 'none'
54
53
  const UNKNOWN_VERDICT = 'unknown'
55
54
  const FAILED_VERDICT = 'failed'
56
55
 
57
- // The command rather than one entry point: the supervisor continues a `queue` as readily as a
58
- // `backlogrun`, and a warning that named the wrong one would send the reader looking at a run that is
59
- // not the one that stopped.
60
- const WARNING_TITLE = 'run:wake supervisor'
61
- const WARNING_RECOVERY = `Check the wake command, then restart with \`pnpm josh run:wake --start\`.`
62
- // **The stop reasons that reach a person, and what each one tells them.** A reason mapped to
63
- // `undefined` is one where nothing went wrong: `ended` is the run finishing through `run:carry --end`,
64
- // `stopped` is a person's own `--stop` or a supervisor being superseded, and a warning channel that
65
- // fired on those is one that stops being read.
66
- //
67
- // **`unreadable` warns**, because it ends the supervisor while a carry record is still sitting there
68
- // handed off, so the run would otherwise be left asleep with nothing anywhere saying so.
69
- // **Every reason is listed, and the silent two are listed as `undefined`.** A partial map would let a
70
- // fifth stop reason compile straight past this and reach nobody, which is the defect class this
71
- // section exists to close.
72
- const STOP_BODIES: Record<WakeStopReason, string | undefined> = {
73
- ended: undefined,
74
- stopped: undefined,
75
- reload: undefined,
76
- failed: 'The supervisor could not continue the run and has stopped.',
77
- unreadable:
78
- 'The carry record could not be read, so the supervisor stopped rather than guess at it.',
79
- }
80
56
  const NO_PROFILE_NOTE = 'the scheduler profile could not be resolved'
81
57
  const RELOAD_FAILED_NOTE =
82
58
  "The supervisor's code changed, but the supervisor to replace it did not start"
@@ -151,87 +127,6 @@ function refuse_without_carry(kind: CarryRead['kind']): number {
151
127
  return report(NONE_VERDICT, FAILURE_EXIT_CODE)
152
128
  }
153
129
 
154
- // The log is named in the warning rather than only in `--list`, because the person reading a warning
155
- // at three in the morning is the one who needs to know where to look next.
156
- function log_hint(context: WakeContext): string {
157
- return `The output of every session this supervisor started is in ${context.log_target}.`
158
- }
159
-
160
- // The log hint goes ahead of the note: the Telegram length limit cuts the tail, and a long failure note
161
- // (stdout tail plus stderr tail) would otherwise take the one line that says where to look next.
162
- function warning_body(stop: LoopStop, body: string, context: WakeContext): string {
163
- return [body, log_hint(context), stop.note].filter(Boolean).join('\n')
164
- }
165
-
166
- // Whether the warning reached a person — the answer `record_failure` leaves its marker on.
167
- async function warn_of_stop(stop: LoopStop, body: string, context: WakeContext): Promise<boolean> {
168
- return await telegram_notify.warn({
169
- issue_title: WARNING_TITLE,
170
- body: warning_body(stop, body, context),
171
- recovery: WARNING_RECOVERY,
172
- })
173
- }
174
-
175
- // **A failed supervisor that leaves the run stranded says so on the stream, with its cause.** Nothing
176
- // else writes there when the supervisor gives up, so a reader of the events would see the last merge
177
- // and a run that looks as if it were still watching. The cause is the note's first line — the
178
- // driver's own `error …` — because the rest is a stderr tail the log already holds.
179
- //
180
- // **Only a strand, and only one a person was told of.** A `failed` stop is not always a strand: a
181
- // `run:carry --end` under a live driver fails it too, and that run ended cleanly. And the event is the
182
- // marker `run_stranded_detect` holds its own notification back on, so it is left only when the warning
183
- // was sent — after a failed send the stop-time detector still finds no marker, and notifies.
184
- function is_strand_told(stop: LoopStop, watched: WatchedRun, is_warned: boolean): boolean {
185
- if (!is_warned || stop.reason !== run_wake_loop.FAILED_REASON) return false
186
-
187
- return run_stranded_detect.is_left_stranded(watched)
188
- }
189
-
190
- async function record_failure(
191
- stop: LoopStop,
192
- watched: WatchedRun,
193
- is_warned: boolean,
194
- ): Promise<void> {
195
- if (!is_strand_told(stop, watched, is_warned)) return
196
-
197
- const [cause = ''] = (stop.note ?? '').split('\n', 1)
198
-
199
- await run_event_stream_emit.emit(
200
- run_event_stream.EVENT_KIND.STRANDED,
201
- `The supervisor failed and stopped watching the run — ${cause}`,
202
- )
203
- }
204
-
205
- // **Only a failed wake exits non-zero; `unreadable` warns but exits zero.** The
206
- // verdict-and-exit-code table in `docs/josh-commands-run.md` → `run:wake` is a contract callers branch on,
207
- // and what that one needs is the notification, not a different exit code.
208
- function exit_code_of(reason: WakeStopReason): number {
209
- return reason === run_wake_loop.FAILED_REASON ? FAILURE_EXIT_CODE : SUCCESS_EXIT_CODE
210
- }
211
-
212
- // A failure that reached nobody is the defect, so the Telegram goes out before the verdict is printed.
213
- // Which stops reach a person, and what each one says — `undefined` for the two where nothing went
214
- // wrong. It is a function rather than a bare lookup so the decision can be asserted for every reason
215
- // the loop can end on.
216
- function stop_body(reason: WakeStopReason): string | undefined {
217
- return STOP_BODIES[reason]
218
- }
219
-
220
- // `started_at` is the run this supervisor took on — what `record_failure` tells an ended run by once
221
- // the carry record that held it no longer reads.
222
- async function finish(stop: LoopStop, context: WakeContext, started_at: string): Promise<number> {
223
- const body = stop_body(stop.reason)
224
-
225
- if (body === undefined) return report(stop.reason)
226
-
227
- console.error(stop.note ?? body)
228
- const is_warned = await warn_of_stop(stop, body, context)
229
-
230
- await record_failure(stop, { ...context, started_at }, is_warned)
231
-
232
- return report(stop.reason, exit_code_of(stop.reason))
233
- }
234
-
235
130
  function launch_supervisor(
236
131
  context: WakeContext,
237
132
  interval: string | undefined,
@@ -375,7 +270,7 @@ async function supervised(
375
270
  profile: AgentProfile,
376
271
  ): Promise<LoopStop> {
377
272
  const source = run_wake_code.source_of(context.worktree, SCRIPT_PATH)
378
- const is_stale = code_check(await run_wake_code.watch(source))
273
+ const is_stale = code_check(await run_wake_code.watch(source, lane_session_broker.take_refusal))
379
274
  const ports = run_wake_ports.ports_for(context, profile, is_stale)
380
275
  const stop = await run_wake_supervise.supervise({
381
276
  context,
@@ -405,9 +300,11 @@ async function loop(context: WakeContext, interval_ms: number): Promise<number>
405
300
  if (claimed.kind === 'running') return report(RUNNING_VERDICT)
406
301
  if (claimed.kind === 'refused') return refuse_without_carry(claimed.carry_kind)
407
302
 
303
+ run_wake_session.keep_awake(process.pid)
304
+
408
305
  const stop = await supervised(context, interval_ms, claimed.wake.profile ?? profile)
409
306
 
410
- return await finish(stop, context, claimed.carry.started_at)
307
+ return await run_wake_finish.finish(stop, context, claimed.carry.started_at)
411
308
  }
412
309
 
413
310
  // **The verdict tracks whether the supervisor is actually running, not merely whether a record is
@@ -467,16 +364,12 @@ const run_wake_cli = {
467
364
  SUPERVISING_VERDICT,
468
365
  UNKNOWN_VERDICT,
469
366
  USAGE,
470
- finish,
471
367
  RELOAD_FAILED_NOTE,
472
- record_failure,
473
368
  reload,
474
369
  restart_supervisor,
475
370
  run,
476
371
  start_for_launch,
477
- stop_body,
478
372
  supervised,
479
- warning_body,
480
373
  }
481
374
 
482
375
  export { run_wake_cli }
@@ -23,31 +23,73 @@ interface CodeSource {
23
23
  interface CodeState {
24
24
  head: string | undefined
25
25
  loaded: string | undefined
26
+ // A suspicion taken while the closure could not be hashed, kept for the next pass.
27
+ is_suspect: boolean
26
28
  }
27
29
 
28
30
  // **A hash that cannot be computed is never stale**: a supervisor that cannot tell keeps running, as
29
- // it did before this check existed, rather than restarting on every pass.
30
- function is_changed(loaded: string | undefined, current: string | undefined): boolean {
31
- if (loaded === undefined || current === undefined) return false
31
+ // it did before this check existed, rather than restarting on every pass. **A suspected supervisor whose
32
+ // start left no baseline is stale** (joshuafolkken/kit#4714): the broker refused a request, and without
33
+ // a baseline nothing else could ever tell it differs, so the successor reads one afresh.
34
+ function is_changed(
35
+ loaded: string | undefined,
36
+ current: string | undefined,
37
+ is_suspected: boolean,
38
+ ): boolean {
39
+ if (current === undefined) return false
40
+ if (loaded === undefined) return is_suspected
32
41
 
33
42
  return loaded !== current
34
43
  }
35
44
 
36
- async function is_stale(state: CodeState, source: CodeSource): Promise<boolean> {
45
+ // **A move and a suspicion are consumed only once they were hashed**, so a closure that could not be
46
+ // read at them is read again at the next pass (joshuafolkken/kit#4714).
47
+ function consume(
48
+ state: CodeState,
49
+ read: { head: string | undefined; is_suspect: boolean },
50
+ current: string | undefined,
51
+ ): void {
52
+ if (current === undefined) {
53
+ state.is_suspect = read.is_suspect
54
+
55
+ return
56
+ }
57
+
58
+ state.head = read.head
59
+ state.is_suspect = false
60
+ }
61
+
62
+ // **A suspicion re-hashes an unmoved commit** (joshuafolkken/kit#4714).
63
+ async function is_stale(
64
+ state: CodeState,
65
+ source: CodeSource,
66
+ is_suspected: () => boolean,
67
+ ): Promise<boolean> {
37
68
  const head = await source.head()
38
- if (head === state.head) return false
69
+ const is_suspect = is_suspected() || state.is_suspect
70
+ if (!is_suspect && head === state.head) return false
71
+
72
+ const current = await source.closure()
39
73
 
40
- state.head = head
74
+ consume(state, { head, is_suspect }, current)
41
75
 
42
- return is_changed(state.loaded, await source.closure())
76
+ return is_changed(state.loaded, current, is_suspect)
43
77
  }
44
78
 
45
79
  // The baseline is read when the watch starts — at the supervisor's start, as close to the moment its
46
- // modules were loaded as a reading can be.
47
- async function watch(source: CodeSource): Promise<() => Promise<boolean>> {
48
- const state: CodeState = { head: await source.head(), loaded: await source.closure() }
80
+ // modules were loaded as a reading can be. `is_suspected` answers, and clears, whether something has
81
+ // seen this code differ from main's since it was last asked (`lane-session-broker.ts`).
82
+ async function watch(
83
+ source: CodeSource,
84
+ is_suspected: () => boolean = () => false,
85
+ ): Promise<() => Promise<boolean>> {
86
+ const state: CodeState = {
87
+ head: await source.head(),
88
+ loaded: await source.closure(),
89
+ is_suspect: false,
90
+ }
49
91
 
50
- return async () => await is_stale(state, source)
92
+ return async () => await is_stale(state, source, is_suspected)
51
93
  }
52
94
 
53
95
  async function read_head(): Promise<string | undefined> {
@@ -1,4 +1,5 @@
1
1
  import { josh_command, type JoshResult } from '#scripts/josh/josh-run'
2
+ import { MS_PER_MINUTE } from '#scripts/lib/timeouts'
2
3
  import { run_carry, type CarryRead } from '#scripts/run/carry/run-carry'
3
4
  import { run_event_setup } from '#scripts/run/event/run-event-setup'
4
5
  import { run_invocation } from '#scripts/run/run-invocation'
@@ -14,7 +15,8 @@ const ERROR_PREFIX = 'error '
14
15
  // **The driver is bounded so the supervisor reaches its pass boundary between dispatches**
15
16
  // (joshuafolkken/kit#4509): unbounded, `backlog:drive` dispatches, collects and merges lanes in its own
16
17
  // loop until a judgment hand-off, and the code check at the pass boundary would wait for that.
17
- const WINDOW_MINUTES = '5'
18
+ const WINDOW_MINUTES = 5
19
+ const WINDOW_MS = WINDOW_MINUTES * MS_PER_MINUTE
18
20
  const WINDOW_VERDICT = 'window'
19
21
 
20
22
  function driver_args(invocation: string): ReadonlyArray<string> | undefined {
@@ -27,7 +29,7 @@ function driver_args(invocation: string): ReadonlyArray<string> | undefined {
27
29
  '--owner',
28
30
  String(process.pid),
29
31
  '--window',
30
- WINDOW_MINUTES,
32
+ String(WINDOW_MINUTES),
31
33
  ...rebuilt.split(' ').slice(1 + named_count),
32
34
  ]
33
35
  }
@@ -222,4 +224,4 @@ async function drive(target: string, wait: Pause = pause): Promise<DriveResult>
222
224
  )
223
225
  }
224
226
 
225
- export const run_wake_driver = { RERUN_LIMIT, drive, driver_args, driver_result }
227
+ export const run_wake_driver = { RERUN_LIMIT, WINDOW_MS, drive, driver_args, driver_result }
@@ -0,0 +1,137 @@
1
+ import { telegram_notify } from '#scripts/notify/telegram-notify'
2
+ import { run_event_stream } from '#scripts/run/event/run-event-stream'
3
+ import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
4
+ import { run_stranded_detect, type WatchedRun } from '#scripts/run/run-stranded-detect'
5
+ import type { WakeStopReason } from './run-wake'
6
+ import type { WakeContext } from './run-wake-describe'
7
+ import { run_wake_loop, type LoopStop } from './run-wake-loop'
8
+
9
+ // How a `run:wake --loop` supervisor ends: which stops reach a person, what the warning says, the
10
+ // `stranded` event it leaves, and the verdict and exit code it ends on. The CLI that runs the loop is
11
+ // `run-wake-cli.ts`.
12
+
13
+ const SUCCESS_EXIT_CODE = 0
14
+ const FAILURE_EXIT_CODE = 1
15
+
16
+ // The command rather than one entry point: the supervisor continues a `queue` as readily as a
17
+ // `backlogrun`, and a warning that named the wrong one would send the reader looking at a run that is
18
+ // not the one that stopped.
19
+ const WARNING_TITLE = 'run:wake supervisor'
20
+ const WARNING_RECOVERY = `Check the wake command, then restart with \`pnpm josh run:wake --start\`.`
21
+ // **The stop reasons that reach a person, and what each one tells them.** A reason mapped to
22
+ // `undefined` is one where nothing went wrong: `ended` is the run finishing through `run:carry --end`,
23
+ // `stopped` is a person's own `--stop` or a supervisor being superseded, and a warning channel that
24
+ // fired on those is one that stops being read.
25
+ //
26
+ // **`unreadable` warns**, because it ends the supervisor while a carry record is still sitting there
27
+ // handed off, so the run would otherwise be left asleep with nothing anywhere saying so.
28
+ // **Every reason is listed, and the silent two are listed as `undefined`.** A partial map would let a
29
+ // fifth stop reason compile straight past this and reach nobody, which is the defect class this
30
+ // section exists to close.
31
+ const STOP_BODIES: Record<WakeStopReason, string | undefined> = {
32
+ ended: undefined,
33
+ stopped: undefined,
34
+ reload: undefined,
35
+ failed: 'The supervisor could not continue the run and has stopped.',
36
+ unreadable:
37
+ 'The carry record could not be read, so the supervisor stopped rather than guess at it.',
38
+ }
39
+
40
+ // The log is named in the warning rather than only in `--list`, because the person reading a warning
41
+ // at three in the morning is the one who needs to know where to look next.
42
+ function log_hint(context: WakeContext): string {
43
+ return `The output of every session this supervisor started is in ${context.log_target}.`
44
+ }
45
+
46
+ // The log hint goes ahead of the note: the Telegram length limit cuts the tail, and a long failure note
47
+ // (stdout tail plus stderr tail) would otherwise take the one line that says where to look next.
48
+ function warning_body(stop: LoopStop, body: string, context: WakeContext): string {
49
+ return [body, log_hint(context), stop.note].filter(Boolean).join('\n')
50
+ }
51
+
52
+ // Whether the warning reached a person — the answer `record_failure` leaves its marker on.
53
+ async function warn_of_stop(stop: LoopStop, body: string, context: WakeContext): Promise<boolean> {
54
+ return await telegram_notify.warn({
55
+ issue_title: WARNING_TITLE,
56
+ body: warning_body(stop, body, context),
57
+ recovery: WARNING_RECOVERY,
58
+ })
59
+ }
60
+
61
+ // **A failed supervisor that leaves the run stranded says so on the stream, with its cause.** Nothing
62
+ // else writes there when the supervisor gives up, so a reader of the events would see the last merge
63
+ // and a run that looks as if it were still watching. The cause is the note's first line — the
64
+ // driver's own `error …` — because the rest is a stderr tail the log already holds.
65
+ //
66
+ // **Only a strand, and only one a person was told of.** A `failed` stop is not always a strand: a
67
+ // `run:carry --end` under a live driver fails it too, and that run ended cleanly. And the event is the
68
+ // marker `run_stranded_detect` holds its own notification back on, so it is left only when the warning
69
+ // was sent — after a failed send the stop-time detector still finds no marker, and notifies.
70
+ function is_strand_told(stop: LoopStop, watched: WatchedRun, is_warned: boolean): boolean {
71
+ if (!is_warned || stop.reason !== run_wake_loop.FAILED_REASON) return false
72
+
73
+ return run_stranded_detect.is_left_stranded(watched)
74
+ }
75
+
76
+ async function record_failure(
77
+ stop: LoopStop,
78
+ watched: WatchedRun,
79
+ is_warned: boolean,
80
+ ): Promise<void> {
81
+ if (!is_strand_told(stop, watched, is_warned)) return
82
+
83
+ const [cause = ''] = (stop.note ?? '').split('\n', 1)
84
+
85
+ await run_event_stream_emit.emit(
86
+ run_event_stream.EVENT_KIND.STRANDED,
87
+ `The supervisor failed and stopped watching the run — ${cause}`,
88
+ )
89
+ }
90
+
91
+ // **Only a failed wake exits non-zero; `unreadable` warns but exits zero.** The
92
+ // verdict-and-exit-code table in `docs/josh-commands-run.md` → `run:wake` is a contract callers branch on,
93
+ // and what that one needs is the notification, not a different exit code.
94
+ function exit_code_of(reason: WakeStopReason): number {
95
+ return reason === run_wake_loop.FAILED_REASON ? FAILURE_EXIT_CODE : SUCCESS_EXIT_CODE
96
+ }
97
+
98
+ // Which stops reach a person, and what each one says — `undefined` for the two where nothing went
99
+ // wrong. It is a function rather than a bare lookup so the decision can be asserted for every reason
100
+ // the loop can end on.
101
+ function stop_body(reason: WakeStopReason): string | undefined {
102
+ return STOP_BODIES[reason]
103
+ }
104
+
105
+ // `started_at` is the run this supervisor took on — what `record_failure` tells an ended run by once
106
+ // the carry record that held it no longer reads.
107
+ async function warn_and_record(
108
+ stop: LoopStop,
109
+ body: string,
110
+ context: WakeContext,
111
+ started_at: string,
112
+ ): Promise<void> {
113
+ console.error(stop.note ?? body)
114
+ const is_warned = await warn_of_stop(stop, body, context)
115
+
116
+ await record_failure(stop, { ...context, started_at }, is_warned)
117
+ }
118
+
119
+ // A failure that reached nobody is the defect, so the Telegram goes out before the verdict is printed.
120
+ async function finish(stop: LoopStop, context: WakeContext, started_at: string): Promise<number> {
121
+ const body = stop_body(stop.reason)
122
+
123
+ if (body !== undefined) await warn_and_record(stop, body, context, started_at)
124
+
125
+ console.info(stop.reason)
126
+
127
+ return exit_code_of(stop.reason)
128
+ }
129
+
130
+ const run_wake_finish = {
131
+ finish,
132
+ record_failure,
133
+ stop_body,
134
+ warning_body,
135
+ }
136
+
137
+ export { run_wake_finish }
@@ -50,7 +50,8 @@ interface LoopPorts {
50
50
  wake: (invocation: string, session_id: string, material: string) => LaunchResult
51
51
  sleep: (milliseconds: number) => Promise<void>
52
52
  now: () => Date
53
- // Closes again a lane a process outlived, once the process is gone (`lane_close.retry_closes`).
53
+ // Closes again a lane a process outlived, once the process is gone (`lane_close.retry_closes`), and
54
+ // withdraws a lane whose issue was closed as not planned (`lane_withdraw_closed.withdraw_closed`).
54
55
  retry_closes: () => Promise<void>
55
56
  // Whether the code this process loaded differs from the code on disk now (`run-wake-code.ts`).
56
57
  is_code_stale: () => Promise<boolean>
@@ -159,8 +160,9 @@ async function decide_pass(wake: RunWake, ports: LoopPorts): Promise<WakeDecisio
159
160
  return run_wake.decide({ ...input, has_work: await ports.has_work(input.read) })
160
161
  }
161
162
 
162
- // A lane whose close a process outlived ends once the process is gone (joshuafolkken/kit#4300). A
163
- // failed retry is the next pass's to try again, never a reason to stop the run.
163
+ // A lane whose close a process outlived ends once the process is gone (joshuafolkken/kit#4300), and
164
+ // a lane whose issue was closed as not planned is withdrawn (joshuafolkken/kit#4915). A failed retry
165
+ // is the next pass's to try again, never a reason to stop the run.
164
166
  async function retry_closes(ports: LoopPorts): Promise<void> {
165
167
  try {
166
168
  await ports.retry_closes()