@joshuafolkken/kit 1.1166.0 → 1.1229.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (437) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/settings.json +2 -2
  3. package/.claude/skills/dependency-update/SKILL.md +4 -8
  4. package/.claude/skills/epic-commands/SKILL.md +2 -2
  5. package/.claude/skills/epic-commands/epic-bundle.md +1 -1
  6. package/.claude/skills/workflow-commands/SKILL.md +2 -4
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +9 -5
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +29 -9
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +7 -2
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +24 -15
  13. package/.claude/skills/workflow-commands/chain-rule.md +1 -1
  14. package/.claude/skills/workflow-commands/delegation.md +2 -2
  15. package/.claude/skills/workflow-commands/followup.md +5 -0
  16. package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
  17. package/.claude/skills/workflow-commands/halfrun.md +1 -1
  18. package/.claude/skills/workflow-commands/issue-scout.md +1 -1
  19. package/.claude/skills/workflow-commands/kickoff.md +1 -1
  20. package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
  21. package/.claude/skills/workflow-commands/pre-gate-cut.md +10 -5
  22. package/.claude/skills/workflow-commands/prerequisite.md +2 -2
  23. package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
  24. package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
  25. package/.codex/hooks.json +2 -2
  26. package/.cursorrules +6 -41
  27. package/.github/actions/setup-node/action.yml +1 -1
  28. package/.github/workflows/auto-tag.yml +3 -4
  29. package/.github/workflows/ci.yml +47 -33
  30. package/.github/workflows/dependabot-auto-merge.yml +13 -6
  31. package/.github/workflows/publish.yml +48 -4
  32. package/.github/workflows/sonar-qube.yml +3 -0
  33. package/.pnpmfile.mjs +4 -3
  34. package/.prettierignore +8 -2
  35. package/CLAUDE.md +18 -20
  36. package/README.md +26 -19
  37. package/SECURITY.md +15 -1
  38. package/dist/CLAUDE.md +18 -20
  39. package/dist/commands/document-section-cli.js +734 -0
  40. package/dist/hooks/audit-provision.js +346 -0
  41. package/dist/hooks/batch-guard.js +5 -4
  42. package/dist/hooks/{chunk-DYAVAM6S.js → chunk-55HHG5PX.js} +1405 -631
  43. package/dist/hooks/{chunk-A6P7PN7P.js → chunk-5VMKCCC4.js} +25 -10
  44. package/dist/hooks/chunk-5VNDSNG3.js +12 -0
  45. package/dist/hooks/{chunk-5KTCMCOT.js → chunk-6HJJXRBX.js} +4 -2
  46. package/dist/hooks/chunk-CDY4NFQG.js +15 -0
  47. package/dist/hooks/chunk-ELCN5LCH.js +135 -0
  48. package/dist/hooks/{chunk-ENA7P5FB.js → chunk-F56MFS4B.js} +3 -3
  49. package/dist/hooks/{chunk-ANJ2WGFD.js → chunk-FIVDHQGT.js} +7 -6
  50. package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
  51. package/dist/hooks/{chunk-GU5MATVE.js → chunk-JUDKDCDP.js} +499 -389
  52. package/dist/hooks/{chunk-ZDZ555YH.js → chunk-NV2JRZZO.js} +743 -909
  53. package/dist/hooks/{chunk-V2BTOZHT.js → chunk-RNWHN5BP.js} +3 -3
  54. package/dist/hooks/{chunk-URJA6XY4.js → chunk-TQNPDZNH.js} +4 -4
  55. package/dist/hooks/{chunk-GYNGP7HI.js → chunk-VC3PZPZO.js} +1 -1
  56. package/dist/hooks/codex-hook-adapter.js +14 -12
  57. package/dist/hooks/duplicate-read-guard.js +6 -5
  58. package/dist/hooks/format-edited.js +4 -3
  59. package/dist/hooks/inputs.json +232 -210
  60. package/dist/hooks/investigation-guard.js +6 -5
  61. package/dist/hooks/pretool-guard.js +12 -10
  62. package/dist/hooks/session-lang.js +2 -4
  63. package/dist/hooks/stop-guard.js +81 -59
  64. package/dist/josh.js +872 -75
  65. package/dist/version/index.d.ts +7 -2
  66. package/dist/version/index.js +152 -75
  67. package/eslint/base.js +23 -1
  68. package/eslint/rules/code-quality.js +3 -0
  69. package/eslint/rules/early-return-one-liner.js +98 -0
  70. package/eslint/rules/unicorn.js +16 -1
  71. package/package.json +7 -6
  72. package/pnpm-workspace.yaml +9 -4
  73. package/prettier/basic.d.ts +3 -1
  74. package/prettier/index.d.ts +3 -1
  75. package/prettier/static.d.ts +3 -1
  76. package/prompts/coding-standards.md +1 -1
  77. package/prompts/collaboration-workflow/file-edits.md +4 -4
  78. package/prompts/collaboration-workflow/glossary.md +1 -1
  79. package/prompts/collaboration-workflow/operating-rules.md +17 -9
  80. package/prompts/collaboration-workflow/overview.md +2 -2
  81. package/prompts/collaboration-workflow/principles.md +14 -12
  82. package/prompts/collaboration-workflow/report-format.md +1 -1
  83. package/prompts/collaboration-workflow/residency.md +1 -1
  84. package/prompts/collaboration-workflow/rule-delivery.md +6 -6
  85. package/prompts/collaboration-workflow/shell-body.md +6 -6
  86. package/prompts/collaboration-workflow/turn-batching.md +1 -1
  87. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
  88. package/prompts/collaboration-workflow/wip-cap.md +10 -10
  89. package/prompts/review-rubric.md +1 -2
  90. package/prompts/review.md +1 -1
  91. package/scripts/agent/agent-headless.ts +29 -0
  92. package/scripts/agent/agent-launch-environment.ts +6 -2
  93. package/scripts/agent/codex-agent-argv.ts +2 -1
  94. package/scripts/auto-ok/auto-ok-cli.ts +1 -1
  95. package/scripts/backlog/backlog-arrival.ts +3 -3
  96. package/scripts/backlog/backlog-budget.ts +1 -1
  97. package/scripts/backlog/backlog-drive-args.ts +135 -0
  98. package/scripts/backlog/backlog-drive-cli.ts +11 -137
  99. package/scripts/backlog/backlog-drive-launch.ts +39 -0
  100. package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
  101. package/scripts/backlog/backlog-drive-named.ts +3 -3
  102. package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
  103. package/scripts/backlog/backlog-drive-owner.ts +1 -1
  104. package/scripts/backlog/backlog-drive-restore.ts +1 -1
  105. package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
  106. package/scripts/backlog/backlog-drive.ts +1 -1
  107. package/scripts/backlog/backlog-next.ts +31 -9
  108. package/scripts/backlog/backlog-offer-cli.ts +6 -6
  109. package/scripts/backlog/backlog-offer.ts +1 -1
  110. package/scripts/backlog/backlog-plan-cli.ts +7 -6
  111. package/scripts/backlog/backlog-pool.ts +57 -11
  112. package/scripts/backlog/backlog-rank.ts +18 -9
  113. package/scripts/backlog/backlog-ready.ts +1 -1
  114. package/scripts/backlog/backlog-restructure.ts +152 -0
  115. package/scripts/backlog/backlog-scope.ts +1 -1
  116. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  117. package/scripts/backlog/backlog-stalled.ts +1 -1
  118. package/scripts/backlog/backlog-waves.ts +15 -17
  119. package/scripts/build/build-codex-hooks.ts +3 -1
  120. package/scripts/build/build-commands.ts +66 -0
  121. package/scripts/build/build-hooks.ts +2 -1
  122. package/scripts/build/build-library.ts +12 -3
  123. package/scripts/build/build.ts +70 -0
  124. package/scripts/build/local-bin.ts +3 -13
  125. package/scripts/cost-runtime/context-cut-threshold.ts +1 -9
  126. package/scripts/cost-runtime/cost-verdict.ts +15 -28
  127. package/scripts/delegation/investigation-reads.ts +3 -1
  128. package/scripts/discovery/repo-origin.ts +5 -5
  129. package/scripts/doctor/doctor-consumer.ts +2 -2
  130. package/scripts/doctor/doctor-io.ts +2 -3
  131. package/scripts/document/capped-print-part.ts +30 -0
  132. package/scripts/document/capped-print.ts +3 -4
  133. package/scripts/document/document-byte-budget.ts +11 -2
  134. package/scripts/document/document-scan.ts +117 -0
  135. package/scripts/document/document-section.ts +143 -16
  136. package/scripts/document/entry-read-budget.ts +5 -3
  137. package/scripts/document/entry-read-set.ts +10 -89
  138. package/scripts/document/read-set-cost.ts +98 -0
  139. package/scripts/epic/epic-add-body.ts +1 -0
  140. package/scripts/epic/epic-audit-checks.ts +1 -1
  141. package/scripts/epic/epic-audit-rationale.ts +4 -3
  142. package/scripts/epic/epic-bundle-cli.ts +14 -3
  143. package/scripts/epic/epic-bundle-poll.ts +41 -0
  144. package/scripts/epic/epic-busy.ts +1 -1
  145. package/scripts/epic/epic-candidate-confirm.ts +3 -2
  146. package/scripts/epic/epic-classify.ts +1 -1
  147. package/scripts/epic/epic-cli-argv.ts +78 -46
  148. package/scripts/epic/epic-cli-remove.ts +11 -19
  149. package/scripts/epic/epic-cli.ts +74 -73
  150. package/scripts/epic/epic-close-comment.ts +1 -1
  151. package/scripts/epic/epic-close.ts +1 -1
  152. package/scripts/epic/epic-cross-repo.ts +5 -5
  153. package/scripts/epic/epic-graph.ts +1 -1
  154. package/scripts/epic/epic-index.ts +1 -1
  155. package/scripts/epic/epic-issue.ts +2 -6
  156. package/scripts/epic/epic-lane-offer.ts +2 -2
  157. package/scripts/epic/epic-next.ts +29 -42
  158. package/scripts/epic/epic-solo-stale.ts +1 -1
  159. package/scripts/epic/epic-triage.ts +8 -4
  160. package/scripts/epic/epic-validate.ts +1 -1
  161. package/scripts/followup/git-followup-finish.ts +2 -2
  162. package/scripts/followup/git-followup-label.ts +2 -2
  163. package/scripts/followup/git-followup-merged.ts +2 -2
  164. package/scripts/followup/git-followup-stages.ts +9 -0
  165. package/scripts/followup/git-pr-followup-checks.ts +142 -0
  166. package/scripts/followup/git-pr-followup.ts +5 -130
  167. package/scripts/gate/core-budget.ts +69 -1
  168. package/scripts/gate/gate-plan.ts +47 -7
  169. package/scripts/gate/gate-skip.ts +38 -7
  170. package/scripts/gate/hook-gate-reuse.ts +12 -10
  171. package/scripts/gate/pre-commit-type-check.ts +4 -1
  172. package/scripts/gate/project-checks.ts +6 -9
  173. package/scripts/gate/type-check-step.ts +2 -3
  174. package/scripts/gate/verification-gate.ts +27 -8
  175. package/scripts/gh/git-gh-api-path.ts +2 -2
  176. package/scripts/gh/git-gh-issue-rest.ts +3 -3
  177. package/scripts/gh/git-gh-issue-write.ts +53 -7
  178. package/scripts/gh/git-gh-issue.ts +5 -3
  179. package/scripts/gh/git-gh-pr-read.ts +68 -19
  180. package/scripts/gh/git-gh-pr-rest.ts +1 -1
  181. package/scripts/gh/git-gh-pr.ts +5 -1
  182. package/scripts/gh/git-pr-ai-review.ts +1 -1
  183. package/scripts/gh/git-pr-checks-eval.ts +9 -3
  184. package/scripts/gh/git-pr-checks-parse.ts +1 -1
  185. package/scripts/gh/git-pr-checks-watch.ts +6 -5
  186. package/scripts/gh/git-pr-checks.ts +18 -6
  187. package/scripts/gh/git-pr-coderabbit.ts +1 -1
  188. package/scripts/gh/git-pr-error.ts +1 -3
  189. package/scripts/gh/git-pr.ts +9 -13
  190. package/scripts/gh/github-issue-url.ts +2 -2
  191. package/scripts/git/constants.ts +12 -6
  192. package/scripts/git/git-command.ts +20 -218
  193. package/scripts/git/git-diff-reads.ts +225 -0
  194. package/scripts/git/git-error.ts +4 -12
  195. package/scripts/git/git-pre-push-hook.ts +139 -0
  196. package/scripts/git/git-prompt.ts +2 -6
  197. package/scripts/git/{schemas.ts → git-schemas.ts} +2 -0
  198. package/scripts/git/git-spawn-sync.ts +3 -1
  199. package/scripts/git/git-spawn.ts +1 -1
  200. package/scripts/git/git-staging.ts +4 -6
  201. package/scripts/git/git-status.ts +3 -9
  202. package/scripts/git/main-merge.ts +57 -21
  203. package/scripts/hooks/codex-hook-adapter.ts +1 -1
  204. package/scripts/hooks/edited-cspell.ts +2 -4
  205. package/scripts/hooks/format-edited-file.ts +3 -12
  206. package/scripts/hooks/pre-push-unit.ts +31 -9
  207. package/scripts/hooks/reserved-run.ts +2 -2
  208. package/scripts/hooks/run-hook.sh +5 -4
  209. package/scripts/hooks/stop-guard.ts +5 -3
  210. package/scripts/init/init-actions.ts +0 -5
  211. package/scripts/init/init-ai-copy.ts +13 -2
  212. package/scripts/init/init-logic-json-merge.ts +19 -0
  213. package/scripts/init/init-logic-secretlint.ts +7 -7
  214. package/scripts/init/init-logic-workspace.ts +41 -2
  215. package/scripts/init/init-logic.ts +16 -26
  216. package/scripts/init/init.ts +5 -15
  217. package/scripts/init/initial-commit.ts +4 -4
  218. package/scripts/init/kit-development-versions.ts +40 -0
  219. package/scripts/init/kit-setup-state.ts +2 -2
  220. package/scripts/init/start-exec.ts +68 -19
  221. package/scripts/init/start-setup-pr.ts +9 -9
  222. package/scripts/init/start-steps.ts +6 -10
  223. package/scripts/issue/defect-rate-cli.ts +1 -1
  224. package/scripts/issue/git-next-issues.ts +1 -1
  225. package/scripts/issue/issue-auto-ok.ts +1 -1
  226. package/scripts/issue/issue-backlinks-cli.ts +2 -2
  227. package/scripts/issue/issue-backlinks.ts +9 -9
  228. package/scripts/issue/issue-comment-cli.ts +19 -13
  229. package/scripts/issue/issue-file-cli.ts +45 -18
  230. package/scripts/issue/issue-file.ts +24 -1
  231. package/scripts/issue/issue-scout-closed.ts +1 -1
  232. package/scripts/issue/issue-state-cli.ts +30 -74
  233. package/scripts/josh/josh-command-map.ts +2 -0
  234. package/scripts/josh/josh-command-types.ts +11 -0
  235. package/scripts/josh/josh-commands-ai.ts +4 -4
  236. package/scripts/josh/josh-commands-backlog.ts +4 -1
  237. package/scripts/josh/josh-commands-development.ts +11 -1
  238. package/scripts/josh/josh-commands-document.ts +1 -0
  239. package/scripts/josh/josh-commands-lane.ts +22 -0
  240. package/scripts/josh/josh-commands-maintenance.ts +4 -4
  241. package/scripts/josh/josh-commands-run.ts +12 -7
  242. package/scripts/josh/josh-in-process.ts +28 -9
  243. package/scripts/josh/josh-logic.ts +33 -15
  244. package/scripts/josh/process-identity.ts +1 -3
  245. package/scripts/josh/session-language-cli.ts +3 -4
  246. package/scripts/lane/lane-await.ts +5 -1
  247. package/scripts/lane/lane-child-invocation.ts +2 -2
  248. package/scripts/lane/lane-cli.ts +3 -3
  249. package/scripts/lane/lane-dispatch.ts +14 -10
  250. package/scripts/lane/lane-environment.ts +5 -1
  251. package/scripts/lane/lane-handoff.ts +1 -1
  252. package/scripts/lane/lane-install.ts +2 -13
  253. package/scripts/lane/lane-launch-cli.ts +35 -3
  254. package/scripts/lane/lane-ledger.ts +145 -0
  255. package/scripts/lane/lane-leftover.ts +1 -1
  256. package/scripts/lane/lane-load.ts +108 -0
  257. package/scripts/lane/lane-measure-cli.ts +149 -0
  258. package/scripts/lane/lane-occupancy.ts +3 -0
  259. package/scripts/lane/lane-reap.ts +1 -3
  260. package/scripts/lane/lane-relaunch.ts +1 -1
  261. package/scripts/lane/lane-stats.ts +224 -0
  262. package/scripts/lane/lane-vacant.ts +36 -0
  263. package/scripts/lane/openai-lane-ship.ts +2 -2
  264. package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
  265. package/scripts/lane/openai-lane-supervisor.ts +7 -165
  266. package/scripts/lane/openai-review-broker.ts +2 -4
  267. package/scripts/lib/ancestor-directories.ts +35 -0
  268. package/scripts/lib/animation-helpers.ts +2 -2
  269. package/scripts/lib/buffered-process.ts +3 -7
  270. package/scripts/lib/cli-flags.ts +71 -1
  271. package/scripts/lib/spawn-exit.ts +2 -2
  272. package/scripts/lib/timeouts.ts +35 -2
  273. package/scripts/lines/line-budget.ts +4 -6
  274. package/scripts/notify/telegram-notify.ts +26 -1
  275. package/scripts/notify/telegram-test.ts +6 -0
  276. package/scripts/observations/observation-ledger-line.ts +6 -6
  277. package/scripts/observations/observation-ledger-migrate.ts +55 -4
  278. package/scripts/observations/observation-ledger.ts +26 -7
  279. package/scripts/overrides/lockfile-overrides.ts +1 -1
  280. package/scripts/overrides/overrides-logic.ts +1 -1
  281. package/scripts/propagate/propagate-git.ts +1 -1
  282. package/scripts/propagate/propagate-publish.ts +2 -2
  283. package/scripts/propagate/propagate-steps.ts +20 -15
  284. package/scripts/propagate/propagate.ts +36 -27
  285. package/scripts/refactor/import-graph.ts +18 -1
  286. package/scripts/refactor/refactor-lint.ts +4 -4
  287. package/scripts/repo/repository-labels.ts +2 -2
  288. package/scripts/repo/required-checks-logic.ts +1 -3
  289. package/scripts/review/live-evidence.ts +2 -4
  290. package/scripts/review/review-brief-cli.ts +1 -1
  291. package/scripts/review/review-finding-ledger.ts +1 -1
  292. package/scripts/review/review-findings-cli.ts +1 -3
  293. package/scripts/rules/decision-oracle-batch.ts +128 -0
  294. package/scripts/rules/decision-oracle-tokens.ts +21 -0
  295. package/scripts/rules/decision-oracle.ts +8 -120
  296. package/scripts/rules/delivered-rules.ts +26 -182
  297. package/scripts/rules/direct-filing.ts +1 -1
  298. package/scripts/rules/early-heartbeat.ts +3 -3
  299. package/scripts/rules/git-argv.ts +1 -1
  300. package/scripts/rules/git-force.ts +1 -1
  301. package/scripts/rules/implementation-cut.ts +2 -2
  302. package/scripts/rules/index-guard.ts +1 -1
  303. package/scripts/rules/issue-comments.ts +118 -0
  304. package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
  305. package/scripts/rules/lane-background.ts +2 -2
  306. package/scripts/rules/lane-park.ts +18 -0
  307. package/scripts/rules/lane-split-park.ts +128 -0
  308. package/scripts/rules/oracle-firing.ts +1 -1
  309. package/scripts/rules/poll-loop.ts +1 -1
  310. package/scripts/rules/pre-gate-cut.ts +3 -3
  311. package/scripts/rules/protected-files.ts +59 -4
  312. package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
  313. package/scripts/rules/shell-body-trigger.ts +1 -1
  314. package/scripts/rules/stop-rules.ts +18 -7
  315. package/scripts/rules/test-declared-commit.ts +4 -4
  316. package/scripts/rules/wip-cap.ts +63 -0
  317. package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
  318. package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
  319. package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
  320. package/scripts/run/{run-carry.ts → carry/run-carry.ts} +12 -6
  321. package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
  322. package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +3 -3
  323. package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +2 -2
  324. package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
  325. package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
  326. package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
  327. package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
  328. package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
  329. package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +16 -2
  330. package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
  331. package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
  332. package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
  333. package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
  334. package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
  335. package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
  336. package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
  337. package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
  338. package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
  339. package/scripts/run/run-ending.ts +2 -2
  340. package/scripts/run/run-halfrun-resume.ts +6 -1
  341. package/scripts/run/run-headless.ts +1 -1
  342. package/scripts/run/run-invocation.ts +85 -84
  343. package/scripts/run/run-liveness.ts +1 -1
  344. package/scripts/run/run-parent-cut-hook.ts +1 -1
  345. package/scripts/run/run-preflight.ts +6 -6
  346. package/scripts/run/run-prep-cli.ts +1 -1
  347. package/scripts/run/run-prrun-resume.ts +1 -1
  348. package/scripts/run/run-report-cli.ts +4 -4
  349. package/scripts/run/run-report.ts +2 -2
  350. package/scripts/run/run-status-cli.ts +1 -1
  351. package/scripts/run/run-step-cli.ts +3 -3
  352. package/scripts/run/run-step.ts +2 -2
  353. package/scripts/run/run-stop-notify.ts +1 -1
  354. package/scripts/run/run-stranded-detect.ts +4 -4
  355. package/scripts/run/run-stranded.ts +2 -2
  356. package/scripts/run/run-watcher-guard-cli.ts +1 -1
  357. package/scripts/run/run-watcher-guard.ts +5 -3
  358. package/scripts/run/run-watcher-hook.ts +1 -1
  359. package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +29 -8
  360. package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
  361. package/scripts/run/ship/run-ship-lock.ts +50 -0
  362. package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +13 -0
  363. package/scripts/run/ship/run-ship-pre-detach.ts +66 -0
  364. package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
  365. package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
  366. package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +10 -6
  367. package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
  368. package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
  369. package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +13 -0
  370. package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +15 -8
  371. package/scripts/run/ship/run-ship-stop-text.ts +13 -0
  372. package/scripts/run/ship/run-ship-sync.ts +128 -0
  373. package/scripts/run/{run-ship.ts → ship/run-ship.ts} +6 -0
  374. package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
  375. package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +31 -9
  376. package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +3 -3
  377. package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +99 -17
  378. package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
  379. package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +8 -2
  380. package/scripts/run/wake/run-wake-restart.ts +58 -0
  381. package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
  382. package/scripts/run/{run-wake.ts → wake/run-wake.ts} +1 -1
  383. package/scripts/safe-chain/preinstall-command.ts +20 -21
  384. package/scripts/safe-chain/project-config.ts +19 -15
  385. package/scripts/sync/sync-ai-files.ts +1 -1
  386. package/scripts/sync/sync-configs.ts +2 -1
  387. package/scripts/sync/sync.ts +15 -2
  388. package/scripts/test/test-red-logic.ts +17 -2
  389. package/scripts/test/test-red.ts +8 -7
  390. package/scripts/test/unit-worker-share.ts +1 -0
  391. package/scripts/time-runtime/time-background.ts +5 -1
  392. package/scripts/time-runtime/time-instant.ts +3 -1
  393. package/scripts/time-runtime/time-spans.ts +6 -139
  394. package/scripts/time-runtime/time-tool-call.ts +160 -0
  395. package/scripts/time-runtime/time-transcript-line.ts +27 -1
  396. package/scripts/version/effective-upstream.ts +2 -20
  397. package/scripts/version/latest-corepack.ts +17 -17
  398. package/scripts/version/latest-regression.ts +1 -1
  399. package/scripts/version/latest-update.ts +4 -4
  400. package/scripts/version/npm-registry.ts +20 -5
  401. package/scripts/version/publishable-range-check.ts +2 -2
  402. package/scripts/version/publishable-range.ts +1 -1
  403. package/scripts/version/release-age.ts +48 -38
  404. package/scripts/version/release-hold.ts +1 -1
  405. package/scripts/version/safe-chain-version-update.ts +40 -0
  406. package/scripts/version/upgrade-shell-command.ts +48 -8
  407. package/scripts/version/version-check-logic.ts +23 -14
  408. package/scripts/version/version-commands.ts +23 -11
  409. package/scripts/version/version-remote.ts +28 -13
  410. package/templates/pnpm-workspace.basic.yaml +4 -0
  411. package/templates/workflows/dependabot-auto-merge.yml +5 -5
  412. package/templates/workflows/github-release.yml +2 -0
  413. package/scripts/build/build-config-merge.ts +0 -27
  414. package/scripts/build/build-managed-marker.ts +0 -27
  415. package/scripts/build/build-self-sync-guard.ts +0 -27
  416. package/scripts/build/build-version.ts +0 -26
  417. package/scripts/version/preinstall-version-update.ts +0 -86
  418. /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
  419. /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
  420. /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
  421. /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
  422. /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
  423. /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
  424. /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
  425. /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
  426. /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
  427. /package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +0 -0
  428. /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
  429. /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
  430. /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
  431. /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
  432. /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
  433. /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
  434. /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
  435. /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
  436. /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
  437. /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
@@ -0,0 +1,63 @@
1
+ import { bash_triggers } from './bash-triggers'
2
+ import { shell_segments } from './shell-segments'
3
+
4
+ // The trigger and the delivered text behind the `wip-cap` row of `delivered-rules.ts`, moved out of
5
+ // the enumeration so the list stays a list (joshuafolkken/kit#3264).
6
+
7
+ // The whole of the WIP cap, in the shape a refusal can carry: the count, the refusal, the two
8
+ // exemptions and the three tests that decide the second one. The three tests are spelled out rather
9
+ // than named, because a delivery that said only "an interrupt is exempt" would hand the deciding back
10
+ // to judgement at exactly the moment nothing else is open to read (joshuafolkken/kit#1518).
11
+ // `WIP_CAP` is the number's single source; `wip-cap.md` states it once and a test pins the two equal.
12
+ const WIP_CAP = 30
13
+ const WIP_CAP_REASON =
14
+ `⛔ backlog WIP cap: \`pnpm josh issue:file\` counts the target repository's open Issues before filing. With more than ${String(WIP_CAP)} ` +
15
+ 'open, close one first; nothing honestly closable means do not file — the command holds such a filing. ' +
16
+ 'Two filings are exempt and proceed while stating the overage — one the run is blocked by (`--over-cap`, ' +
17
+ 'or the `tier-a` / `split` route), and an interrupt (`--route interrupt`), decided by three tests rather ' +
18
+ 'than judgement: a verification answers wrongly, a documented workflow cannot complete, or data is lost ' +
19
+ 'or written outside the repository. Meeting none of the three, the finding is discretionary and waits. ' +
20
+ 'Both procedures are in `prompts/collaboration-workflow/wip-cap.md`. Reissue this call and let the ' +
21
+ 'command count — it fires once per run and cannot repeat on the call in hand.'
22
+
23
+ // **Keeping the WIP cap is counting the open Issues**, which is the one act the rule asks for before
24
+ // a filing.
25
+ //
26
+ // **It has to be Issues, open, and a listing — all three, in one segment.** A pattern that took any
27
+ // of them alone scored `gh pr list --state open` and `epic:bundle`'s candidate search as the count,
28
+ // and those inflate exactly the ratio the retirement decision reads. `gh issue list` alone is not
29
+ // enough either: the cap is about *open* Issues, and a listing that does not say so is some other
30
+ // question. Segment-wise like the comments predicate, so a spelling quoted inside a filing's body
31
+ // is not read as the count that filing skipped.
32
+ const ISSUE_LISTING = /^gh\s+(?:-{1,2}[\w-]+(?:[= ]\S+)?\s+)*issue\s+list\b/u
33
+ const ISSUES_QUERY = /repos\/[^\s'"]*\/issues\?[^\s'"]*state=open/u
34
+ const OPEN_STATE = /--state[= ]open|state=open/u
35
+ // **A label filter makes it a different question.** `gh issue list --label epic --state open` and
36
+ // `…/issues?labels=epic&state=open` ask which epics are open, which is what `epic:bundle` and the
37
+ // Issue template do; counting either as the WIP count would credit the cap as kept by a run that
38
+ // never counted the backlog. The residual the pattern cannot separate is named in
39
+ // `docs/josh-commands.md`: the inventory command in the `diag` skill is byte-identical to a hand count
40
+ // of the backlog, so no pattern can tell those two apart.
41
+ const LABEL_FILTER = /--label\b|[?&]labels=/u
42
+
43
+ function counts_open_issues(command: string): boolean {
44
+ return shell_segments.segments_of(command).some((segment) => {
45
+ if (LABEL_FILTER.test(segment)) return false
46
+
47
+ return (ISSUE_LISTING.test(segment) && OPEN_STATE.test(segment)) || ISSUES_QUERY.test(segment)
48
+ })
49
+ }
50
+
51
+ // The enumeration row itself, so `delivered-rules.ts` spreads one entry rather than restating the
52
+ // trigger and reason it already single-sources here. Once per run: the refusal changes what the run
53
+ // knows, so the reissue is let through.
54
+ const ROW = {
55
+ id: 'wip-cap',
56
+ is_trigger: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
57
+ reason: WIP_CAP_REASON,
58
+ keeps: bash_triggers.on_bash_command(counts_open_issues),
59
+ }
60
+
61
+ const wip_cap = { ROW, WIP_CAP, WIP_CAP_REASON }
62
+
63
+ export { wip_cap }
@@ -1,6 +1,6 @@
1
1
  import { cli_flags } from '#scripts/lib/cli-flags'
2
+ import { run_issue_number } from '#scripts/run/run-issue-number'
2
3
  import { run_carry, type CarryChange, type CarryClaimRequest, type CarryOwner } from './run-carry'
3
- import { run_issue_number } from './run-issue-number'
4
4
 
5
5
  // The argument half of `josh run:carry`. It sat inside `run-carry-cli.ts` until the ownership answers
6
6
  // joshuafolkken/kit#1722 added took that file to 96% of the 300-line limit, and the seam it split on
@@ -9,6 +9,7 @@ import { run_issue_number } from './run-issue-number'
9
9
 
10
10
  const NO_INCREMENT = 0
11
11
  const ONE_CUT = 1
12
+ const ONE_MERGE = 1
12
13
  // The four request groups below are mutually exclusive, so more than one of them named in a single
13
14
  // invocation is a usage error rather than an order to guess.
14
15
  const ONE_GROUP = 1
@@ -22,7 +23,7 @@ const MIN_PID = 1
22
23
  // cannot advance a budget that is no longer its own. `--end` alone accepts it and ignores it, and a
23
24
  // usage line that offered it there would be promising an ownership check nothing performs.
24
25
  const USAGE =
25
- 'Usage: josh run:carry [--json] | --begin <invocation> [--owner <pid>] | --resume <invocation> [--owner <pid>] | (--cut | --merged <count> | --filed <count> | --done <issue> | --retrospective --summary <text>) [--owner <pid>] | --end [--stopped <reason>]'
26
+ 'Usage: josh run:carry [--json] | --begin <invocation> [--owner <pid>] | --resume <invocation> [--owner <pid>] | (--cut | --merged <issue> | --filed <count> | --done <issue> | --retrospective --summary <text>) [--owner <pid>] | --end [--stopped <reason>]'
26
27
 
27
28
  const OPTIONS = {
28
29
  begin: { type: 'string' },
@@ -89,12 +90,12 @@ function to_count(value: OptionValue): number | undefined {
89
90
  return COUNT_PATTERN.test(text) ? Number(text) : undefined
90
91
  }
91
92
 
92
- // **`--done` names an issue, so it is checked as one and not as a count.** The shape is
93
- // `run-issue-number.ts`'s, which already refuses `0` and a leading zero — the same rule the `run:*`
93
+ // **`--done` and `--merged` name an issue, so each is checked as one and not as a count.** The shape
94
+ // is `run-issue-number.ts`'s, which already refuses `0` and a leading zero — the same rule the `run:*`
94
95
  // commands interpolate a number under, imported rather than restated. Absent is an empty object rather
95
96
  // than a zero: there is no issue to record, which is a different fact from recording issue zero. A
96
- // present-but-malformed value invalidates the whole invocation, exactly as a bad `--merged` does.
97
- function to_done(value: OptionValue): Pick<CarryChange, 'done'> | undefined {
97
+ // present-but-malformed value is `undefined`, which invalidates the whole invocation.
98
+ function to_issue(value: OptionValue): { issue?: number } | undefined {
98
99
  const text = cli_flags.string_of(value)
99
100
 
100
101
  if (text === undefined) return {}
@@ -106,7 +107,30 @@ function to_done(value: OptionValue): Pick<CarryChange, 'done'> | undefined {
106
107
  // The pattern is anchored on digits and bounds nothing, so the magnitude check is separate — the
107
108
  // same shape `backlog_budget_cli.to_count` ends with. A number past the safe range would be
108
109
  // recorded as a different number and never match anything in `remaining`.
109
- return Number.isSafeInteger(issue) ? { done: issue } : undefined
110
+ return Number.isSafeInteger(issue) ? { issue } : undefined
111
+ }
112
+
113
+ function to_done(value: OptionValue): Pick<CarryChange, 'done'> | undefined {
114
+ const parsed = to_issue(value)
115
+
116
+ if (parsed === undefined) return undefined
117
+
118
+ return parsed.issue === undefined ? {} : { done: parsed.issue }
119
+ }
120
+
121
+ // **`--merged` names the issue that merged, never a count** (joshuafolkken/kit#3296). A bare count
122
+ // could not be matched against `merged_issues`, so a parent that counted a merge by hand and the
123
+ // driver's `run:merge` for the same issue added two for one merge. Naming the issue makes the hand
124
+ // count the same change `run:merge` applies — one merge plus its number — so `apply_change`'s
125
+ // duplicate check holds whichever of the two arrives first.
126
+ function to_merged(value: OptionValue): Pick<CarryChange, 'merged' | 'merged_issue'> | undefined {
127
+ const parsed = to_issue(value)
128
+
129
+ if (parsed === undefined) return undefined
130
+
131
+ return parsed.issue === undefined
132
+ ? { merged: NO_INCREMENT }
133
+ : { merged: ONE_MERGE, merged_issue: parsed.issue }
110
134
  }
111
135
 
112
136
  // The two boolean marks in the counting group — `--cut` and `--retrospective` — as the partial the
@@ -119,18 +143,18 @@ function to_marks(values: ParsedValues): Pick<CarryChange, 'cuts' | 'retrospecti
119
143
  }
120
144
 
121
145
  function to_change(values: ParsedValues): CarryChange | undefined {
122
- const merged = to_count(values.merged)
146
+ const merged = to_merged(values.merged)
123
147
  const filed = to_count(values.filed)
124
148
  const done = to_done(values.done)
125
149
 
126
150
  if (merged === undefined || filed === undefined || done === undefined) return undefined
127
151
 
128
- return { merged, filed, ...to_marks(values), ...done }
152
+ return { ...merged, filed, ...to_marks(values), ...done }
129
153
  }
130
154
 
131
- // **A counting flag is what makes a count, never the sum of one.** `--merged 0` is a run reporting
132
- // that a wave merged nothing, and reading it as a bare read would answer `none` with exit 0 against
133
- // a record that is not there — the silent zero `docs/josh-commands.md` says exits 1.
155
+ // **A counting flag is what makes a count, never the sum of one.** `--filed 0` is a run reporting
156
+ // that it filed nothing, and reading it as a bare read would answer `none` with exit 0 against a
157
+ // record that is not there — the silent zero `docs/josh-commands.md` says exits 1.
134
158
  function has_count(values: ParsedValues): boolean {
135
159
  return (
136
160
  values.cut === true ||
@@ -189,7 +213,7 @@ function is_summary_paired(values: ParsedValues): boolean {
189
213
 
190
214
  // The counting group, split out so the shape below stays a flat list of exits. The summary rides along
191
215
  // only when it is there — guaranteed paired with `--retrospective` by `is_summary_paired` — so a plain
192
- // `--merged` count never carries one.
216
+ // `--merged` never carries one.
193
217
  function to_count_request(values: ParsedValues, owner: CarryOwner): Request | undefined {
194
218
  const change = to_change(values)
195
219
 
@@ -18,7 +18,9 @@ function next_merged_issues(
18
18
  ): ReadonlyArray<number> | undefined {
19
19
  if (issue === undefined) return carry.merged_issues
20
20
 
21
- return [...(carry.merged_issues ?? []), issue]
21
+ const current = carry.merged_issues ?? []
22
+
23
+ return current.includes(issue) ? current : [...current, issue]
22
24
  }
23
25
 
24
26
  function is_duplicate_merge(carry: RunCarry, change: CarryChange): boolean {
@@ -27,6 +29,24 @@ function is_duplicate_merge(carry: RunCarry, change: CarryChange): boolean {
27
29
  return issue !== undefined && carry.merged_issues?.includes(issue) === true
28
30
  }
29
31
 
32
+ // Whether a change carries anything besides its merge — a filing, a cut, a streak step, a done issue
33
+ // or the retrospective mark.
34
+ function has_other_change(change: CarryChange): boolean {
35
+ const amounts = [change.filed, change.cuts, change.failures, change.outages]
36
+ const has_amount = amounts.some((amount) => (amount ?? NO_INCREMENT) > NO_INCREMENT)
37
+
38
+ return has_amount || change.done !== undefined || change.retrospective === true
39
+ }
40
+
41
+ // **A duplicate merge drops only the merge** (joshuafolkken/kit#3296): `--merged <issue>` may ride
42
+ // beside `--filed` or `--done` in one call, and those are still increments the record has not seen.
43
+ // Only a duplicate carrying nothing else leaves the record untouched, which is `undefined` here.
44
+ function effective_change(carry: RunCarry, change: CarryChange): CarryChange | undefined {
45
+ if (!is_duplicate_merge(carry, change)) return change
46
+
47
+ return has_other_change(change) ? { ...change, merged: NO_INCREMENT } : undefined
48
+ }
49
+
30
50
  function next_counts(
31
51
  carry: RunCarry,
32
52
  change: CarryChange,
@@ -47,18 +67,20 @@ function apply_change(
47
67
  change: CarryChange,
48
68
  now: Date = new Date(),
49
69
  ): RunCarry {
50
- if (is_duplicate_merge(carry, change)) return carry
70
+ const effective = effective_change(carry, change)
71
+
72
+ if (effective === undefined) return carry
51
73
 
52
- const streak = run_carry_streak.next_state(carry, change, now)
74
+ const streak = run_carry_streak.next_state(carry, effective, now)
53
75
  const next: RunCarry = {
54
76
  ...carry,
55
- ...next_counts(carry, change),
56
- merged_issues: next_merged_issues(carry, change.merged_issue),
77
+ ...next_counts(carry, effective),
78
+ merged_issues: next_merged_issues(carry, effective.merged_issue),
57
79
  failures: streak.failures,
58
80
  outages: streak.outages,
59
81
  last_outage_at: streak.last_outage_at,
60
- done: next_done(carry, change.done),
61
- retrospective: change.retrospective === true || carry.retrospective,
82
+ done: next_done(carry, effective.done),
83
+ retrospective: effective.retrospective === true || carry.retrospective,
62
84
  }
63
85
 
64
86
  stamp_file.write_stamp(target, next)
@@ -2,6 +2,9 @@
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { cost_cli } from '#scripts/cost-runtime/cost-cli'
4
4
  import { cost_verdict } from '#scripts/cost-runtime/cost-verdict'
5
+ import { run_event_stream } from '#scripts/run/event/run-event-stream'
6
+ import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
7
+ import { run_stop_notify } from '#scripts/run/run-stop-notify'
5
8
  import {
6
9
  run_carry,
7
10
  type CarryClaim,
@@ -12,9 +15,6 @@ import {
12
15
  import { run_carry_args, type CountRequest, type Request } from './run-carry-args'
13
16
  import { run_carry_conversation } from './run-carry-conversation'
14
17
  import { run_carry_stash } from './run-carry-stash'
15
- import { run_event_stream } from './run-event-stream'
16
- import { run_event_stream_emit } from './run-event-stream-emit'
17
- import { run_stop_notify } from './run-stop-notify'
18
18
 
19
19
  // `josh run:carry` — the record that carries one invocation's budget across its own session cuts
20
20
  // (joshuafolkken/kit#1714). A `backlogrun` begins it, counts a merge and a filing into it, marks each
@@ -303,9 +303,7 @@ function blocked_count(
303
303
  request: CountRequest,
304
304
  is_json: boolean,
305
305
  ): number | undefined {
306
- if (run_carry.is_count_refused(carry, request.owner)) {
307
- return report_count_refused(carry, is_json)
308
- }
306
+ if (run_carry.is_count_refused(carry, request.owner)) return report_count_refused(carry, is_json)
309
307
 
310
308
  if (is_cut_count(request) && run_carry.is_at_cut_cap(carry)) {
311
309
  return report_cut_capped(carry, is_json)
@@ -2,10 +2,10 @@ import { backlog_budget } from '#scripts/backlog/backlog-budget'
2
2
  import { git_command } from '#scripts/git/git-command'
3
3
  import { process_identity } from '#scripts/josh/process-identity'
4
4
  import { stamp_file } from '#scripts/josh/stamp-file'
5
+ import { run_invocation } from '#scripts/run/run-invocation'
5
6
  import { z } from 'zod'
6
7
  import { run_carry_change } from './run-carry-change'
7
8
  import { run_carry_conversation } from './run-carry-conversation'
8
- import { run_invocation } from './run-invocation'
9
9
 
10
10
  // joshuafolkken/kit#1714: a `backlogrun` declares a budget — `--max`, `--idle` and the 8-hour
11
11
  // whole-run bound — and then loses all of it at the session cut, because the cut ends the session and
@@ -480,15 +480,20 @@ function busy_message(carry: RunCarry): string {
480
480
  return `A live run holds this budget — ${describe_carry(carry)}, owner pid ${String(carry.owner_pid)} still running. Nothing was established. Wait for that run, or end its record with \`${END_COMMAND}\` once you know it is over.`
481
481
  }
482
482
 
483
+ // The adoption command, shared by the standing refusal and the judgment wake's hand-off.
484
+ // **`--owner "$PPID"` is part of the command, not an optional extra.** An adoption made without it
485
+ // leaves the record declaring no owner, and from then on every second parent reads it as not provably
486
+ // live and is answered `standing` rather than `busy` — the ownership defense degrading silently
487
+ // through the very message that recommends the command.
488
+ function claim_command(invocation: string): string {
489
+ return `${RESUME_COMMAND} "${invocation}" ${OWNER_ARGUMENT}`
490
+ }
491
+
483
492
  // The record's owner is gone and no cut handed it off — the crashed run, and the same command
484
493
  // retyped over it. Both ways out are named, because which one is right is the reader's to say and
485
494
  // not this command's to guess.
486
- // **`--owner "$PPID"` is part of the command it prints, not an optional extra.** An adoption made
487
- // without it leaves the record declaring no owner, and from then on every second parent reads it as
488
- // not provably live and is answered `standing` rather than `busy` — the ownership defense degrading
489
- // silently through the very message that recommends the command.
490
495
  function standing_message(carry: RunCarry): string {
491
- return `A run record is standing here that no cut handed off — ${describe_carry(carry)}. Nothing was established. Carry that budget with \`${RESUME_COMMAND} "${carry.invocation}" ${OWNER_ARGUMENT}\`, or discard it with \`${END_COMMAND}\` and begin again.`
496
+ return `A run record is standing here that no cut handed off — ${describe_carry(carry)}. Nothing was established. Carry that budget with \`${claim_command(carry.invocation)}\`, or discard it with \`${END_COMMAND}\` and begin again.`
492
497
  }
493
498
 
494
499
  // The count refusal reads differently by cause: a record awaiting its successor, or one a successor
@@ -535,6 +540,7 @@ const run_carry = {
535
540
  begin_carry,
536
541
  busy_message,
537
542
  carry_path,
543
+ claim_command,
538
544
  classify,
539
545
  classify_claim,
540
546
  count_refused_message,
@@ -1,5 +1,5 @@
1
1
  import { cli_flags } from '#scripts/lib/cli-flags'
2
- import { run_issue_number } from './run-issue-number'
2
+ import { run_issue_number } from '#scripts/run/run-issue-number'
3
3
 
4
4
  // `josh run:cut` turns its `argv` into one of four requests. The contract mirrors `run-carry-args.ts`:
5
5
  // parsing lives here, acting on the record lives in `run-cut-cli.ts`, and a malformed command line is
@@ -6,12 +6,12 @@ import { git_command } from '#scripts/git/git-command'
6
6
  import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
7
7
  import { lane_relaunch } from '#scripts/lane/lane-relaunch'
8
8
  import { openai_lane_supervisor } from '#scripts/lane/openai-lane-supervisor'
9
+ import { run_event_stream } from '#scripts/run/event/run-event-stream'
10
+ import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
9
11
  import { run_cut, type CutState, type RunCut } from './run-cut'
10
12
  import { run_cut_args, type Request } from './run-cut-args'
11
13
  import { run_cut_handoff } from './run-cut-handoff'
12
14
  import { run_cut_report } from './run-cut-report'
13
- import { run_event_stream } from './run-event-stream'
14
- import { run_event_stream_emit } from './run-event-stream-emit'
15
15
 
16
16
  // `josh run:cut` — the record that lets a lane child end its process before the gate and a fresh one
17
17
  // resume from it (joshuafolkken/kit#1839). `run:cut <N>` writes the record and relaunches a fresh
@@ -60,7 +60,7 @@ const {
60
60
  } = run_cut_report
61
61
 
62
62
  // **The pre-gate cut is conditional on the same statistic the implementation-phase cut reads**
63
- // (joshuafolkken/kit#2312). `cost_cli.session_verdict` prices the recent `RECENT_REQUEST_WINDOW`
63
+ // (joshuafolkken/kit#2312). `cost_cli.session_verdict` prices the newest request
64
64
  // against `CONTEXT_CUT_THRESHOLD`, so no second threshold is introduced — an `under` session skips the
65
65
  // cut, and an unmeasurable one keeps the old unconditional cut as the safety net joshuafolkken/kit#1933
66
66
  // relies on. Only the pre-gate phase is checked here; the implementation-phase caller gates its own
@@ -14,7 +14,7 @@ const RESUME_VERDICT = 'resume'
14
14
  // re-reading the record.
15
15
  const RESUME_IMPL_VERDICT = 'resume-impl'
16
16
  const FRESH_VERDICT = 'fresh'
17
- // The recent-window context is under the shared cut threshold, so the pre-gate cut was not worth its
17
+ // The current context is under the shared cut threshold, so the pre-gate cut was not worth its
18
18
  // resume and nothing was cut (joshuafolkken/kit#2312). A benign, non-failing answer: the run carries
19
19
  // on to the gate itself, exactly as `not-a-lane` does.
20
20
  const UNDER_THRESHOLD_VERDICT = 'under-threshold'
@@ -111,7 +111,7 @@ function refuse_unready(state: CutState): number {
111
111
  // relaunch costs more than it saves; the current process carries the run on to the gate uncut.
112
112
  function report_under_threshold(): number {
113
113
  console.error(
114
- 'The recent-window context is under the shared cut threshold, so nothing was cut; a resume would cost more than the accumulation it would drop. Continue to the gate.',
114
+ 'The current context is under the shared cut threshold, so nothing was cut; a resume would cost more than the accumulation it would drop. Continue to the gate.',
115
115
  )
116
116
 
117
117
  return report(UNDER_THRESHOLD_VERDICT, SUCCESS_EXIT_CODE)
@@ -5,9 +5,9 @@ import { git_command } from '#scripts/git/git-command'
5
5
  import { git_common_directory } from '#scripts/git/git-common-directory'
6
6
  import { stamp_file } from '#scripts/josh/stamp-file'
7
7
  import { lane_child_invocation } from '#scripts/lane/lane-child-invocation'
8
+ import { run_hold, type RunHold } from '#scripts/run/hold/run-hold'
8
9
  import { z } from 'zod'
9
10
  import { run_cut_handoff, type Handoff } from './run-cut-handoff'
10
- import { run_hold, type RunHold } from './run-hold'
11
11
 
12
12
  // joshuafolkken/kit#1839: a lane child is a detached `fullrun #<N>` process, and the thinking it
13
13
  // accumulates while implementing rides on every later API call in the same session — measured at 176K
@@ -2,19 +2,19 @@
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { josh_command } from '#scripts/josh/josh-run'
4
4
  import { lane_child_marker } from '#scripts/lane/lane-child-marker'
5
- import { run_cut_report } from './run-cut-report'
5
+ import { run_cut_report } from '#scripts/run/cut/run-cut-report'
6
+ import { run_hold_cli } from '#scripts/run/hold/run-hold-cli'
7
+ import { run_halfrun_resume } from '#scripts/run/run-halfrun-resume'
8
+ import { run_label } from '#scripts/run/run-label'
9
+ import { run_next } from '#scripts/run/run-next'
10
+ import { run_prep } from '#scripts/run/run-prep'
11
+ import { run_prep_cli } from '#scripts/run/run-prep-cli'
12
+ import { run_prrun_resume } from '#scripts/run/run-prrun-resume'
13
+ import { run_stage, type StageCommand, type StageDecision } from '#scripts/run/run-stage'
14
+ import { run_stage_read, type StageRead } from '#scripts/run/run-stage-read'
15
+ import { run_step } from '#scripts/run/run-step'
6
16
  import { run_entry, type EntryParts } from './run-entry'
7
17
  import { run_entry_stop } from './run-entry-stop'
8
- import { run_halfrun_resume } from './run-halfrun-resume'
9
- import { run_hold_cli } from './run-hold-cli'
10
- import { run_label } from './run-label'
11
- import { run_next } from './run-next'
12
- import { run_prep } from './run-prep'
13
- import { run_prep_cli } from './run-prep-cli'
14
- import { run_prrun_resume } from './run-prrun-resume'
15
- import { run_stage, type StageCommand, type StageDecision } from './run-stage'
16
- import { run_stage_read, type StageRead } from './run-stage-read'
17
- import { run_step } from './run-step'
18
18
 
19
19
  // `josh run:entry <N>` — one call for the fixed entry sequence a lane opens on (joshuafolkken/kit#2372).
20
20
  // The loop used to spend a round trip on `run:hold`, one on `cost --cut`, one on `run:prep` and one on
@@ -1,5 +1,5 @@
1
- import { run_hold_cli } from './run-hold-cli'
2
- import { run_step } from './run-step'
1
+ import { run_hold_cli } from '#scripts/run/hold/run-hold-cli'
2
+ import { run_step } from '#scripts/run/run-step'
3
3
 
4
4
  // `josh run:entry <N>` — the one call a `fullrun` makes to open a run (joshuafolkken/kit#2372). It
5
5
  // folds the fixed entry sequence a lane spends four separate round trips on — claim the tree
@@ -1,4 +1,4 @@
1
- import type { CarryRead } from './run-carry'
1
+ import type { CarryRead } from '#scripts/run/carry/run-carry'
2
2
  import { run_event_stream, type RunEvent } from './run-event-stream'
3
3
 
4
4
  // The one place a stream consumer scopes the event stream to a single invocation (joshuafolkken/kit#2395).
@@ -1,5 +1,5 @@
1
1
  import { error_text } from '#scripts/lib/error-message'
2
- import { run_carry } from './run-carry'
2
+ import { run_carry } from '#scripts/run/carry/run-carry'
3
3
  import { run_event_scope } from './run-event-scope'
4
4
  import { run_event_stream, type RunEvent } from './run-event-stream'
5
5
 
@@ -168,6 +168,20 @@ function last_position(events: ReadonlyArray<RunEvent>): number {
168
168
 
169
169
  // The bounded stream serialized back to JSONL. The newest `EVENT_CAP` are kept and the oldest roll off;
170
170
  // each survivor keeps its original `pos`, so the bound never renumbers a position a reader is holding.
171
+ // The stream held to `EVENT_CAP`, dropping the oldest trace events before any position
172
+ // (joshuafolkken/kit#3245). A long run's ship-stage and heartbeat lines once filled 396 of the 500 slots
173
+ // and pushed its cuts off, so the retrospective counted `cut 0` for a run with thirteen; a trace says only
174
+ // that the run was alive, so it is the first to go. Only a stream of positions alone rolls them off.
175
+ function bounded(events: ReadonlyArray<RunEvent>): ReadonlyArray<RunEvent> {
176
+ const excess = events.length - EVENT_CAP
177
+
178
+ if (excess <= 0) return events
179
+
180
+ const dropped = new Set(events.filter((event) => TRACE_KINDS.has(event.kind)).slice(0, excess))
181
+
182
+ return events.filter((event) => !dropped.has(event)).slice(-EVENT_CAP)
183
+ }
184
+
171
185
  function serialize(events: ReadonlyArray<RunEvent>): string {
172
186
  return `${events.map((event) => JSON.stringify(event)).join(LINE_SEPARATOR)}${LINE_SEPARATOR}`
173
187
  }
@@ -184,9 +198,9 @@ function append(target: string, kind: string, text: string, at: string): AppendR
184
198
 
185
199
  const existing = read_events(target)
186
200
  const position = last_position(existing) + POSITION_INCREMENT
187
- const bounded = [...existing, { pos: position, at, kind, text }].slice(-EVENT_CAP)
201
+ const events = bounded([...existing, { pos: position, at, kind, text }])
188
202
 
189
- stamp_file.write_text_stamp(target, serialize(bounded))
203
+ stamp_file.write_text_stamp(target, serialize(events))
190
204
 
191
205
  return { appended: true, position }
192
206
  }
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { fileURLToPath } from 'node:url'
3
- import { run_halfrun_resume } from './run-halfrun-resume'
3
+ import { run_halfrun_resume } from '#scripts/run/run-halfrun-resume'
4
+ import { run_label } from '#scripts/run/run-label'
5
+ import { run_preflight, type PreflightDecision } from '#scripts/run/run-preflight'
6
+ import { run_prrun_resume } from '#scripts/run/run-prrun-resume'
7
+ import { run_tidy_cli } from '#scripts/run/tidy/run-tidy-cli'
4
8
  import { run_hold, type HoldRead, type RunHold } from './run-hold'
5
- import { run_label } from './run-label'
6
- import { run_preflight, type PreflightDecision } from './run-preflight'
7
- import { run_prrun_resume } from './run-prrun-resume'
8
- import { run_tidy_cli } from './run-tidy-cli'
9
9
 
10
10
  // `josh run:hold [<N>]` and `josh run:release [<N> | --force]` — the working-tree guard the typed
11
11
  // entry points ask before they start (joshuafolkken/kit#1091).
@@ -5,12 +5,13 @@ import { CONTEXT_CUT_THRESHOLD } from '#scripts/cost-runtime/context-cut-thresho
5
5
  import { issue_closing_pr } from '#scripts/issue/issue-closing-pr'
6
6
  import { issue_state_cli } from '#scripts/issue/issue-state-cli'
7
7
  import { lane_handoff } from '#scripts/lane/lane-handoff'
8
+ import { lane_ledger } from '#scripts/lane/lane-ledger'
8
9
  import { cli_flags } from '#scripts/lib/cli-flags'
9
- import { run_carry, type CarryOwner, type RunCarry } from './run-carry'
10
- import { run_ending } from './run-ending'
11
- import { run_event_stream } from './run-event-stream'
12
- import { run_event_stream_emit } from './run-event-stream-emit'
13
- import { run_issue_number } from './run-issue-number'
10
+ import { run_carry, type CarryOwner, type RunCarry } from '#scripts/run/carry/run-carry'
11
+ import { run_event_stream } from '#scripts/run/event/run-event-stream'
12
+ import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
13
+ import { run_ending } from '#scripts/run/run-ending'
14
+ import { run_issue_number } from '#scripts/run/run-issue-number'
14
15
  import { run_merge, type ChildOutcome, type EndingSignals } from './run-merge'
15
16
  import { run_merge_steps, type MergeContext } from './run-merge-steps'
16
17
 
@@ -234,6 +235,7 @@ async function on_merged(ctx: MergeContext): Promise<MergeVerdict> {
234
235
  if (refused !== undefined) return report_count_refused(refused)
235
236
 
236
237
  await run_event_stream_emit.emit(run_event_stream.EVENT_KIND.MERGE, `#${ctx.child} merged`)
238
+ await lane_ledger.record_merge(Number(ctx.child))
237
239
 
238
240
  if (await run_merge_steps.is_over_budget(ctx.over)) return emit(OVER_TOKEN, SUCCESS_EXIT_CODE)
239
241
 
@@ -316,7 +318,10 @@ async function on_parked(ctx: MergeContext): Promise<MergeVerdict> {
316
318
  return emit(await run_merge_steps.ask_next(ctx), SUCCESS_EXIT_CODE)
317
319
  }
318
320
 
321
+ // A split child: nothing is counted, and its lane is closed when it holds nothing, so the promoted
322
+ // epic does not keep a seat the next child needs (joshuafolkken/kit#3334).
319
323
  async function on_skipped(ctx: MergeContext): Promise<MergeVerdict> {
324
+ await run_merge_steps.close_split_lane(ctx.child)
320
325
  await run_event_stream_emit.emit(run_event_stream.EVENT_KIND.SPLIT, `#${ctx.child} split`)
321
326
 
322
327
  return emit(await run_merge_steps.ask_next(ctx), SUCCESS_EXIT_CODE)
@@ -9,9 +9,16 @@ import { lane_close } from '#scripts/lane/lane-close'
9
9
  import { lane_reap } from '#scripts/lane/lane-reap'
10
10
  import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
11
11
  import { lane_relaunch } from '#scripts/lane/lane-relaunch'
12
- import { run_carry, type CarryChange, type CarryOwner, type RunCarry } from './run-carry'
13
- import { run_carry_conversation } from './run-carry-conversation'
14
- import { run_cut, type RunCut } from './run-cut'
12
+ import { lane_vacant } from '#scripts/lane/lane-vacant'
13
+ import { poll } from '#scripts/lib/poll'
14
+ import {
15
+ run_carry,
16
+ type CarryChange,
17
+ type CarryOwner,
18
+ type RunCarry,
19
+ } from '#scripts/run/carry/run-carry'
20
+ import { run_carry_conversation } from '#scripts/run/carry/run-carry-conversation'
21
+ import { run_cut, type RunCut } from '#scripts/run/cut/run-cut'
15
22
  import { run_merge } from './run-merge'
16
23
 
17
24
  // The result of attempting to apply a carry change. Distinguishing `refused` from `applied` lets
@@ -38,6 +45,7 @@ const LANE_CLOSE_OCCASION = 'lane close'
38
45
  const GIT_ENTRY = '.git'
39
46
  const MERGE_CLOSER = 'pnpm josh run:merge'
40
47
  const SINGLE_READ = { attempts: 1, interval_ms: 0 }
48
+ const SYNC_RETRY = { attempts: 3, interval_ms: 5000 }
41
49
  // What the driver judged when it parks a child, stated on the issue (joshuafolkken/kit#2769).
42
50
  const FAILED_CAUSE = 'the child’s session ended with its issue still open and unfinished.'
43
51
  const CUT_RELAUNCH_CAUSE =
@@ -125,10 +133,27 @@ async function refused_carry(ctx: MergeContext): Promise<RunCarry | undefined> {
125
133
  return record.carry
126
134
  }
127
135
 
136
+ // **A failed `main:sync` is retried before it ends the run** (joshuafolkken/kit#3323): one failure
137
+ // right after a merge — measured while several lanes merged at once — stopped the whole drive, and a
138
+ // hand-run `main:sync` minutes later succeeded. **The error carries the step's stderr, not its
139
+ // stdout**: `main:sync` reports git's refusal on stderr, while stdout carries the wrapper's guard
140
+ // statistics, which is all the old message showed.
128
141
  async function sync_main(): Promise<void> {
129
- const result = await josh(['main:sync'])
142
+ // `poll_until` answers only whether it succeeded, so the last attempt's stderr is kept here.
143
+ let error_output = ''
130
144
 
131
- if (result.code !== 0) throw new Error(`main:sync failed: ${result.out}`)
145
+ const is_synced = await poll.poll_until(
146
+ async () => {
147
+ const result = await josh(['main:sync'])
148
+
149
+ error_output = result.err ?? ''
150
+
151
+ return result.code === 0
152
+ },
153
+ { ...SYNC_RETRY, sleeper: poll.sleep },
154
+ )
155
+
156
+ if (!is_synced) throw new Error(`main:sync failed: ${error_output}`)
132
157
  }
133
158
 
134
159
  async function close_lane(child: string): Promise<void> {
@@ -172,6 +197,28 @@ async function preserve_uncommitted(child: string): Promise<boolean> {
172
197
  }
173
198
  }
174
199
 
200
+ function kept_lane_comment(directory: string): string {
201
+ return `The split left this issue's lane open: it still holds a commit, an uncommitted change or a live process — ${directory}`
202
+ }
203
+
204
+ // A child promoted to an epic by a split leaves its lane holding a seat (joshuafolkken/kit#3334): the
205
+ // epic root is never dispatched again, so nothing else would ever close it. The lane is closed only
206
+ // when it holds nothing — no commit, no change, no live process (`lane_vacant`); any other lane is
207
+ // kept, and why is stated on the issue.
208
+ async function close_split_lane(child: string): Promise<void> {
209
+ const lane = await lane_registry.find_open_lane(child)
210
+
211
+ if (lane === undefined) return
212
+
213
+ if (await lane_vacant.is_vacant(lane)) {
214
+ await close_lane(child)
215
+
216
+ return
217
+ }
218
+
219
+ await git_gh_issue_write.issue_try_comment(child, kept_lane_comment(lane.directory))
220
+ }
221
+
175
222
  // The progress comment is a human-readable mirror of the carry record, so it is best-effort and only
176
223
  // where a named epic has a body to carry it — a pure backlog run keeps the record alone.
177
224
  async function post_counters(ctx: MergeContext, carry: RunCarry | undefined): Promise<void> {
@@ -356,6 +403,7 @@ async function ask_next(ctx: MergeContext): Promise<string> {
356
403
  const run_merge_steps = {
357
404
  CUT_RELAUNCH_CAUSE,
358
405
  ask_next,
406
+ close_split_lane,
359
407
  do_failed,
360
408
  do_merged,
361
409
  do_outage,
@@ -5,7 +5,7 @@ import {
5
5
  NEEDS_DECISION_LABEL,
6
6
  } from '#scripts/issue/issue-labels'
7
7
  import type { IssueState } from '#scripts/issue/issue-state'
8
- import type { CarryChange, RunCarry } from './run-carry'
8
+ import type { CarryChange, RunCarry } from '#scripts/run/carry/run-carry'
9
9
 
10
10
  // joshuafolkken/kit#2024: a `backlogrun` merge event was two parent turns — a reading turn and an
11
11
  // acting turn — spent at the session whose context is largest and whose per-turn cost is highest.
@@ -6,7 +6,7 @@ import { backlog_ready } from '#scripts/backlog/backlog-ready'
6
6
  import { gh_spawn } from '#scripts/gh/gh-spawn'
7
7
  import { lane_child_marker } from '#scripts/lane/lane-child-marker'
8
8
  import { error_text } from '#scripts/lib/error-message'
9
- import { run_event_stream_emit } from './run-event-stream-emit'
9
+ import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
10
10
  import { run_progress, type ProgressState } from './run-progress'
11
11
  import { run_progress_args, type ParsedValues } from './run-progress-args'
12
12
  import { run_progress_clock } from './run-progress-clock'