@joshuafolkken/kit 1.1178.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 (425) 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 +6 -2
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +23 -2
  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 +21 -13
  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 +6 -1
  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 +32 -31
  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-S6X6WYKR.js → chunk-55HHG5PX.js} +1210 -711
  43. package/dist/hooks/{chunk-ZAL3NRGL.js → chunk-5VMKCCC4.js} +25 -10
  44. package/dist/hooks/chunk-5VNDSNG3.js +12 -0
  45. package/dist/hooks/{chunk-32GQTL4F.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-VESKRQOF.js → chunk-F56MFS4B.js} +3 -3
  49. package/dist/hooks/{chunk-UAGLLIGZ.js → chunk-FIVDHQGT.js} +7 -6
  50. package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
  51. package/dist/hooks/{chunk-G6KRCKEC.js → chunk-JUDKDCDP.js} +496 -378
  52. package/dist/hooks/{chunk-GDS5N2KB.js → chunk-NV2JRZZO.js} +718 -681
  53. package/dist/hooks/{chunk-VQLLNFNT.js → chunk-RNWHN5BP.js} +3 -3
  54. package/dist/hooks/{chunk-DO6NWWHU.js → chunk-TQNPDZNH.js} +4 -4
  55. package/dist/hooks/{chunk-I4XSL4TT.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 +217 -195
  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 +66 -58
  64. package/dist/josh.js +868 -74
  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 +16 -5
  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 +1 -1
  111. package/scripts/backlog/backlog-pool.ts +57 -11
  112. package/scripts/backlog/backlog-ready.ts +1 -1
  113. package/scripts/backlog/backlog-scope.ts +1 -1
  114. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  115. package/scripts/backlog/backlog-stalled.ts +1 -1
  116. package/scripts/build/build-codex-hooks.ts +3 -1
  117. package/scripts/build/build-commands.ts +66 -0
  118. package/scripts/build/build-hooks.ts +2 -1
  119. package/scripts/build/build-library.ts +12 -3
  120. package/scripts/build/build.ts +70 -0
  121. package/scripts/build/local-bin.ts +3 -13
  122. package/scripts/delegation/investigation-reads.ts +3 -1
  123. package/scripts/discovery/repo-origin.ts +5 -5
  124. package/scripts/doctor/doctor-consumer.ts +2 -2
  125. package/scripts/doctor/doctor-io.ts +2 -3
  126. package/scripts/document/capped-print-part.ts +30 -0
  127. package/scripts/document/capped-print.ts +3 -4
  128. package/scripts/document/document-byte-budget.ts +11 -2
  129. package/scripts/document/document-scan.ts +117 -0
  130. package/scripts/document/document-section.ts +143 -16
  131. package/scripts/document/entry-read-budget.ts +5 -3
  132. package/scripts/document/entry-read-set.ts +10 -89
  133. package/scripts/document/read-set-cost.ts +98 -0
  134. package/scripts/epic/epic-add-body.ts +1 -0
  135. package/scripts/epic/epic-audit-checks.ts +1 -1
  136. package/scripts/epic/epic-audit-rationale.ts +4 -3
  137. package/scripts/epic/epic-bundle-cli.ts +14 -3
  138. package/scripts/epic/epic-bundle-poll.ts +41 -0
  139. package/scripts/epic/epic-busy.ts +1 -1
  140. package/scripts/epic/epic-candidate-confirm.ts +3 -2
  141. package/scripts/epic/epic-classify.ts +1 -1
  142. package/scripts/epic/epic-cli-argv.ts +78 -46
  143. package/scripts/epic/epic-cli-remove.ts +11 -19
  144. package/scripts/epic/epic-cli.ts +74 -73
  145. package/scripts/epic/epic-close-comment.ts +1 -1
  146. package/scripts/epic/epic-close.ts +1 -1
  147. package/scripts/epic/epic-cross-repo.ts +5 -5
  148. package/scripts/epic/epic-graph.ts +1 -1
  149. package/scripts/epic/epic-index.ts +1 -1
  150. package/scripts/epic/epic-issue.ts +2 -6
  151. package/scripts/epic/epic-lane-offer.ts +2 -2
  152. package/scripts/epic/epic-next.ts +29 -42
  153. package/scripts/epic/epic-solo-stale.ts +1 -1
  154. package/scripts/epic/epic-triage.ts +8 -4
  155. package/scripts/epic/epic-validate.ts +1 -1
  156. package/scripts/followup/git-followup-finish.ts +2 -2
  157. package/scripts/followup/git-followup-label.ts +2 -2
  158. package/scripts/followup/git-followup-merged.ts +2 -2
  159. package/scripts/followup/git-pr-followup-checks.ts +142 -0
  160. package/scripts/followup/git-pr-followup.ts +4 -129
  161. package/scripts/gate/core-budget.ts +69 -1
  162. package/scripts/gate/gate-plan.ts +47 -7
  163. package/scripts/gate/gate-skip.ts +38 -7
  164. package/scripts/gate/hook-gate-reuse.ts +12 -10
  165. package/scripts/gate/pre-commit-type-check.ts +4 -1
  166. package/scripts/gate/project-checks.ts +6 -9
  167. package/scripts/gate/type-check-step.ts +2 -3
  168. package/scripts/gate/verification-gate.ts +27 -8
  169. package/scripts/gh/git-gh-api-path.ts +2 -2
  170. package/scripts/gh/git-gh-issue-rest.ts +3 -3
  171. package/scripts/gh/git-gh-issue-write.ts +53 -7
  172. package/scripts/gh/git-gh-pr-read.ts +64 -19
  173. package/scripts/gh/git-gh-pr-rest.ts +1 -1
  174. package/scripts/gh/git-gh-pr.ts +5 -1
  175. package/scripts/gh/git-pr-ai-review.ts +1 -1
  176. package/scripts/gh/git-pr-checks-eval.ts +1 -3
  177. package/scripts/gh/git-pr-checks-parse.ts +1 -1
  178. package/scripts/gh/git-pr-checks-watch.ts +6 -5
  179. package/scripts/gh/git-pr-checks.ts +7 -5
  180. package/scripts/gh/git-pr-coderabbit.ts +1 -1
  181. package/scripts/gh/git-pr-error.ts +1 -3
  182. package/scripts/gh/git-pr.ts +9 -13
  183. package/scripts/gh/github-issue-url.ts +2 -2
  184. package/scripts/git/constants.ts +2 -6
  185. package/scripts/git/git-command.ts +20 -218
  186. package/scripts/git/git-diff-reads.ts +225 -0
  187. package/scripts/git/git-error.ts +4 -12
  188. package/scripts/git/git-pre-push-hook.ts +139 -0
  189. package/scripts/git/git-prompt.ts +2 -6
  190. package/scripts/git/git-spawn-sync.ts +3 -1
  191. package/scripts/git/git-spawn.ts +1 -1
  192. package/scripts/git/git-staging.ts +4 -6
  193. package/scripts/git/git-status.ts +3 -9
  194. package/scripts/hooks/codex-hook-adapter.ts +1 -1
  195. package/scripts/hooks/edited-cspell.ts +2 -4
  196. package/scripts/hooks/format-edited-file.ts +3 -12
  197. package/scripts/hooks/pre-push-unit.ts +31 -9
  198. package/scripts/hooks/reserved-run.ts +2 -2
  199. package/scripts/hooks/run-hook.sh +5 -4
  200. package/scripts/hooks/stop-guard.ts +5 -3
  201. package/scripts/init/init-actions.ts +0 -5
  202. package/scripts/init/init-ai-copy.ts +13 -2
  203. package/scripts/init/init-logic-json-merge.ts +19 -0
  204. package/scripts/init/init-logic-secretlint.ts +7 -7
  205. package/scripts/init/init-logic-workspace.ts +41 -2
  206. package/scripts/init/init-logic.ts +16 -26
  207. package/scripts/init/init.ts +5 -15
  208. package/scripts/init/initial-commit.ts +4 -4
  209. package/scripts/init/kit-development-versions.ts +40 -0
  210. package/scripts/init/kit-setup-state.ts +2 -2
  211. package/scripts/init/start-exec.ts +68 -19
  212. package/scripts/init/start-setup-pr.ts +9 -9
  213. package/scripts/init/start-steps.ts +6 -10
  214. package/scripts/issue/defect-rate-cli.ts +1 -1
  215. package/scripts/issue/git-next-issues.ts +1 -1
  216. package/scripts/issue/issue-auto-ok.ts +1 -1
  217. package/scripts/issue/issue-backlinks-cli.ts +2 -2
  218. package/scripts/issue/issue-backlinks.ts +9 -9
  219. package/scripts/issue/issue-comment-cli.ts +19 -13
  220. package/scripts/issue/issue-file-cli.ts +45 -18
  221. package/scripts/issue/issue-file.ts +24 -1
  222. package/scripts/issue/issue-scout-closed.ts +1 -1
  223. package/scripts/issue/issue-state-cli.ts +30 -74
  224. package/scripts/josh/josh-command-map.ts +2 -0
  225. package/scripts/josh/josh-command-types.ts +11 -0
  226. package/scripts/josh/josh-commands-ai.ts +4 -4
  227. package/scripts/josh/josh-commands-backlog.ts +4 -1
  228. package/scripts/josh/josh-commands-development.ts +10 -0
  229. package/scripts/josh/josh-commands-document.ts +1 -0
  230. package/scripts/josh/josh-commands-lane.ts +22 -0
  231. package/scripts/josh/josh-commands-maintenance.ts +4 -4
  232. package/scripts/josh/josh-commands-run.ts +12 -7
  233. package/scripts/josh/josh-in-process.ts +28 -9
  234. package/scripts/josh/josh-logic.ts +33 -15
  235. package/scripts/josh/process-identity.ts +1 -3
  236. package/scripts/josh/session-language-cli.ts +3 -4
  237. package/scripts/lane/lane-await.ts +5 -1
  238. package/scripts/lane/lane-child-invocation.ts +2 -2
  239. package/scripts/lane/lane-cli.ts +3 -3
  240. package/scripts/lane/lane-dispatch.ts +14 -10
  241. package/scripts/lane/lane-environment.ts +5 -1
  242. package/scripts/lane/lane-handoff.ts +1 -1
  243. package/scripts/lane/lane-install.ts +2 -13
  244. package/scripts/lane/lane-launch-cli.ts +15 -1
  245. package/scripts/lane/lane-ledger.ts +145 -0
  246. package/scripts/lane/lane-leftover.ts +1 -1
  247. package/scripts/lane/lane-load.ts +108 -0
  248. package/scripts/lane/lane-measure-cli.ts +149 -0
  249. package/scripts/lane/lane-occupancy.ts +3 -0
  250. package/scripts/lane/lane-reap.ts +1 -3
  251. package/scripts/lane/lane-relaunch.ts +1 -1
  252. package/scripts/lane/lane-stats.ts +224 -0
  253. package/scripts/lane/lane-vacant.ts +36 -0
  254. package/scripts/lane/openai-lane-ship.ts +2 -2
  255. package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
  256. package/scripts/lane/openai-lane-supervisor.ts +7 -165
  257. package/scripts/lane/openai-review-broker.ts +2 -4
  258. package/scripts/lib/ancestor-directories.ts +35 -0
  259. package/scripts/lib/animation-helpers.ts +2 -2
  260. package/scripts/lib/buffered-process.ts +3 -7
  261. package/scripts/lib/cli-flags.ts +71 -1
  262. package/scripts/lib/spawn-exit.ts +2 -2
  263. package/scripts/lib/timeouts.ts +35 -2
  264. package/scripts/lines/line-budget.ts +4 -6
  265. package/scripts/notify/telegram-test.ts +6 -0
  266. package/scripts/observations/observation-ledger-line.ts +6 -6
  267. package/scripts/observations/observation-ledger-migrate.ts +55 -4
  268. package/scripts/observations/observation-ledger.ts +26 -7
  269. package/scripts/overrides/lockfile-overrides.ts +1 -1
  270. package/scripts/overrides/overrides-logic.ts +1 -1
  271. package/scripts/propagate/propagate-git.ts +1 -1
  272. package/scripts/propagate/propagate-publish.ts +2 -2
  273. package/scripts/propagate/propagate-steps.ts +20 -15
  274. package/scripts/propagate/propagate.ts +36 -27
  275. package/scripts/refactor/import-graph.ts +18 -1
  276. package/scripts/refactor/refactor-lint.ts +4 -4
  277. package/scripts/repo/repository-labels.ts +2 -2
  278. package/scripts/repo/required-checks-logic.ts +1 -3
  279. package/scripts/review/live-evidence.ts +2 -4
  280. package/scripts/review/review-brief-cli.ts +1 -1
  281. package/scripts/review/review-finding-ledger.ts +1 -1
  282. package/scripts/review/review-findings-cli.ts +1 -3
  283. package/scripts/rules/decision-oracle-batch.ts +128 -0
  284. package/scripts/rules/decision-oracle-tokens.ts +21 -0
  285. package/scripts/rules/decision-oracle.ts +8 -120
  286. package/scripts/rules/delivered-rules.ts +26 -182
  287. package/scripts/rules/direct-filing.ts +1 -1
  288. package/scripts/rules/early-heartbeat.ts +3 -3
  289. package/scripts/rules/git-argv.ts +1 -1
  290. package/scripts/rules/git-force.ts +1 -1
  291. package/scripts/rules/implementation-cut.ts +2 -2
  292. package/scripts/rules/index-guard.ts +1 -1
  293. package/scripts/rules/issue-comments.ts +118 -0
  294. package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
  295. package/scripts/rules/lane-background.ts +2 -2
  296. package/scripts/rules/lane-park.ts +18 -0
  297. package/scripts/rules/lane-split-park.ts +128 -0
  298. package/scripts/rules/oracle-firing.ts +1 -1
  299. package/scripts/rules/poll-loop.ts +1 -1
  300. package/scripts/rules/pre-gate-cut.ts +1 -1
  301. package/scripts/rules/protected-files.ts +59 -4
  302. package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
  303. package/scripts/rules/shell-body-trigger.ts +1 -1
  304. package/scripts/rules/stop-rules.ts +18 -7
  305. package/scripts/rules/test-declared-commit.ts +4 -4
  306. package/scripts/rules/wip-cap.ts +63 -0
  307. package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
  308. package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
  309. package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
  310. package/scripts/run/{run-carry.ts → carry/run-carry.ts} +1 -1
  311. package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
  312. package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +2 -2
  313. package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
  314. package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
  315. package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
  316. package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
  317. package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
  318. package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +16 -2
  319. package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
  320. package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
  321. package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
  322. package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
  323. package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
  324. package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
  325. package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
  326. package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
  327. package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
  328. package/scripts/run/run-ending.ts +2 -2
  329. package/scripts/run/run-halfrun-resume.ts +6 -1
  330. package/scripts/run/run-headless.ts +1 -1
  331. package/scripts/run/run-invocation.ts +85 -84
  332. package/scripts/run/run-liveness.ts +1 -1
  333. package/scripts/run/run-parent-cut-hook.ts +1 -1
  334. package/scripts/run/run-preflight.ts +6 -6
  335. package/scripts/run/run-prep-cli.ts +1 -1
  336. package/scripts/run/run-prrun-resume.ts +1 -1
  337. package/scripts/run/run-report-cli.ts +4 -4
  338. package/scripts/run/run-report.ts +2 -2
  339. package/scripts/run/run-status-cli.ts +1 -1
  340. package/scripts/run/run-step-cli.ts +3 -3
  341. package/scripts/run/run-step.ts +2 -2
  342. package/scripts/run/run-stop-notify.ts +1 -1
  343. package/scripts/run/run-stranded-detect.ts +4 -4
  344. package/scripts/run/run-stranded.ts +2 -2
  345. package/scripts/run/run-watcher-guard-cli.ts +1 -1
  346. package/scripts/run/run-watcher-guard.ts +5 -3
  347. package/scripts/run/run-watcher-hook.ts +1 -1
  348. package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +2 -2
  349. package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
  350. package/scripts/run/ship/run-ship-lock.ts +50 -0
  351. package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
  352. package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
  353. package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +4 -3
  354. package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
  355. package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
  356. package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +11 -12
  357. package/scripts/run/ship/run-ship-stop-text.ts +13 -0
  358. package/scripts/run/{run-ship-sync.ts → ship/run-ship-sync.ts} +25 -11
  359. package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
  360. package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +23 -8
  361. package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +3 -3
  362. package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +59 -8
  363. package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
  364. package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +8 -2
  365. package/scripts/run/wake/run-wake-restart.ts +58 -0
  366. package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
  367. package/scripts/run/{run-wake.ts → wake/run-wake.ts} +1 -1
  368. package/scripts/safe-chain/preinstall-command.ts +20 -21
  369. package/scripts/safe-chain/project-config.ts +19 -15
  370. package/scripts/sync/sync-ai-files.ts +1 -1
  371. package/scripts/sync/sync-configs.ts +2 -1
  372. package/scripts/sync/sync.ts +15 -2
  373. package/scripts/test/unit-worker-share.ts +1 -0
  374. package/scripts/time-runtime/time-background.ts +5 -1
  375. package/scripts/time-runtime/time-instant.ts +3 -1
  376. package/scripts/time-runtime/time-spans.ts +6 -139
  377. package/scripts/time-runtime/time-tool-call.ts +160 -0
  378. package/scripts/version/effective-upstream.ts +2 -20
  379. package/scripts/version/latest-corepack.ts +17 -17
  380. package/scripts/version/latest-regression.ts +1 -1
  381. package/scripts/version/latest-update.ts +4 -4
  382. package/scripts/version/npm-registry.ts +20 -5
  383. package/scripts/version/publishable-range-check.ts +2 -2
  384. package/scripts/version/publishable-range.ts +1 -1
  385. package/scripts/version/release-age.ts +48 -38
  386. package/scripts/version/release-hold.ts +1 -1
  387. package/scripts/version/safe-chain-version-update.ts +40 -0
  388. package/scripts/version/upgrade-shell-command.ts +48 -8
  389. package/scripts/version/version-check-logic.ts +23 -14
  390. package/scripts/version/version-commands.ts +23 -11
  391. package/scripts/version/version-remote.ts +28 -13
  392. package/templates/pnpm-workspace.basic.yaml +4 -0
  393. package/templates/workflows/dependabot-auto-merge.yml +5 -5
  394. package/templates/workflows/github-release.yml +2 -0
  395. package/scripts/build/build-config-merge.ts +0 -27
  396. package/scripts/build/build-managed-marker.ts +0 -27
  397. package/scripts/build/build-self-sync-guard.ts +0 -27
  398. package/scripts/build/build-version.ts +0 -26
  399. package/scripts/version/preinstall-version-update.ts +0 -86
  400. /package/scripts/git/{schemas.ts → git-schemas.ts} +0 -0
  401. /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
  402. /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
  403. /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
  404. /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
  405. /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
  406. /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
  407. /package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +0 -0
  408. /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
  409. /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
  410. /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
  411. /package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +0 -0
  412. /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
  413. /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
  414. /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
  415. /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
  416. /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
  417. /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
  418. /package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +0 -0
  419. /package/scripts/run/{run-ship-pre-detach.ts → ship/run-ship-pre-detach.ts} +0 -0
  420. /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
  421. /package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +0 -0
  422. /package/scripts/run/{run-ship.ts → ship/run-ship.ts} +0 -0
  423. /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
  424. /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
  425. /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
@@ -0,0 +1,108 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import { freemem, loadavg } from 'node:os'
3
+ import { execa } from 'execa'
4
+ import { lane_await } from './lane-await'
5
+ import type { LoadEntry } from './lane-ledger'
6
+ import { lane_registry, type LaneInfo } from './lane-registry'
7
+
8
+ // One machine-load sample for the lane-limit measurement (joshuafolkken/kit#3355): the one-minute load
9
+ // average, free memory, swap in use and how many lanes have a child working, read at one instant so
10
+ // `josh lane:stats` can set the load beside the lane count that produced it.
11
+ //
12
+ // **Swap is read per platform and is optional.** Node exposes no swap figure, so macOS asks `sysctl` and
13
+ // Linux reads `/proc/meminfo`; any other platform, or an output neither parser recognizes, records the
14
+ // sample without it rather than as a zero nobody measured.
15
+
16
+ const BYTES_PER_MB = 1_048_576
17
+ const MB_PER_GB = 1024
18
+ const KB_PER_MB = 1024
19
+ const DARWIN_SWAP = /used = (?<amount>[\d.]+)(?<unit>[MG])/u
20
+ const LINUX_SWAP_TOTAL = /^SwapTotal:\s+(?<kb>\d+) kB/mu
21
+ const LINUX_SWAP_FREE = /^SwapFree:\s+(?<kb>\d+) kB/mu
22
+ const MEMINFO_PATH = '/proc/meminfo'
23
+ const SYSCTL_ARGUMENTS = ['-n', 'vm.swapusage']
24
+ const GB_UNIT = 'G'
25
+
26
+ // `sysctl -n vm.swapusage` → `total = 5120.00M used = 3993.25M free = 1126.75M (encrypted)`.
27
+ function parse_darwin_swap(output: string): number | undefined {
28
+ const groups = DARWIN_SWAP.exec(output)?.groups
29
+
30
+ if (groups?.['amount'] === undefined) return undefined
31
+
32
+ const scale = groups['unit'] === GB_UNIT ? MB_PER_GB : 1
33
+
34
+ return Number(groups['amount']) * scale
35
+ }
36
+
37
+ function kilobytes_of(meminfo: string, pattern: RegExp): number | undefined {
38
+ const kb = pattern.exec(meminfo)?.groups?.['kb']
39
+
40
+ return kb === undefined ? undefined : Number(kb)
41
+ }
42
+
43
+ // `/proc/meminfo` carries the total and the free swap; what is in use is their difference.
44
+ function parse_linux_swap(meminfo: string): number | undefined {
45
+ const total = kilobytes_of(meminfo, LINUX_SWAP_TOTAL)
46
+ const free = kilobytes_of(meminfo, LINUX_SWAP_FREE)
47
+
48
+ if (total === undefined || free === undefined) return undefined
49
+
50
+ return (total - free) / KB_PER_MB
51
+ }
52
+
53
+ async function read_swap_mb(
54
+ platform: NodeJS.Platform = process.platform,
55
+ ): Promise<number | undefined> {
56
+ try {
57
+ if (platform === 'darwin') {
58
+ const { stdout } = await execa('sysctl', SYSCTL_ARGUMENTS)
59
+
60
+ return parse_darwin_swap(stdout)
61
+ }
62
+
63
+ return platform === 'linux' ? parse_linux_swap(await readFile(MEMINFO_PATH, 'utf8')) : undefined
64
+ } catch {
65
+ return undefined
66
+ }
67
+ }
68
+
69
+ // The lanes with a child at work. An open lane is not enough: a stranded lane, or one a park kept, holds
70
+ // a tree but no running child, so it adds no load — the child's process is what `lane:await` polls.
71
+ function count_working(
72
+ lanes: ReadonlyArray<LaneInfo>,
73
+ is_running: (issue: string) => boolean = lane_await.is_process_running_default,
74
+ ): number {
75
+ return lanes.filter((lane) => !lane.is_stranded && is_running(lane.issue)).length
76
+ }
77
+
78
+ async function read_lanes(): Promise<number | undefined> {
79
+ try {
80
+ return count_working(await lane_registry.list_lanes())
81
+ } catch {
82
+ return undefined
83
+ }
84
+ }
85
+
86
+ async function sample(at: string): Promise<LoadEntry> {
87
+ const [swap_mb, lanes] = await Promise.all([read_swap_mb(), read_lanes()])
88
+ const [load = 0] = loadavg()
89
+
90
+ return {
91
+ kind: 'load',
92
+ at,
93
+ load,
94
+ free_mb: Math.round(freemem() / BYTES_PER_MB),
95
+ swap_mb,
96
+ lanes,
97
+ }
98
+ }
99
+
100
+ const lane_load = {
101
+ count_working,
102
+ parse_darwin_swap,
103
+ parse_linux_swap,
104
+ read_swap_mb,
105
+ sample,
106
+ }
107
+
108
+ export { lane_load }
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env tsx
2
+ import { setInterval as every } from 'node:timers/promises'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { cli_flags } from '#scripts/lib/cli-flags'
5
+ import { lane_capacity } from './lane-capacity'
6
+ import { lane_ledger } from './lane-ledger'
7
+ import { lane_load } from './lane-load'
8
+ import { lane_stats } from './lane-stats'
9
+
10
+ // `josh lane:sample` / `josh lane:stats` — the lane-limit measurement (joshuafolkken/kit#3355).
11
+ //
12
+ // `lane:sample` appends one machine-load sample to the ledger, or one every `--every <seconds>` until it
13
+ // is stopped; `lane:stats` reduces a period of the ledger to the one table row #3347 records per lane
14
+ // limit. The merges and the gates write themselves (`run:merge`, `josh gate`), so a person runs only
15
+ // the sampler beside a `backlogrun` and closes the period with `lane:stats`. The procedure is
16
+ // `docs/maintainers/lane-limit-measurement.md`.
17
+
18
+ const ARGV_OFFSET = 2
19
+ const FAILURE_EXIT_CODE = 1
20
+ const MS_PER_SECOND = 1000
21
+ const SAMPLE_VERB = 'sample'
22
+ const STATS_VERB = 'stats'
23
+ const USAGE = [
24
+ 'Usage: josh lane:sample [--every <seconds>]',
25
+ ' josh lane:stats --period <days> [--limit <lane-limit>]',
26
+ ].join('\n')
27
+
28
+ const SAMPLE_OPTIONS = { every: { type: 'string' } } as const
29
+ const STATS_OPTIONS = { period: { type: 'string' }, limit: { type: 'string' } } as const
30
+
31
+ // Where the ledger is and what "now" is, overridable so a test reads a fixed ledger at a fixed instant.
32
+ interface MeasureContext {
33
+ ledger_path?: string
34
+ now_ms?: number
35
+ }
36
+
37
+ function refuse(): number {
38
+ console.error(USAGE)
39
+
40
+ return FAILURE_EXIT_CODE
41
+ }
42
+
43
+ // A positive finite number, or `undefined` for anything else — a typo must not become a zero period.
44
+ function positive(text: string | undefined): number | undefined {
45
+ const value = Number(text)
46
+
47
+ return text !== undefined && Number.isFinite(value) && value > 0 ? value : undefined
48
+ }
49
+
50
+ async function take_sample(context: MeasureContext): Promise<void> {
51
+ const at = new Date(context.now_ms ?? Date.now()).toISOString()
52
+
53
+ await lane_ledger.record(await lane_load.sample(at), context.ledger_path)
54
+ }
55
+
56
+ // The `--every` interval: `{ seconds: undefined }` for a single sample, `undefined` for a refusal.
57
+ function sample_interval(rest: ReadonlyArray<string>): { seconds: number | undefined } | undefined {
58
+ const values = cli_flags.values_of(rest, SAMPLE_OPTIONS)
59
+
60
+ if (values === undefined) return undefined
61
+
62
+ if (values.every === undefined) return { seconds: undefined }
63
+
64
+ const seconds = positive(values.every)
65
+
66
+ return seconds === undefined ? undefined : { seconds }
67
+ }
68
+
69
+ // A sample every `seconds` until a person stops the process, which is how a measurement period is held
70
+ // open; it never returns.
71
+ async function keep_sampling(context: MeasureContext, seconds: number): Promise<void> {
72
+ const ticks = every(seconds * MS_PER_SECOND, context)
73
+
74
+ for await (const tick_context of ticks) await take_sample(tick_context)
75
+ }
76
+
77
+ // One sample now, then — with `--every` — one per interval.
78
+ async function sample_command(
79
+ rest: ReadonlyArray<string>,
80
+ context: MeasureContext,
81
+ ): Promise<number> {
82
+ const interval = sample_interval(rest)
83
+
84
+ if (interval === undefined) return refuse()
85
+
86
+ await take_sample(context)
87
+
88
+ if (interval.seconds !== undefined) await keep_sampling(context, interval.seconds)
89
+
90
+ return 0
91
+ }
92
+
93
+ // The label defaults to the limit this process reads, which is the period's limit only when nobody
94
+ // changed it since — `--limit` names it outright.
95
+ function limit_label(given: string | undefined): string {
96
+ if (given !== undefined) return given
97
+
98
+ const choice = lane_capacity.lane_limit()
99
+
100
+ return choice.kind === 'limit' ? String(choice.limit) : lane_stats.MISSING
101
+ }
102
+
103
+ async function read_ledger(
104
+ context: MeasureContext,
105
+ ): Promise<ReturnType<typeof lane_ledger.read_entries>> {
106
+ const path = context.ledger_path ?? (await lane_ledger.target())
107
+
108
+ return path === undefined ? [] : lane_ledger.read_entries(path)
109
+ }
110
+
111
+ async function stats_command(
112
+ rest: ReadonlyArray<string>,
113
+ context: MeasureContext,
114
+ ): Promise<number> {
115
+ const values = cli_flags.values_of(rest, STATS_OPTIONS)
116
+ const period_days = positive(values?.period)
117
+
118
+ if (values === undefined || period_days === undefined) return refuse()
119
+
120
+ const window = lane_stats.window_of(period_days, context.now_ms ?? Date.now())
121
+ const label = { limit: limit_label(values.limit), period_days }
122
+ const row = lane_stats.row(await read_ledger(context), window, label)
123
+
124
+ console.info(`${lane_stats.header()}\n${row}`)
125
+
126
+ return 0
127
+ }
128
+
129
+ async function run(argv: ReadonlyArray<string>, context: MeasureContext = {}): Promise<number> {
130
+ const [verb, ...rest] = argv
131
+
132
+ if (verb === SAMPLE_VERB) return await sample_command(rest, context)
133
+
134
+ return verb === STATS_VERB ? await stats_command(rest, context) : refuse()
135
+ }
136
+
137
+ async function main(argv: ReadonlyArray<string>): Promise<void> {
138
+ process.exitCode = await run(argv)
139
+ }
140
+
141
+ const lane_measure_cli = {
142
+ USAGE,
143
+ run,
144
+ }
145
+
146
+ if (process.argv[1] === fileURLToPath(import.meta.url)) await main(process.argv.slice(ARGV_OFFSET))
147
+
148
+ export type { MeasureContext }
149
+ export { lane_measure_cli }
@@ -82,7 +82,10 @@ function without_label_line(issue: number): string {
82
82
 
83
83
  function labelled_lines(labelled: ReadonlyArray<LabelledIssue>): Array<string> {
84
84
  return labelled.flatMap((entry) => {
85
+ // One return type across the branches (sonarjs/function-return-type) keeps the array wrappers.
86
+ // eslint-disable-next-line unicorn/no-unnecessary-array-flat-map -- see above
85
87
  if (entry.liveness === 'stopped') return [stopped_line(entry.issue)]
88
+ // eslint-disable-next-line unicorn/no-unnecessary-array-flat-map -- see above
86
89
  if (entry.liveness === 'unknown') return [unknown_line(entry.issue)]
87
90
 
88
91
  return []
@@ -1,4 +1,5 @@
1
1
  import { execFileSync } from 'node:child_process'
2
+ import { PROBE_TIMEOUT_MS } from '#scripts/lib/timeouts'
2
3
  import { lane_child_invocation } from './lane-child-invocation'
3
4
 
4
5
  // Ending a lane child that did not end itself (joshuafolkken/kit#2421).
@@ -24,9 +25,6 @@ const CHILDREN_OF_FLAG = '-P'
24
25
  const PS_COMMAND = '/bin/ps'
25
26
  const PARENT_FORMAT = ['-o', 'ppid=', '-p']
26
27
  const TERMINATE_SIGNAL = 'SIGTERM'
27
- // A probe that hung would hang the close it runs inside; failing answers "no process", which leaves
28
- // the lane exactly as it was before this module existed.
29
- const PROBE_TIMEOUT_MS = 2000
30
28
  // `0` addresses the caller's own process group and `1` is init — neither is ever a lane child.
31
29
  const LOWEST_CHILD_PID = 2
32
30
 
@@ -1,6 +1,6 @@
1
1
  import { agent_argv } from '#scripts/agent/agent-argv'
2
2
  import { detached_launch, type LaunchResult } from '#scripts/run/detached-launch'
3
- import { run_ship_detach } from '#scripts/run/run-ship-detach'
3
+ import { run_ship_detach } from '#scripts/run/ship/run-ship-detach'
4
4
  import { lane_child_invocation } from './lane-child-invocation'
5
5
  import { lane_child_marker } from './lane-child-marker'
6
6
  import { lane_dispatch_log } from './lane-dispatch-log'
@@ -0,0 +1,224 @@
1
+ import type { GateEntry, LedgerEntry, LoadEntry } from './lane-ledger'
2
+
3
+ // The lane-limit measurement's one table row (joshuafolkken/kit#3355): the ledger entries of one period
4
+ // reduced to throughput, gate duration and machine load, in the column order
5
+ // `docs/maintainers/lane-limit-measurement.md` documents, so the row pastes into #3347's table as is.
6
+ //
7
+ // **A column with no samples prints `—`, never `0`.** A period nobody ran the sampler through has no
8
+ // load, which is not a load of zero; a zero would be read as an idle machine.
9
+ //
10
+ // **Throughput is per active hour** — the time load samples saw a lane child working — rather than per
11
+ // calendar hour, so the nights between runs in a several-day period do not dilute the rate. The gaps
12
+ // between work entries cannot stand in for it: at a low limit the gap from one merge to the next child's
13
+ // first gate is a whole implementation, which a gap cap drops, inflating exactly the low limits' rate.
14
+ // Only a period no sample saw working falls back to the gaps of up to an hour between merges and gates.
15
+ //
16
+ // **The means take working samples only** for the same reason: the sampler runs through the nights,
17
+ // and their idle samples would read four busy lanes as about one. Peaks and minima take every sample.
18
+
19
+ const MS_PER_MINUTE = 60_000
20
+ const MS_PER_HOUR = 3_600_000
21
+ // A busy lane pool finishes a gate well within this; a longer silence is time nobody was running work.
22
+ const ACTIVE_GAP_MS = MS_PER_HOUR
23
+ const MS_PER_DAY = 86_400_000
24
+ const MB_PER_GB = 1024
25
+ const HALF = 2
26
+ const DECIMALS = 1
27
+ const MISSING = '—'
28
+ const CELL_SEPARATOR = ' | '
29
+
30
+ const COLUMNS = [
31
+ 'JOSH_LANE_LIMIT',
32
+ 'period (days)',
33
+ 'merges',
34
+ 'merges / hour',
35
+ 'effective lanes',
36
+ 'gate median (min)',
37
+ 'gate max (min)',
38
+ 'load peak',
39
+ 'load mean',
40
+ 'swap peak (GB)',
41
+ 'swap mean (GB)',
42
+ 'free memory min (GB)',
43
+ 'free memory mean (GB)',
44
+ 'load samples',
45
+ ] as const
46
+
47
+ interface StatsWindow {
48
+ since_ms: number
49
+ until_ms: number
50
+ }
51
+
52
+ interface RowLabel {
53
+ limit: string
54
+ period_days: number
55
+ }
56
+
57
+ function window_of(period_days: number, now_ms: number): StatsWindow {
58
+ return { since_ms: now_ms - period_days * MS_PER_DAY, until_ms: now_ms }
59
+ }
60
+
61
+ function in_window(entries: ReadonlyArray<LedgerEntry>, window: StatsWindow): Array<LedgerEntry> {
62
+ return entries.filter((entry) => {
63
+ const at = Date.parse(entry.at)
64
+
65
+ return at >= window.since_ms && at <= window.until_ms
66
+ })
67
+ }
68
+
69
+ function is_gate(entry: LedgerEntry): entry is GateEntry {
70
+ return entry.kind === 'gate'
71
+ }
72
+
73
+ function is_load(entry: LedgerEntry): entry is LoadEntry {
74
+ return entry.kind === 'load'
75
+ }
76
+
77
+ function is_number(value: number | undefined): value is number {
78
+ return value !== undefined
79
+ }
80
+
81
+ function mean(values: ReadonlyArray<number>): number | undefined {
82
+ if (values.length === 0) return undefined
83
+
84
+ return values.reduce((sum, value) => sum + value, 0) / values.length
85
+ }
86
+
87
+ function median(values: ReadonlyArray<number>): number | undefined {
88
+ if (values.length === 0) return undefined
89
+
90
+ const sorted = values.toSorted((left, right) => left - right)
91
+ const middle = Math.floor(sorted.length / HALF)
92
+
93
+ return sorted.length % HALF === 0 ? mean(sorted.slice(middle - 1, middle + 1)) : sorted[middle]
94
+ }
95
+
96
+ function peak(values: ReadonlyArray<number>): number | undefined {
97
+ return values.length === 0 ? undefined : Math.max(...values)
98
+ }
99
+
100
+ function lowest(values: ReadonlyArray<number>): number | undefined {
101
+ return values.length === 0 ? undefined : Math.min(...values)
102
+ }
103
+
104
+ type WorkingEntry = LoadEntry & { lanes: number }
105
+
106
+ function is_working(sample: LoadEntry): sample is WorkingEntry {
107
+ return (sample.lanes ?? 0) > 0
108
+ }
109
+
110
+ function sum_hours(gaps: ReadonlyArray<number>): number | undefined {
111
+ const active = gaps.filter((gap) => gap <= ACTIVE_GAP_MS).reduce((sum, gap) => sum + gap, 0)
112
+
113
+ return active > 0 ? active / MS_PER_HOUR : undefined
114
+ }
115
+
116
+ // The fallback for a period no sampler saw working: the gaps between consecutive work entries (merges
117
+ // and gates) no longer than `ACTIVE_GAP_MS`.
118
+ function gap_hours(entries: ReadonlyArray<LedgerEntry>): number | undefined {
119
+ const instants = entries
120
+ .filter((entry) => !is_load(entry))
121
+ .map((entry) => Date.parse(entry.at))
122
+ .toSorted((left, right) => left - right)
123
+
124
+ return sum_hours(
125
+ instants.slice(1).map((instant, index) => instant - (instants[index] ?? instant)),
126
+ )
127
+ }
128
+
129
+ // The time from each working sample to the next sample. The cap still applies, so a sampler stopped
130
+ // mid-run does not count its silence as work.
131
+ function sampled_hours(samples: ReadonlyArray<LoadEntry>): number | undefined {
132
+ const sorted = samples.toSorted((left, right) => Date.parse(left.at) - Date.parse(right.at))
133
+ const gaps = sorted
134
+ .slice(1)
135
+ .map((next, index) => ({ sample: sorted[index] ?? next, next }))
136
+ .filter(({ sample }) => is_working(sample))
137
+ .map(({ sample, next }) => Date.parse(next.at) - Date.parse(sample.at))
138
+
139
+ return sum_hours(gaps)
140
+ }
141
+
142
+ // The hours work was under way; `undefined` when nothing leaves any active time.
143
+ function active_hours(entries: ReadonlyArray<LedgerEntry>): number | undefined {
144
+ const samples = entries.filter(is_load)
145
+
146
+ return samples.some((sample) => is_working(sample)) ? sampled_hours(samples) : gap_hours(entries)
147
+ }
148
+
149
+ function gigabytes(megabytes: number | undefined): number | undefined {
150
+ return megabytes === undefined ? undefined : megabytes / MB_PER_GB
151
+ }
152
+
153
+ function cell(value: number | undefined): string {
154
+ return value === undefined ? MISSING : value.toFixed(DECIMALS)
155
+ }
156
+
157
+ function throughput_cells(entries: ReadonlyArray<LedgerEntry>): Array<string> {
158
+ const merges = entries.filter((entry) => entry.kind === 'merge').length
159
+ const hours = active_hours(entries)
160
+ const lanes = entries
161
+ .filter(is_load)
162
+ .filter(is_working)
163
+ .map((entry) => entry.lanes)
164
+ const per_hour = hours === undefined ? undefined : merges / hours
165
+
166
+ return [String(merges), cell(per_hour), cell(mean(lanes))]
167
+ }
168
+
169
+ function gate_cells(entries: ReadonlyArray<LedgerEntry>): Array<string> {
170
+ const minutes = entries.filter(is_gate).map((entry) => entry.elapsed_ms / MS_PER_MINUTE)
171
+
172
+ return [cell(median(minutes)), cell(peak(minutes))]
173
+ }
174
+
175
+ function load_cells(samples: ReadonlyArray<LoadEntry>): Array<string> {
176
+ const working = samples.filter(is_working)
177
+ const swap = samples.map((sample) => sample.swap_mb).filter(is_number)
178
+ const working_swap = working.map((sample) => sample.swap_mb).filter(is_number)
179
+ const free = samples.map((sample) => sample.free_mb)
180
+ const working_free = working.map((sample) => sample.free_mb)
181
+
182
+ return [
183
+ cell(peak(samples.map((sample) => sample.load))),
184
+ cell(mean(working.map((sample) => sample.load))),
185
+ cell(gigabytes(peak(swap))),
186
+ cell(gigabytes(mean(working_swap))),
187
+ cell(gigabytes(lowest(free))),
188
+ cell(gigabytes(mean(working_free))),
189
+ String(samples.length),
190
+ ]
191
+ }
192
+
193
+ function to_row(cells: ReadonlyArray<string>): string {
194
+ return `| ${cells.join(CELL_SEPARATOR)} |`
195
+ }
196
+
197
+ function header(): string {
198
+ return [to_row(COLUMNS), to_row(COLUMNS.map(() => '---'))].join('\n')
199
+ }
200
+
201
+ // The period's row: `entries` is the whole ledger, filtered here to `window`.
202
+ function row(entries: ReadonlyArray<LedgerEntry>, window: StatsWindow, label: RowLabel): string {
203
+ const period = in_window(entries, window)
204
+
205
+ return to_row([
206
+ label.limit,
207
+ String(label.period_days),
208
+ ...throughput_cells(period),
209
+ ...gate_cells(period),
210
+ ...load_cells(period.filter(is_load)),
211
+ ])
212
+ }
213
+
214
+ const lane_stats = {
215
+ COLUMNS,
216
+ MISSING,
217
+ header,
218
+ median,
219
+ row,
220
+ window_of,
221
+ }
222
+
223
+ export type { RowLabel, StatsWindow }
224
+ export { lane_stats }
@@ -0,0 +1,36 @@
1
+ import { git_command } from '#scripts/git/git-command'
2
+ import { run_hold } from '#scripts/run/hold/run-hold'
3
+ import { lane_await } from './lane-await'
4
+ import type { LaneInfo } from './lane-registry'
5
+
6
+ // Whether an open lane holds nothing worth keeping (joshuafolkken/kit#3289): no commit on its branch
7
+ // beyond the default branch, a clean work tree, and no child or ship supervisor alive for its issue. A
8
+ // `backlogrun` cut between `lane:open` and `lane:dispatch` leaves exactly such a lane, and `lane:open`
9
+ // refuses it as `already-open` — so the next launch of that issue failed on a lane with nothing in it.
10
+ //
11
+ // **Every unreadable answer is "not vacant"**: `is_tree_dirty` already reads an unreadable status as
12
+ // dirty, and a branch whose commits cannot be counted is kept rather than closed, because closing it
13
+ // deletes the branch and anything on it.
14
+ async function has_no_commits(lane: LaneInfo): Promise<boolean> {
15
+ try {
16
+ const base = await git_command.default_branch_reference()
17
+
18
+ return (await git_command.commit_count_beyond(base, lane.branch)) === 0
19
+ } catch {
20
+ return false
21
+ }
22
+ }
23
+
24
+ function is_occupied(lane: LaneInfo): boolean {
25
+ return lane.is_stranded || lane_await.is_process_running_default(lane.issue)
26
+ }
27
+
28
+ async function is_vacant(lane: LaneInfo): Promise<boolean> {
29
+ if (is_occupied(lane) || (await run_hold.is_tree_dirty(lane.directory))) return false
30
+
31
+ return await has_no_commits(lane)
32
+ }
33
+
34
+ const lane_vacant = { is_vacant }
35
+
36
+ export { lane_vacant }
@@ -1,6 +1,6 @@
1
1
  import { setTimeout as sleep } from 'node:timers/promises'
2
- import { run_ship_detach } from '#scripts/run/run-ship-detach'
3
- import { run_ship_probe } from '#scripts/run/run-ship-probe'
2
+ import { run_ship_detach } from '#scripts/run/ship/run-ship-detach'
3
+ import { run_ship_probe } from '#scripts/run/ship/run-ship-probe'
4
4
 
5
5
  const POLL_MS = 250
6
6
  type ShipEnding = 'none' | 'success' | 'failed' | 'abnormal'