@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
@@ -7,6 +7,7 @@ import { git_preflight } from '#scripts/git/git-preflight'
7
7
  import type { JoshResult } from '#scripts/josh/josh-run'
8
8
  import { error_text } from '#scripts/lib/error-message'
9
9
  import { live_evidence } from '#scripts/review/live-evidence'
10
+ import { run_ship_lock } from './run-ship-lock'
10
11
  import { run_ship_scoped } from './run-ship-scoped'
11
12
 
12
13
  // `josh ship`'s first stage (joshuafolkken/kit#2946). A detached ship used to meet the pull request's
@@ -71,13 +72,16 @@ async function caught(problems: Promise<Array<string>>): Promise<Array<string>>
71
72
  }
72
73
  }
73
74
 
75
+ // The lock file is asked here too (joshuafolkken/kit#3307), so a drift stops the ship before the gate
76
+ // rather than being rewritten by the pre-push hook's install under the push.
74
77
  async function asked_problems(request: PreflightRequest): Promise<Array<string>> {
75
- const [preflight, evidence] = await Promise.all([
78
+ const [preflight, evidence, lock] = await Promise.all([
76
79
  caught(git_preflight.problems_of({ cli_input: request.title, will_open_pr: true })),
77
80
  caught(evidence_problems(request.body_path)),
81
+ caught(run_ship_lock.lock_problems()),
78
82
  ])
79
83
 
80
- return [...preflight, ...evidence]
84
+ return [...preflight, ...evidence, ...lock]
81
85
  }
82
86
 
83
87
  async function stage(request: PreflightRequest): Promise<JoshResult> {
@@ -1,8 +1,8 @@
1
1
  import { git_command } from '#scripts/git/git-command'
2
2
  import { git_worktree } from '#scripts/git/git-worktree'
3
3
  import { error_text } from '#scripts/lib/error-message'
4
- import { run_hold } from './run-hold'
5
- import { run_preflight } from './run-preflight'
4
+ import { run_hold } from '#scripts/run/hold/run-hold'
5
+ import { run_preflight } from '#scripts/run/run-preflight'
6
6
  import { run_ship_stage, type ShipState } from './run-ship-stage'
7
7
 
8
8
  // The read side of a resumed `josh ship` (joshuafolkken/kit#2426): the repository's actual state, asked
@@ -3,10 +3,11 @@ import { lane_child_invocation } from '#scripts/lane/lane-child-invocation'
3
3
  import { lane_child_marker } from '#scripts/lane/lane-child-marker'
4
4
  import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
5
5
  import { lane_relaunch } from '#scripts/lane/lane-relaunch'
6
- import { run_event_stream } from './run-event-stream'
7
- import { run_event_stream_emit } from './run-event-stream-emit'
6
+ import { run_event_stream } from '#scripts/run/event/run-event-stream'
7
+ import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
8
8
  import type { ShipResume } from './run-ship-next'
9
9
  import { run_ship_stage, type Stage } from './run-ship-stage'
10
+ import { run_ship_stop_text } from './run-ship-stop-text'
10
11
 
11
12
  // How a detached `josh ship` supervisor hands a stopped stage back (joshuafolkken/kit#2428). The agent
12
13
  // ended when it handed the region over, so a red gate, a High/Medium review, a failed push or red CI has
@@ -29,7 +30,7 @@ const OPENAI_PROVIDER = 'openai'
29
30
  type ReturnOutcome = 'relaunched' | 'recorded'
30
31
 
31
32
  function stop_text(issue: string, stage: Stage, resume: ShipResume): string {
32
- return `#${issue} ${run_ship_stage.stop_reason(stage, resume.conflicts)} failed — pnpm josh ship --log ${issue}`
33
+ return run_ship_stop_text.format(issue, run_ship_stage.stop_reason(stage, resume.conflicts))
33
34
  }
34
35
 
35
36
  function can_relaunch(lane: LaneInfo | undefined): lane is LaneInfo {
@@ -6,7 +6,7 @@ import { stamp_file } from '#scripts/josh/stamp-file'
6
6
  import { lane_child_marker } from '#scripts/lane/lane-child-marker'
7
7
  import { openai_review_broker } from '#scripts/lane/openai-review-broker'
8
8
  import { review_record } from '#scripts/review/review-record'
9
- import { detached_launch } from './detached-launch'
9
+ import { detached_launch } from '#scripts/run/detached-launch'
10
10
  import { run_ship_review, type RoundOutcome, type ScoredVerdict } from './run-ship-review'
11
11
  import { run_ship_scoped, type Phase } from './run-ship-scoped'
12
12
 
@@ -59,8 +59,8 @@ function failure(out: string): JoshResult {
59
59
  return { code: FAILURE_EXIT_CODE, out }
60
60
  }
61
61
 
62
- function paths(root: string = PROJECT_ROOT): { brief: string; findings: string; log: string } {
63
- return openai_review_broker.review_paths(root)
62
+ function paths(): { brief: string; findings: string; log: string } {
63
+ return openai_review_broker.review_paths(PROJECT_ROOT)
64
64
  }
65
65
 
66
66
  async function josh(argv: ReadonlyArray<string>): Promise<JoshResult> {
@@ -39,7 +39,16 @@ async function scoped_pair(): Promise<JoshResult> {
39
39
  )
40
40
  }
41
41
 
42
- const run_ship_scoped = { run_phases, scoped_pair }
42
+ // The scoped pair and then `josh gate` — the gate stage, and the followup's gate over a merged tree
43
+ // before its push (joshuafolkken/kit#3307), so both leave the record the pre-push hook reuses.
44
+ async function scoped_gate(): Promise<JoshResult> {
45
+ return await run_phases([
46
+ scoped_pair,
47
+ async () => await josh_command.josh_run(['gate'], should_forward_stderr),
48
+ ])
49
+ }
50
+
51
+ const run_ship_scoped = { run_phases, scoped_gate, scoped_pair }
43
52
 
44
53
  export type { Phase }
45
54
  export { run_ship_scoped }
@@ -48,22 +48,20 @@ const PREFLIGHT_STEP: Step = {
48
48
  // with no green record (joshuafolkken/kit#2946). The commit step carries the `--skip-*` flags a resumed
49
49
  // ship needs, so an existing commit or push is never made twice.
50
50
  const COMMIT_STEPS: ReadonlyArray<Step> = [
51
- {
52
- stage: STAGE.GATE,
53
- header: run_ship.GATE_HEADER,
54
- run: async () =>
55
- await run_ship_scoped.run_phases([
56
- run_ship_scoped.scoped_pair,
57
- async () => await josh(['gate']),
58
- ]),
59
- },
60
- // joshuafolkken/kit#3221: the default branch merged again right before the commit, so the pull
61
- // request opens on a branch that is current; a clean merge goes on without waking anyone.
51
+ // joshuafolkken/kit#3221: the default branch merged again before the commit, so the pull request
52
+ // opens on a branch that is current; a clean merge goes on without waking anyone. It runs before the
53
+ // gate (joshuafolkken/kit#3307): a merge after it moved the merge base the gate's record pins, so the
54
+ // pre-push hook could never reuse that record and re-ran the whole unit suite on every merged ship.
62
55
  {
63
56
  stage: STAGE.SYNC,
64
57
  header: run_ship.SYNC_HEADER,
65
58
  run: async () => await run_ship_sync.sync_stage(),
66
59
  },
60
+ {
61
+ stage: STAGE.GATE,
62
+ header: run_ship.GATE_HEADER,
63
+ run: async () => await run_ship_scoped.scoped_gate(),
64
+ },
67
65
  {
68
66
  stage: STAGE.COMMIT,
69
67
  header: run_ship.COMMIT_HEADER,
@@ -87,7 +85,8 @@ const MERGE_STEPS: ReadonlyArray<Step> = [
87
85
  ]
88
86
 
89
87
  // `--review` (joshuafolkken/kit#2427) puts the supervised round-1 review in front of the gate: it
90
- // launches the gate itself, so the gate stage that follows reuses that tree's green record. It also
88
+ // launches the gate itself, so the gate stage that follows reuses that tree's green record unless the
89
+ // sync stage between them merged the default branch and changed the tree. It also
91
90
  // puts the round-2 pass between the commit and the followup (joshuafolkken/kit#2489), so the PR opens
92
91
  // between the rounds and round 2 runs beside CI — a no-op when round 1 left no fix delta.
93
92
  const REVIEW_STEP: Step = {
@@ -0,0 +1,13 @@
1
+ // The text a `josh ship` supervisor records when a stage stops, written and read in one place so the
2
+ // retrospective's per-stage count never drifts from the writer (joshuafolkken/kit#3245).
3
+ const STOP_PATTERN = /^#\d+ (\S+) failed — /u
4
+
5
+ function format(issue: string, reason: string): string {
6
+ return `#${issue} ${reason} failed — pnpm josh ship --log ${issue}`
7
+ }
8
+
9
+ function reason_of(text: string): string | undefined {
10
+ return STOP_PATTERN.exec(text)?.[1]
11
+ }
12
+
13
+ export const run_ship_stop_text = { format, reason_of }
@@ -26,6 +26,7 @@ const MAX_MERGE_RETRIES = 2
26
26
  const CONFLICT_NOTE = 'merging the default branch left unmerged paths:'
27
27
  const DEFERRED_PREFIX = 'merge deferred to the followup: '
28
28
  const CURRENT_SUFFIX = ' brings nothing in'
29
+ const MERGED_PREFIX = 'merged '
29
30
 
30
31
  // A stage's result, plus the paths a stopped merge left unmerged — carried to the stop prompt.
31
32
  interface StepResult extends JoshResult {
@@ -52,21 +53,24 @@ function conflict_result(files: ReadonlyArray<string>, lead: string): StepResult
52
53
  return { code: FAILURE_EXIT_CODE, out, conflicts: files }
53
54
  }
54
55
 
56
+ function sync_note(outcome: Exclude<MergeOutcome, { kind: 'conflict' }>): string {
57
+ if (outcome.kind === 'refused') return `${DEFERRED_PREFIX}${outcome.message}`
58
+
59
+ return outcome.kind === 'current'
60
+ ? `${outcome.branch}${CURRENT_SUFFIX}`
61
+ : `${MERGED_PREFIX}${outcome.branch}`
62
+ }
63
+
55
64
  // A refusal does not stop the commit: the guard refuses only uncommitted work the default branch also
56
65
  // touches, and once that work is committed the followup merges again on a clean tree. A merge that changed
57
- // the tree meets the scoped pair again, so the commit never carries an unchecked merge.
66
+ // the tree is checked by the gate stage that follows (joshuafolkken/kit#3307), so the commit never carries
67
+ // an unchecked merge and the gate's record names the merge base the push carries.
58
68
  async function sync_stage(): Promise<StepResult> {
59
69
  const outcome = await merge_outcome()
60
70
 
61
71
  if (outcome.kind === 'conflict') return conflict_result(outcome.files, '')
62
- if (outcome.kind === 'merged') return await run_ship_scoped.scoped_pair()
63
-
64
- const out =
65
- outcome.kind === 'current'
66
- ? `${outcome.branch}${CURRENT_SUFFIX}`
67
- : `${DEFERRED_PREFIX}${outcome.message}`
68
72
 
69
- return { code: SUCCESS_EXIT_CODE, out }
73
+ return { code: SUCCESS_EXIT_CODE, out: sync_note(outcome) }
70
74
  }
71
75
 
72
76
  // Any read that fails answers "not conflicting": the followup failure then stands as it was.
@@ -80,6 +84,18 @@ async function is_conflicting(): Promise<boolean> {
80
84
  }
81
85
  }
82
86
 
87
+ // The merged tree is gated before it is pushed (joshuafolkken/kit#3307): the push then carries the tree
88
+ // the gate's record describes, so the pre-push hook reuses it rather than re-running the unit suite.
89
+ async function push_merged(title: string): Promise<StepResult | undefined> {
90
+ const gate = await run_ship_scoped.scoped_gate()
91
+
92
+ if (gate.code !== SUCCESS_EXIT_CODE) return gate
93
+
94
+ const push = await josh(['git', '-y', '--skip-commit', '--skip-pr', title])
95
+
96
+ return push.code === SUCCESS_EXIT_CODE ? undefined : push
97
+ }
98
+
83
99
  // The result to stop on, or `undefined` once a clean merge is pushed and the followup can wait again.
84
100
  async function merge_again(title: string, failed: JoshResult): Promise<StepResult | undefined> {
85
101
  if (!(await is_conflicting())) return failed
@@ -89,9 +105,7 @@ async function merge_again(title: string, failed: JoshResult): Promise<StepResul
89
105
  if (outcome.kind === 'conflict') return conflict_result(outcome.files, failed.out)
90
106
  if (outcome.kind !== 'merged') return failed
91
107
 
92
- const push = await josh(['git', '-y', '--skip-commit', '--skip-pr', title])
93
-
94
- return push.code === SUCCESS_EXIT_CODE ? undefined : push
108
+ return await push_merged(title)
95
109
  }
96
110
 
97
111
  async function followup_stage(
@@ -3,7 +3,7 @@ import { git_stash } from '#scripts/git/stash/git-stash'
3
3
  import { lane_close } from '#scripts/lane/lane-close'
4
4
  import { lane_registry, type LaneInfo } from '#scripts/lane/lane-registry'
5
5
  import { error_text } from '#scripts/lib/error-message'
6
- import { run_hold } from './run-hold'
6
+ import { run_hold } from '#scripts/run/hold/run-hold'
7
7
  import { run_tidy, type Outcome, type Verdict } from './run-tidy'
8
8
 
9
9
  // The lane half of `josh run:tidy` (joshuafolkken/kit#2701): each lane whose issue merged, read for
@@ -4,15 +4,17 @@ import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
5
  import type { AgentProfile } from '#scripts/agent/agent-role-profile'
6
6
  import { cli_flags } from '#scripts/lib/cli-flags'
7
+ import { poll } from '#scripts/lib/poll'
7
8
  import { telegram_notify } from '#scripts/notify/telegram-notify'
8
- import { run_carry, type CarryRead } from './run-carry'
9
- import { run_event_stream } from './run-event-stream'
10
- import { run_headless } from './run-headless'
9
+ import { run_carry, type CarryRead } from '#scripts/run/carry/run-carry'
10
+ import { run_event_stream } from '#scripts/run/event/run-event-stream'
11
+ import { run_headless } from '#scripts/run/run-headless'
11
12
  import { run_wake, type RunWake, type WakeStopReason, type WakeTidyResult } from './run-wake'
12
13
  import { run_wake_describe, type WakeContext } from './run-wake-describe'
13
14
  import { run_wake_driver } from './run-wake-driver'
14
15
  import { run_wake_handoff } from './run-wake-handoff'
15
16
  import { run_wake_loop, type LoopPorts, type LoopStop } from './run-wake-loop'
17
+ import { run_wake_restart, type RestartPorts } from './run-wake-restart'
16
18
  import { run_wake_session, type LaunchResult } from './run-wake-session'
17
19
 
18
20
  // `josh run:wake --start | --list | --stop | --loop` — the supervisor that continues a cut
@@ -309,6 +311,22 @@ function tidy_up(target: string): void {
309
311
  if (run_wake.tidy_own_wake(target) === SUPERSEDED_RESULT) note_to_stderr(SUPERSEDED_NOTE)
310
312
  }
311
313
 
314
+ function restart_ports(context: WakeContext, ports: LoopPorts, interval_ms: number): RestartPorts {
315
+ return {
316
+ pass: async () => await run_wake_loop.run_loop(context.wake_target, ports, interval_ms),
317
+ is_resumable: () => run_carry.read_carry(context.carry_target).kind === 'carried',
318
+ pause: async () => {
319
+ await poll.sleep(interval_ms)
320
+ },
321
+ note: note_to_stderr,
322
+ }
323
+ }
324
+
325
+ // A failed pass is restarted while the carry record is still resumable (`run-wake-restart.ts`), so
326
+ // only a failure past the restart bound reaches `finish` and its warning (joshuafolkken/kit#3332).
327
+ // **The record is claimed once and held across every restart**, so a `--stop` landing in the pause
328
+ // between two passes still finds it, signals this process, and the next pass reads the removal as the
329
+ // person's stop rather than claiming a fresh record and resuming.
312
330
  async function loop(context: WakeContext, interval_ms: number): Promise<number> {
313
331
  const read = run_carry.read_carry(context.carry_target)
314
332
 
@@ -321,11 +339,8 @@ async function loop(context: WakeContext, interval_ms: number): Promise<number>
321
339
 
322
340
  if (claimed === undefined) return report(RUNNING_VERDICT)
323
341
 
324
- const stop = await run_wake_loop.run_loop(
325
- context.wake_target,
326
- ports_for(context, claimed.profile ?? profile),
327
- interval_ms,
328
- )
342
+ const ports = ports_for(context, claimed.profile ?? profile)
343
+ const stop = await run_wake_restart.supervise(restart_ports(context, ports, interval_ms))
329
344
 
330
345
  tidy_up(context.wake_target)
331
346
 
@@ -1,9 +1,9 @@
1
1
  import { agent_role_profile } from '#scripts/agent/agent-role-profile'
2
2
  import { gh_spawn } from '#scripts/gh/gh-spawn'
3
3
  import { issue_citation } from '#scripts/rules/issue-citation'
4
- import { run_carry, type CarryRead } from './run-carry'
5
- import { run_event_stream } from './run-event-stream'
6
- import { run_liveness } from './run-liveness'
4
+ import { run_carry, type CarryRead } from '#scripts/run/carry/run-carry'
5
+ import { run_event_stream } from '#scripts/run/event/run-event-stream'
6
+ import { run_liveness } from '#scripts/run/run-liveness'
7
7
  import { run_wake, type RunWake } from './run-wake'
8
8
 
9
9
  // The `--list` presentation of a wake record (joshuafolkken/kit#2407 split it out of `run-wake-cli.ts`,
@@ -1,6 +1,6 @@
1
1
  import { josh_command, type JoshResult } from '#scripts/josh/josh-run'
2
- import { run_carry, type CarryRead } from './run-carry'
3
- import { run_invocation } from './run-invocation'
2
+ import { run_carry, type CarryRead } from '#scripts/run/carry/run-carry'
3
+ import { run_invocation } from '#scripts/run/run-invocation'
4
4
  import type { DriveResult } from './run-wake-loop'
5
5
 
6
6
  const SUCCESS = 0
@@ -114,7 +114,12 @@ function failure_note(result: JoshResult): string {
114
114
  return [cause_of(result.out), details_tail(result.err ?? '')].filter(Boolean).join('\n')
115
115
  }
116
116
 
117
- function read_result(result: JoshResult, target: string, invocation: string): DriveResult {
117
+ function read_result(
118
+ result: JoshResult | undefined,
119
+ target: string,
120
+ invocation: string,
121
+ ): DriveResult {
122
+ if (result === undefined) return { kind: 'released' }
118
123
  if (result.code !== SUCCESS) return { kind: 'failed', note: failure_note(result) }
119
124
 
120
125
  return driver_result(result.out, run_carry.read_carry(target), invocation, result.err)
@@ -128,7 +133,51 @@ function claim_record(target: string, invocation: string): boolean {
128
133
  return run_carry.adopt_carry(target, current.carry, run_carry.owner_of(process.pid)) !== undefined
129
134
  }
130
135
 
131
- async function drive(target: string): Promise<DriveResult> {
136
+ // **A verdict with nothing to judge is re-driven here, never handed to a session** (joshuafolkken/kit#3245).
137
+ // A woken session met with `window`, `merge busy` or `merge retry` only cut and handed the loop back, and
138
+ // the supervisor then ran the very same driver again — a model round trip, at a cache write each, that
139
+ // decided nothing. The record is already this process's, so the rerun needs no fresh claim; the limit
140
+ // keeps a driver that never settles from holding the supervisor past its own checks.
141
+ const RERUN_VERDICTS: ReadonlyArray<string> = ['window', 'merge busy', 'merge retry']
142
+ const RERUN_LIMIT = 10
143
+ const RERUN_PAUSE_MS = 60_000
144
+
145
+ type Pause = (ms: number) => Promise<void>
146
+
147
+ async function pause(ms: number): Promise<void> {
148
+ await new Promise((resolve) => {
149
+ setTimeout(resolve, ms)
150
+ })
151
+ }
152
+
153
+ function is_mechanical(result: JoshResult): boolean {
154
+ const verdict = result.out.split('\n', 1)[FIRST_LINE] ?? ''
155
+
156
+ return (
157
+ result.code === SUCCESS &&
158
+ RERUN_VERDICTS.some((entry) => verdict === entry || verdict.startsWith(`${entry} `))
159
+ )
160
+ }
161
+
162
+ // A record that expired or was removed during the pause answers `undefined`: its stale verdict must not
163
+ // reach a session, so the supervisor's own next pass re-reads the record and ends the run as `expired`
164
+ // or `stopped`; a rerun would only fail on it and report the run as `failed`.
165
+ async function run_driver(
166
+ target: string,
167
+ args: ReadonlyArray<string>,
168
+ wait: Pause,
169
+ remaining: number,
170
+ ): Promise<JoshResult | undefined> {
171
+ const result = await josh_command.josh_run(args, true)
172
+ if (remaining === 0 || !is_mechanical(result)) return result
173
+
174
+ await wait(RERUN_PAUSE_MS)
175
+ if (run_carry.read_carry(target).kind !== 'carried') return undefined
176
+
177
+ return await run_driver(target, args, wait, remaining - 1)
178
+ }
179
+
180
+ async function drive(target: string, wait: Pause = pause): Promise<DriveResult> {
132
181
  const read = run_carry.read_carry(target)
133
182
  if (read.kind !== 'carried') return { kind: 'failed', note: 'The carry record changed.' }
134
183
 
@@ -139,9 +188,11 @@ async function drive(target: string): Promise<DriveResult> {
139
188
  return { kind: 'failed', note: 'The driver could not claim the carry record.' }
140
189
  }
141
190
 
142
- const result = await josh_command.josh_run(args, true)
143
-
144
- return read_result(result, target, read.carry.invocation)
191
+ return read_result(
192
+ await run_driver(target, args, wait, RERUN_LIMIT),
193
+ target,
194
+ read.carry.invocation,
195
+ )
145
196
  }
146
197
 
147
- export const run_wake_driver = { drive, driver_args, driver_result }
198
+ export const run_wake_driver = { RERUN_LIMIT, drive, driver_args, driver_result }
@@ -1,5 +1,5 @@
1
1
  import { stamp_file } from '#scripts/josh/stamp-file'
2
- import { run_carry } from './run-carry'
2
+ import { run_carry } from '#scripts/run/carry/run-carry'
3
3
 
4
4
  // The supervisor's recovery hand-off (joshuafolkken/kit#2437). A session that claimed the carry record
5
5
  // and then died without `run:carry --cut` leaves it `carried`, owned by a gone process, with no
@@ -1,4 +1,4 @@
1
- import type { CarryRead } from './run-carry'
1
+ import type { CarryRead } from '#scripts/run/carry/run-carry'
2
2
  import {
3
3
  run_wake,
4
4
  type RunWake,
@@ -56,8 +56,13 @@ interface LoopPorts {
56
56
  now: () => Date
57
57
  }
58
58
 
59
+ // `released`: the carry record ended while the driver paused, so there is nothing to hand on and the
60
+ // next pass decides from the record how the run ends.
59
61
  type DriveResult =
60
- { kind: 'finished' } | { kind: 'judgment'; material: string } | { kind: 'failed'; note: string }
62
+ | { kind: 'finished' }
63
+ | { kind: 'released' }
64
+ | { kind: 'judgment'; material: string }
65
+ | { kind: 'failed'; note: string }
61
66
 
62
67
  interface LoopStop {
63
68
  reason: WakeStopReason
@@ -89,6 +94,7 @@ async function wake_step(wake: RunWake, ports: LoopPorts): Promise<StepOutcome>
89
94
  const driven = await ports.drive()
90
95
  if (driven.kind === 'finished') return stopped('ended')
91
96
  if (driven.kind === 'failed') return stopped(FAILED_REASON, driven.note)
97
+ if (driven.kind === 'released') return { kind: 'continue', wake }
92
98
 
93
99
  const session_id = ports.new_session_id()
94
100
 
@@ -0,0 +1,58 @@
1
+ import { run_wake_loop, type LoopStop } from './run-wake-loop'
2
+
3
+ // **A supervisor that fails while the run is still resumable restarts itself** (joshuafolkken/kit#3332).
4
+ // A `backlog:drive` that exited on a transient `main:sync` failure ended the supervisor as `failed`,
5
+ // and the run then sat for forty minutes with a valid carry record, waiting for one `run:wake --start`
6
+ // that only a person was left to type — joshuafolkken/kit#3297's "resume it yourself" rule binds an AI
7
+ // session, and once the supervisor is gone there is none to read it.
8
+ //
9
+ // **Bounded, and only the bound reaches a person.** A failure that is not transient fails again on
10
+ // every pass, so the restarts stop at `RESTART_LIMIT` and the last failure is handed back for the
11
+ // ordinary warning, its note saying how many restarts went before it.
12
+
13
+ const RESTART_LIMIT = 3
14
+
15
+ interface RestartPorts {
16
+ // One run of the loop over the record the caller claimed once and holds across every restart, so a
17
+ // `--stop` landing in the pause is read by the next pass as the person's stop.
18
+ pass: () => Promise<LoopStop>
19
+ // Whether the carry record can still be continued — `carried`, neither `expired` nor gone.
20
+ is_resumable: () => boolean
21
+ pause: () => Promise<void>
22
+ note: (text: string) => void
23
+ }
24
+
25
+ function is_restartable(stop: LoopStop, restarts: number, ports: RestartPorts): boolean {
26
+ return (
27
+ stop.reason === run_wake_loop.FAILED_REASON && restarts < RESTART_LIMIT && ports.is_resumable()
28
+ )
29
+ }
30
+
31
+ function restart_note(stop: LoopStop, restarts: number): string {
32
+ return `the supervisor failed (${stop.note ?? 'no note'}); restarting it, ${String(restarts + 1)} of ${String(RESTART_LIMIT)}`
33
+ }
34
+
35
+ function with_restart_count(stop: LoopStop, restarts: number): LoopStop {
36
+ if (restarts === 0 || stop.reason !== run_wake_loop.FAILED_REASON) return stop
37
+
38
+ return {
39
+ ...stop,
40
+ note: [stop.note, `after ${String(restarts)} automatic restarts`].filter(Boolean).join('\n'),
41
+ }
42
+ }
43
+
44
+ async function supervise(ports: RestartPorts, restarts = 0): Promise<LoopStop> {
45
+ const stop = await ports.pass()
46
+
47
+ if (!is_restartable(stop, restarts, ports)) return with_restart_count(stop, restarts)
48
+
49
+ ports.note(restart_note(stop, restarts))
50
+ await ports.pause()
51
+
52
+ return await supervise(ports, restarts + 1)
53
+ }
54
+
55
+ const run_wake_restart = { RESTART_LIMIT, supervise }
56
+
57
+ export { run_wake_restart }
58
+ export type { RestartPorts }
@@ -1,8 +1,8 @@
1
1
  import { agent_argv, type AgentArgvResult } from '#scripts/agent/agent-argv'
2
2
  import { agent_role_profile, type AgentProfile } from '#scripts/agent/agent-role-profile'
3
3
  import { claude_agent_argv } from '#scripts/agent/claude-agent-argv'
4
- import { detached_launch, type LaunchArgv } from './detached-launch'
5
- import { run_invocation } from './run-invocation'
4
+ import { detached_launch, type LaunchArgv } from '#scripts/run/detached-launch'
5
+ import { run_invocation } from '#scripts/run/run-invocation'
6
6
 
7
7
  // How the supervisor starts things: the next agent session, and — at `--start` — its own detached
8
8
  // self. Both go through one `launch`, because the two differ only in what is being run
@@ -159,5 +159,5 @@ const run_wake_session = {
159
159
  wake_argv,
160
160
  }
161
161
 
162
- export type { LaunchRequest, LaunchResult } from './detached-launch'
162
+ export type { LaunchRequest, LaunchResult } from '#scripts/run/detached-launch'
163
163
  export { run_wake_session }
@@ -3,8 +3,8 @@ import { backlog_budget } from '#scripts/backlog/backlog-budget'
3
3
  import { process_identity } from '#scripts/josh/process-identity'
4
4
  import { process_owner_schema } from '#scripts/josh/process-owner'
5
5
  import { stamp_file } from '#scripts/josh/stamp-file'
6
+ import type { CarryRead } from '#scripts/run/carry/run-carry'
6
7
  import { z } from 'zod'
7
- import type { CarryRead } from './run-carry'
8
8
 
9
9
  // joshuafolkken/kit#1719. joshuafolkken/kit#1714 made a `backlogrun`'s budget survive the session cut
10
10
  // and left out the one thing that starts the next session, so the record was carried and the
@@ -1,38 +1,37 @@
1
- // `setup-ci` only writes shims and adds them to PATH on a CI runner, so a local `pnpm install` is
2
- // scanned only when the developer has enabled safe-chain's own shell integration. That integration
3
- // runs pnpm behind safe-chain's registry proxy and hands `GLOBAL_AGENT_HTTP_PROXY` to it, which a bare
4
- // pnpm never sets — the one signal a lifecycle script can read (joshuafolkken/kit#2707).
5
- const SETUP_CI_CMD = 'pnpm dlx @aikidosec/safe-chain setup-ci'
6
- const LEGACY_PREINSTALL_RE = /^pnpm dlx @aikidosec\/safe-chain(?:@\S+)? setup-ci$/u
1
+ // The `preinstall` kit writes fetches nothing: it only checks that safe-chain is scanning the install
2
+ // and warns when it is not (joshuafolkken/kit#3269). It used to run `pnpm dlx @aikidosec/safe-chain
3
+ // setup-ci`, which downloaded an unverified package on every install and — `setup-ci` acting only on
4
+ // a CI runner, where kit installs with `--ignore-scripts` — protected nothing. safe-chain's shell
5
+ // integration runs pnpm behind its registry proxy and hands `GLOBAL_AGENT_HTTP_PROXY` to it, which a
6
+ // bare pnpm never sets — the one signal a lifecycle script can read (joshuafolkken/kit#2707).
7
+ const INSTALL_GUIDE_ANCHOR = 'installing-safe-chain'
8
+ const INSTALL_GUIDE_URL = `https://github.com/joshuafolkken/kit/blob/main/SECURITY.md#${INSTALL_GUIDE_ANCHOR}`
9
+ // `.pnpmfile.mjs` recognizes the command by the package name, so the hint names it.
7
10
  const INTEGRATION_HINT =
8
- 'Warning: safe-chain is not scanning this install because its shell integration is not active.' +
9
- ' Install safe-chain (https://github.com/AikidoSec/safe-chain#installation) or run: safe-chain setup' +
11
+ 'Warning: safe-chain (@aikidosec/safe-chain) is not scanning this install because its shell' +
12
+ ` integration is not active. Install it with the hash-verified steps at ${INSTALL_GUIDE_URL}` +
10
13
  ' - then restart your terminal.'
11
14
  // `preinstall` runs before any dependency is installed, kit included, so the check is plain Node with
12
15
  // no imports. It never fails the install and stays silent on CI, where the runner is set up instead.
13
16
  // The source avoids double quotes, `$` and backticks so the shell passes it to Node verbatim.
14
17
  const LOCAL_INTEGRATION_CHECK_SOURCE = `if(!process.env.CI&&!process.env.GLOBAL_AGENT_HTTP_PROXY)console.warn('${INTEGRATION_HINT}')`
15
- const LOCAL_INTEGRATION_CHECK_CMD = `node -e "${LOCAL_INTEGRATION_CHECK_SOURCE}"`
18
+ const SAFE_CHAIN_CMD = `node -e "${LOCAL_INTEGRATION_CHECK_SOURCE}"`
19
+ // Every `preinstall` an earlier kit wrote: `setup-ci` alone, pinned or not, or followed by the
20
+ // `node -e` check of any earlier wording.
21
+ const LEGACY_PREINSTALL_RE =
22
+ /^pnpm dlx @aikidosec\/safe-chain(?:@\S+)? setup-ci(?: && node -e "[^"]*")?$/u
16
23
 
17
- function with_local_check(setup_command: string): string {
18
- return `${setup_command} && ${LOCAL_INTEGRATION_CHECK_CMD}`
19
- }
20
-
21
- // Upgrade a `preinstall` kit wrote before the check existed, pinned version included; any other
22
- // value is the consumer's own and is left exactly as it is.
24
+ // Replace a `preinstall` kit wrote earlier; any other value is the consumer's own and is left exactly
25
+ // as it is.
23
26
  function migrate_preinstall(value: string): string {
24
- if (!LEGACY_PREINSTALL_RE.test(value)) return value
25
-
26
- return with_local_check(value)
27
+ return LEGACY_PREINSTALL_RE.test(value) ? SAFE_CHAIN_CMD : value
27
28
  }
28
29
 
29
- const SAFE_CHAIN_CMD = with_local_check(SETUP_CI_CMD)
30
-
31
30
  const safe_chain_preinstall = {
32
31
  SAFE_CHAIN_CMD,
33
- LOCAL_INTEGRATION_CHECK_CMD,
34
32
  LOCAL_INTEGRATION_CHECK_SOURCE,
35
33
  INTEGRATION_HINT,
34
+ INSTALL_GUIDE_ANCHOR,
36
35
  migrate_preinstall,
37
36
  }
38
37