@joshuafolkken/kit 1.1166.0 → 1.1229.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (437) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/settings.json +2 -2
  3. package/.claude/skills/dependency-update/SKILL.md +4 -8
  4. package/.claude/skills/epic-commands/SKILL.md +2 -2
  5. package/.claude/skills/epic-commands/epic-bundle.md +1 -1
  6. package/.claude/skills/workflow-commands/SKILL.md +2 -4
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +9 -5
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +29 -9
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +7 -2
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +24 -15
  13. package/.claude/skills/workflow-commands/chain-rule.md +1 -1
  14. package/.claude/skills/workflow-commands/delegation.md +2 -2
  15. package/.claude/skills/workflow-commands/followup.md +5 -0
  16. package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
  17. package/.claude/skills/workflow-commands/halfrun.md +1 -1
  18. package/.claude/skills/workflow-commands/issue-scout.md +1 -1
  19. package/.claude/skills/workflow-commands/kickoff.md +1 -1
  20. package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
  21. package/.claude/skills/workflow-commands/pre-gate-cut.md +10 -5
  22. package/.claude/skills/workflow-commands/prerequisite.md +2 -2
  23. package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
  24. package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
  25. package/.codex/hooks.json +2 -2
  26. package/.cursorrules +6 -41
  27. package/.github/actions/setup-node/action.yml +1 -1
  28. package/.github/workflows/auto-tag.yml +3 -4
  29. package/.github/workflows/ci.yml +47 -33
  30. package/.github/workflows/dependabot-auto-merge.yml +13 -6
  31. package/.github/workflows/publish.yml +48 -4
  32. package/.github/workflows/sonar-qube.yml +3 -0
  33. package/.pnpmfile.mjs +4 -3
  34. package/.prettierignore +8 -2
  35. package/CLAUDE.md +18 -20
  36. package/README.md +26 -19
  37. package/SECURITY.md +15 -1
  38. package/dist/CLAUDE.md +18 -20
  39. package/dist/commands/document-section-cli.js +734 -0
  40. package/dist/hooks/audit-provision.js +346 -0
  41. package/dist/hooks/batch-guard.js +5 -4
  42. package/dist/hooks/{chunk-DYAVAM6S.js → chunk-55HHG5PX.js} +1405 -631
  43. package/dist/hooks/{chunk-A6P7PN7P.js → chunk-5VMKCCC4.js} +25 -10
  44. package/dist/hooks/chunk-5VNDSNG3.js +12 -0
  45. package/dist/hooks/{chunk-5KTCMCOT.js → chunk-6HJJXRBX.js} +4 -2
  46. package/dist/hooks/chunk-CDY4NFQG.js +15 -0
  47. package/dist/hooks/chunk-ELCN5LCH.js +135 -0
  48. package/dist/hooks/{chunk-ENA7P5FB.js → chunk-F56MFS4B.js} +3 -3
  49. package/dist/hooks/{chunk-ANJ2WGFD.js → chunk-FIVDHQGT.js} +7 -6
  50. package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
  51. package/dist/hooks/{chunk-GU5MATVE.js → chunk-JUDKDCDP.js} +499 -389
  52. package/dist/hooks/{chunk-ZDZ555YH.js → chunk-NV2JRZZO.js} +743 -909
  53. package/dist/hooks/{chunk-V2BTOZHT.js → chunk-RNWHN5BP.js} +3 -3
  54. package/dist/hooks/{chunk-URJA6XY4.js → chunk-TQNPDZNH.js} +4 -4
  55. package/dist/hooks/{chunk-GYNGP7HI.js → chunk-VC3PZPZO.js} +1 -1
  56. package/dist/hooks/codex-hook-adapter.js +14 -12
  57. package/dist/hooks/duplicate-read-guard.js +6 -5
  58. package/dist/hooks/format-edited.js +4 -3
  59. package/dist/hooks/inputs.json +232 -210
  60. package/dist/hooks/investigation-guard.js +6 -5
  61. package/dist/hooks/pretool-guard.js +12 -10
  62. package/dist/hooks/session-lang.js +2 -4
  63. package/dist/hooks/stop-guard.js +81 -59
  64. package/dist/josh.js +872 -75
  65. package/dist/version/index.d.ts +7 -2
  66. package/dist/version/index.js +152 -75
  67. package/eslint/base.js +23 -1
  68. package/eslint/rules/code-quality.js +3 -0
  69. package/eslint/rules/early-return-one-liner.js +98 -0
  70. package/eslint/rules/unicorn.js +16 -1
  71. package/package.json +7 -6
  72. package/pnpm-workspace.yaml +9 -4
  73. package/prettier/basic.d.ts +3 -1
  74. package/prettier/index.d.ts +3 -1
  75. package/prettier/static.d.ts +3 -1
  76. package/prompts/coding-standards.md +1 -1
  77. package/prompts/collaboration-workflow/file-edits.md +4 -4
  78. package/prompts/collaboration-workflow/glossary.md +1 -1
  79. package/prompts/collaboration-workflow/operating-rules.md +17 -9
  80. package/prompts/collaboration-workflow/overview.md +2 -2
  81. package/prompts/collaboration-workflow/principles.md +14 -12
  82. package/prompts/collaboration-workflow/report-format.md +1 -1
  83. package/prompts/collaboration-workflow/residency.md +1 -1
  84. package/prompts/collaboration-workflow/rule-delivery.md +6 -6
  85. package/prompts/collaboration-workflow/shell-body.md +6 -6
  86. package/prompts/collaboration-workflow/turn-batching.md +1 -1
  87. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
  88. package/prompts/collaboration-workflow/wip-cap.md +10 -10
  89. package/prompts/review-rubric.md +1 -2
  90. package/prompts/review.md +1 -1
  91. package/scripts/agent/agent-headless.ts +29 -0
  92. package/scripts/agent/agent-launch-environment.ts +6 -2
  93. package/scripts/agent/codex-agent-argv.ts +2 -1
  94. package/scripts/auto-ok/auto-ok-cli.ts +1 -1
  95. package/scripts/backlog/backlog-arrival.ts +3 -3
  96. package/scripts/backlog/backlog-budget.ts +1 -1
  97. package/scripts/backlog/backlog-drive-args.ts +135 -0
  98. package/scripts/backlog/backlog-drive-cli.ts +11 -137
  99. package/scripts/backlog/backlog-drive-launch.ts +39 -0
  100. package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
  101. package/scripts/backlog/backlog-drive-named.ts +3 -3
  102. package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
  103. package/scripts/backlog/backlog-drive-owner.ts +1 -1
  104. package/scripts/backlog/backlog-drive-restore.ts +1 -1
  105. package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
  106. package/scripts/backlog/backlog-drive.ts +1 -1
  107. package/scripts/backlog/backlog-next.ts +31 -9
  108. package/scripts/backlog/backlog-offer-cli.ts +6 -6
  109. package/scripts/backlog/backlog-offer.ts +1 -1
  110. package/scripts/backlog/backlog-plan-cli.ts +7 -6
  111. package/scripts/backlog/backlog-pool.ts +57 -11
  112. package/scripts/backlog/backlog-rank.ts +18 -9
  113. package/scripts/backlog/backlog-ready.ts +1 -1
  114. package/scripts/backlog/backlog-restructure.ts +152 -0
  115. package/scripts/backlog/backlog-scope.ts +1 -1
  116. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  117. package/scripts/backlog/backlog-stalled.ts +1 -1
  118. package/scripts/backlog/backlog-waves.ts +15 -17
  119. package/scripts/build/build-codex-hooks.ts +3 -1
  120. package/scripts/build/build-commands.ts +66 -0
  121. package/scripts/build/build-hooks.ts +2 -1
  122. package/scripts/build/build-library.ts +12 -3
  123. package/scripts/build/build.ts +70 -0
  124. package/scripts/build/local-bin.ts +3 -13
  125. package/scripts/cost-runtime/context-cut-threshold.ts +1 -9
  126. package/scripts/cost-runtime/cost-verdict.ts +15 -28
  127. package/scripts/delegation/investigation-reads.ts +3 -1
  128. package/scripts/discovery/repo-origin.ts +5 -5
  129. package/scripts/doctor/doctor-consumer.ts +2 -2
  130. package/scripts/doctor/doctor-io.ts +2 -3
  131. package/scripts/document/capped-print-part.ts +30 -0
  132. package/scripts/document/capped-print.ts +3 -4
  133. package/scripts/document/document-byte-budget.ts +11 -2
  134. package/scripts/document/document-scan.ts +117 -0
  135. package/scripts/document/document-section.ts +143 -16
  136. package/scripts/document/entry-read-budget.ts +5 -3
  137. package/scripts/document/entry-read-set.ts +10 -89
  138. package/scripts/document/read-set-cost.ts +98 -0
  139. package/scripts/epic/epic-add-body.ts +1 -0
  140. package/scripts/epic/epic-audit-checks.ts +1 -1
  141. package/scripts/epic/epic-audit-rationale.ts +4 -3
  142. package/scripts/epic/epic-bundle-cli.ts +14 -3
  143. package/scripts/epic/epic-bundle-poll.ts +41 -0
  144. package/scripts/epic/epic-busy.ts +1 -1
  145. package/scripts/epic/epic-candidate-confirm.ts +3 -2
  146. package/scripts/epic/epic-classify.ts +1 -1
  147. package/scripts/epic/epic-cli-argv.ts +78 -46
  148. package/scripts/epic/epic-cli-remove.ts +11 -19
  149. package/scripts/epic/epic-cli.ts +74 -73
  150. package/scripts/epic/epic-close-comment.ts +1 -1
  151. package/scripts/epic/epic-close.ts +1 -1
  152. package/scripts/epic/epic-cross-repo.ts +5 -5
  153. package/scripts/epic/epic-graph.ts +1 -1
  154. package/scripts/epic/epic-index.ts +1 -1
  155. package/scripts/epic/epic-issue.ts +2 -6
  156. package/scripts/epic/epic-lane-offer.ts +2 -2
  157. package/scripts/epic/epic-next.ts +29 -42
  158. package/scripts/epic/epic-solo-stale.ts +1 -1
  159. package/scripts/epic/epic-triage.ts +8 -4
  160. package/scripts/epic/epic-validate.ts +1 -1
  161. package/scripts/followup/git-followup-finish.ts +2 -2
  162. package/scripts/followup/git-followup-label.ts +2 -2
  163. package/scripts/followup/git-followup-merged.ts +2 -2
  164. package/scripts/followup/git-followup-stages.ts +9 -0
  165. package/scripts/followup/git-pr-followup-checks.ts +142 -0
  166. package/scripts/followup/git-pr-followup.ts +5 -130
  167. package/scripts/gate/core-budget.ts +69 -1
  168. package/scripts/gate/gate-plan.ts +47 -7
  169. package/scripts/gate/gate-skip.ts +38 -7
  170. package/scripts/gate/hook-gate-reuse.ts +12 -10
  171. package/scripts/gate/pre-commit-type-check.ts +4 -1
  172. package/scripts/gate/project-checks.ts +6 -9
  173. package/scripts/gate/type-check-step.ts +2 -3
  174. package/scripts/gate/verification-gate.ts +27 -8
  175. package/scripts/gh/git-gh-api-path.ts +2 -2
  176. package/scripts/gh/git-gh-issue-rest.ts +3 -3
  177. package/scripts/gh/git-gh-issue-write.ts +53 -7
  178. package/scripts/gh/git-gh-issue.ts +5 -3
  179. package/scripts/gh/git-gh-pr-read.ts +68 -19
  180. package/scripts/gh/git-gh-pr-rest.ts +1 -1
  181. package/scripts/gh/git-gh-pr.ts +5 -1
  182. package/scripts/gh/git-pr-ai-review.ts +1 -1
  183. package/scripts/gh/git-pr-checks-eval.ts +9 -3
  184. package/scripts/gh/git-pr-checks-parse.ts +1 -1
  185. package/scripts/gh/git-pr-checks-watch.ts +6 -5
  186. package/scripts/gh/git-pr-checks.ts +18 -6
  187. package/scripts/gh/git-pr-coderabbit.ts +1 -1
  188. package/scripts/gh/git-pr-error.ts +1 -3
  189. package/scripts/gh/git-pr.ts +9 -13
  190. package/scripts/gh/github-issue-url.ts +2 -2
  191. package/scripts/git/constants.ts +12 -6
  192. package/scripts/git/git-command.ts +20 -218
  193. package/scripts/git/git-diff-reads.ts +225 -0
  194. package/scripts/git/git-error.ts +4 -12
  195. package/scripts/git/git-pre-push-hook.ts +139 -0
  196. package/scripts/git/git-prompt.ts +2 -6
  197. package/scripts/git/{schemas.ts → git-schemas.ts} +2 -0
  198. package/scripts/git/git-spawn-sync.ts +3 -1
  199. package/scripts/git/git-spawn.ts +1 -1
  200. package/scripts/git/git-staging.ts +4 -6
  201. package/scripts/git/git-status.ts +3 -9
  202. package/scripts/git/main-merge.ts +57 -21
  203. package/scripts/hooks/codex-hook-adapter.ts +1 -1
  204. package/scripts/hooks/edited-cspell.ts +2 -4
  205. package/scripts/hooks/format-edited-file.ts +3 -12
  206. package/scripts/hooks/pre-push-unit.ts +31 -9
  207. package/scripts/hooks/reserved-run.ts +2 -2
  208. package/scripts/hooks/run-hook.sh +5 -4
  209. package/scripts/hooks/stop-guard.ts +5 -3
  210. package/scripts/init/init-actions.ts +0 -5
  211. package/scripts/init/init-ai-copy.ts +13 -2
  212. package/scripts/init/init-logic-json-merge.ts +19 -0
  213. package/scripts/init/init-logic-secretlint.ts +7 -7
  214. package/scripts/init/init-logic-workspace.ts +41 -2
  215. package/scripts/init/init-logic.ts +16 -26
  216. package/scripts/init/init.ts +5 -15
  217. package/scripts/init/initial-commit.ts +4 -4
  218. package/scripts/init/kit-development-versions.ts +40 -0
  219. package/scripts/init/kit-setup-state.ts +2 -2
  220. package/scripts/init/start-exec.ts +68 -19
  221. package/scripts/init/start-setup-pr.ts +9 -9
  222. package/scripts/init/start-steps.ts +6 -10
  223. package/scripts/issue/defect-rate-cli.ts +1 -1
  224. package/scripts/issue/git-next-issues.ts +1 -1
  225. package/scripts/issue/issue-auto-ok.ts +1 -1
  226. package/scripts/issue/issue-backlinks-cli.ts +2 -2
  227. package/scripts/issue/issue-backlinks.ts +9 -9
  228. package/scripts/issue/issue-comment-cli.ts +19 -13
  229. package/scripts/issue/issue-file-cli.ts +45 -18
  230. package/scripts/issue/issue-file.ts +24 -1
  231. package/scripts/issue/issue-scout-closed.ts +1 -1
  232. package/scripts/issue/issue-state-cli.ts +30 -74
  233. package/scripts/josh/josh-command-map.ts +2 -0
  234. package/scripts/josh/josh-command-types.ts +11 -0
  235. package/scripts/josh/josh-commands-ai.ts +4 -4
  236. package/scripts/josh/josh-commands-backlog.ts +4 -1
  237. package/scripts/josh/josh-commands-development.ts +11 -1
  238. package/scripts/josh/josh-commands-document.ts +1 -0
  239. package/scripts/josh/josh-commands-lane.ts +22 -0
  240. package/scripts/josh/josh-commands-maintenance.ts +4 -4
  241. package/scripts/josh/josh-commands-run.ts +12 -7
  242. package/scripts/josh/josh-in-process.ts +28 -9
  243. package/scripts/josh/josh-logic.ts +33 -15
  244. package/scripts/josh/process-identity.ts +1 -3
  245. package/scripts/josh/session-language-cli.ts +3 -4
  246. package/scripts/lane/lane-await.ts +5 -1
  247. package/scripts/lane/lane-child-invocation.ts +2 -2
  248. package/scripts/lane/lane-cli.ts +3 -3
  249. package/scripts/lane/lane-dispatch.ts +14 -10
  250. package/scripts/lane/lane-environment.ts +5 -1
  251. package/scripts/lane/lane-handoff.ts +1 -1
  252. package/scripts/lane/lane-install.ts +2 -13
  253. package/scripts/lane/lane-launch-cli.ts +35 -3
  254. package/scripts/lane/lane-ledger.ts +145 -0
  255. package/scripts/lane/lane-leftover.ts +1 -1
  256. package/scripts/lane/lane-load.ts +108 -0
  257. package/scripts/lane/lane-measure-cli.ts +149 -0
  258. package/scripts/lane/lane-occupancy.ts +3 -0
  259. package/scripts/lane/lane-reap.ts +1 -3
  260. package/scripts/lane/lane-relaunch.ts +1 -1
  261. package/scripts/lane/lane-stats.ts +224 -0
  262. package/scripts/lane/lane-vacant.ts +36 -0
  263. package/scripts/lane/openai-lane-ship.ts +2 -2
  264. package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
  265. package/scripts/lane/openai-lane-supervisor.ts +7 -165
  266. package/scripts/lane/openai-review-broker.ts +2 -4
  267. package/scripts/lib/ancestor-directories.ts +35 -0
  268. package/scripts/lib/animation-helpers.ts +2 -2
  269. package/scripts/lib/buffered-process.ts +3 -7
  270. package/scripts/lib/cli-flags.ts +71 -1
  271. package/scripts/lib/spawn-exit.ts +2 -2
  272. package/scripts/lib/timeouts.ts +35 -2
  273. package/scripts/lines/line-budget.ts +4 -6
  274. package/scripts/notify/telegram-notify.ts +26 -1
  275. package/scripts/notify/telegram-test.ts +6 -0
  276. package/scripts/observations/observation-ledger-line.ts +6 -6
  277. package/scripts/observations/observation-ledger-migrate.ts +55 -4
  278. package/scripts/observations/observation-ledger.ts +26 -7
  279. package/scripts/overrides/lockfile-overrides.ts +1 -1
  280. package/scripts/overrides/overrides-logic.ts +1 -1
  281. package/scripts/propagate/propagate-git.ts +1 -1
  282. package/scripts/propagate/propagate-publish.ts +2 -2
  283. package/scripts/propagate/propagate-steps.ts +20 -15
  284. package/scripts/propagate/propagate.ts +36 -27
  285. package/scripts/refactor/import-graph.ts +18 -1
  286. package/scripts/refactor/refactor-lint.ts +4 -4
  287. package/scripts/repo/repository-labels.ts +2 -2
  288. package/scripts/repo/required-checks-logic.ts +1 -3
  289. package/scripts/review/live-evidence.ts +2 -4
  290. package/scripts/review/review-brief-cli.ts +1 -1
  291. package/scripts/review/review-finding-ledger.ts +1 -1
  292. package/scripts/review/review-findings-cli.ts +1 -3
  293. package/scripts/rules/decision-oracle-batch.ts +128 -0
  294. package/scripts/rules/decision-oracle-tokens.ts +21 -0
  295. package/scripts/rules/decision-oracle.ts +8 -120
  296. package/scripts/rules/delivered-rules.ts +26 -182
  297. package/scripts/rules/direct-filing.ts +1 -1
  298. package/scripts/rules/early-heartbeat.ts +3 -3
  299. package/scripts/rules/git-argv.ts +1 -1
  300. package/scripts/rules/git-force.ts +1 -1
  301. package/scripts/rules/implementation-cut.ts +2 -2
  302. package/scripts/rules/index-guard.ts +1 -1
  303. package/scripts/rules/issue-comments.ts +118 -0
  304. package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
  305. package/scripts/rules/lane-background.ts +2 -2
  306. package/scripts/rules/lane-park.ts +18 -0
  307. package/scripts/rules/lane-split-park.ts +128 -0
  308. package/scripts/rules/oracle-firing.ts +1 -1
  309. package/scripts/rules/poll-loop.ts +1 -1
  310. package/scripts/rules/pre-gate-cut.ts +3 -3
  311. package/scripts/rules/protected-files.ts +59 -4
  312. package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
  313. package/scripts/rules/shell-body-trigger.ts +1 -1
  314. package/scripts/rules/stop-rules.ts +18 -7
  315. package/scripts/rules/test-declared-commit.ts +4 -4
  316. package/scripts/rules/wip-cap.ts +63 -0
  317. package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
  318. package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
  319. package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
  320. package/scripts/run/{run-carry.ts → carry/run-carry.ts} +12 -6
  321. package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
  322. package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +3 -3
  323. package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +2 -2
  324. package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
  325. package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
  326. package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
  327. package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
  328. package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
  329. package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +16 -2
  330. package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
  331. package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
  332. package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
  333. package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
  334. package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
  335. package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
  336. package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
  337. package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
  338. package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
  339. package/scripts/run/run-ending.ts +2 -2
  340. package/scripts/run/run-halfrun-resume.ts +6 -1
  341. package/scripts/run/run-headless.ts +1 -1
  342. package/scripts/run/run-invocation.ts +85 -84
  343. package/scripts/run/run-liveness.ts +1 -1
  344. package/scripts/run/run-parent-cut-hook.ts +1 -1
  345. package/scripts/run/run-preflight.ts +6 -6
  346. package/scripts/run/run-prep-cli.ts +1 -1
  347. package/scripts/run/run-prrun-resume.ts +1 -1
  348. package/scripts/run/run-report-cli.ts +4 -4
  349. package/scripts/run/run-report.ts +2 -2
  350. package/scripts/run/run-status-cli.ts +1 -1
  351. package/scripts/run/run-step-cli.ts +3 -3
  352. package/scripts/run/run-step.ts +2 -2
  353. package/scripts/run/run-stop-notify.ts +1 -1
  354. package/scripts/run/run-stranded-detect.ts +4 -4
  355. package/scripts/run/run-stranded.ts +2 -2
  356. package/scripts/run/run-watcher-guard-cli.ts +1 -1
  357. package/scripts/run/run-watcher-guard.ts +5 -3
  358. package/scripts/run/run-watcher-hook.ts +1 -1
  359. package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +29 -8
  360. package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
  361. package/scripts/run/ship/run-ship-lock.ts +50 -0
  362. package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +13 -0
  363. package/scripts/run/ship/run-ship-pre-detach.ts +66 -0
  364. package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
  365. package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
  366. package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +10 -6
  367. package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
  368. package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
  369. package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +13 -0
  370. package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +15 -8
  371. package/scripts/run/ship/run-ship-stop-text.ts +13 -0
  372. package/scripts/run/ship/run-ship-sync.ts +128 -0
  373. package/scripts/run/{run-ship.ts → ship/run-ship.ts} +6 -0
  374. package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
  375. package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +31 -9
  376. package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +3 -3
  377. package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +99 -17
  378. package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
  379. package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +8 -2
  380. package/scripts/run/wake/run-wake-restart.ts +58 -0
  381. package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
  382. package/scripts/run/{run-wake.ts → wake/run-wake.ts} +1 -1
  383. package/scripts/safe-chain/preinstall-command.ts +20 -21
  384. package/scripts/safe-chain/project-config.ts +19 -15
  385. package/scripts/sync/sync-ai-files.ts +1 -1
  386. package/scripts/sync/sync-configs.ts +2 -1
  387. package/scripts/sync/sync.ts +15 -2
  388. package/scripts/test/test-red-logic.ts +17 -2
  389. package/scripts/test/test-red.ts +8 -7
  390. package/scripts/test/unit-worker-share.ts +1 -0
  391. package/scripts/time-runtime/time-background.ts +5 -1
  392. package/scripts/time-runtime/time-instant.ts +3 -1
  393. package/scripts/time-runtime/time-spans.ts +6 -139
  394. package/scripts/time-runtime/time-tool-call.ts +160 -0
  395. package/scripts/time-runtime/time-transcript-line.ts +27 -1
  396. package/scripts/version/effective-upstream.ts +2 -20
  397. package/scripts/version/latest-corepack.ts +17 -17
  398. package/scripts/version/latest-regression.ts +1 -1
  399. package/scripts/version/latest-update.ts +4 -4
  400. package/scripts/version/npm-registry.ts +20 -5
  401. package/scripts/version/publishable-range-check.ts +2 -2
  402. package/scripts/version/publishable-range.ts +1 -1
  403. package/scripts/version/release-age.ts +48 -38
  404. package/scripts/version/release-hold.ts +1 -1
  405. package/scripts/version/safe-chain-version-update.ts +40 -0
  406. package/scripts/version/upgrade-shell-command.ts +48 -8
  407. package/scripts/version/version-check-logic.ts +23 -14
  408. package/scripts/version/version-commands.ts +23 -11
  409. package/scripts/version/version-remote.ts +28 -13
  410. package/templates/pnpm-workspace.basic.yaml +4 -0
  411. package/templates/workflows/dependabot-auto-merge.yml +5 -5
  412. package/templates/workflows/github-release.yml +2 -0
  413. package/scripts/build/build-config-merge.ts +0 -27
  414. package/scripts/build/build-managed-marker.ts +0 -27
  415. package/scripts/build/build-self-sync-guard.ts +0 -27
  416. package/scripts/build/build-version.ts +0 -26
  417. package/scripts/version/preinstall-version-update.ts +0 -86
  418. /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
  419. /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
  420. /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
  421. /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
  422. /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
  423. /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
  424. /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
  425. /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
  426. /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
  427. /package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +0 -0
  428. /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
  429. /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
  430. /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
  431. /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
  432. /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
  433. /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
  434. /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
  435. /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
  436. /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
  437. /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
@@ -62,9 +62,7 @@ function create_prompt(): Interface | undefined {
62
62
  }
63
63
 
64
64
  function handle_prompt_fallback<T>(fallback_value?: T): T {
65
- if (fallback_value !== undefined) {
66
- return fallback_value
67
- }
65
+ if (fallback_value !== undefined) return fallback_value
68
66
 
69
67
  throw new Error('TTY not available')
70
68
  }
@@ -72,9 +70,7 @@ function handle_prompt_fallback<T>(fallback_value?: T): T {
72
70
  async function with_prompt<T>(callback: PromptCallback<T>, fallback_value?: T): Promise<T> {
73
71
  const prompt = create_prompt()
74
72
 
75
- if (prompt === undefined) {
76
- return handle_prompt_fallback(fallback_value)
77
- }
73
+ if (prompt === undefined) return handle_prompt_fallback(fallback_value)
78
74
 
79
75
  try {
80
76
  // eslint-disable-next-line promise/prefer-await-to-callbacks
@@ -140,6 +140,8 @@ const open_issue_schema = z.object({
140
140
  labels: z.array(issue_label_schema).optional(),
141
141
  createdAt: z.string(),
142
142
  blockedBy: blocked_by_schema,
143
+ // Present only on the pickup listing, which reads it for the restructures each issue declares.
144
+ body: z.string().optional(),
143
145
  })
144
146
 
145
147
  type RollupItemData = z.infer<typeof rollup_item_schema>
@@ -1,4 +1,5 @@
1
1
  import { read_spawn_stderr, read_spawn_stdout } from '#scripts/lib/spawn-exit'
2
+ import { GIT_TIMEOUT_MS } from '#scripts/lib/timeouts'
2
3
  import { execaSync } from 'execa'
3
4
  import { git_utilities } from './constants'
4
5
 
@@ -7,7 +8,6 @@ import { git_utilities } from './constants'
7
8
  // non-zero exit is returned as a result, and every call is bounded: a git that never answers is an
8
9
  // unknown the caller already handles, never a hang.
9
10
 
10
- const GIT_TIMEOUT_MS = 10_000
11
11
  const SUCCESS_EXIT_CODE = 0
12
12
  // git translates its messages, so a caller matching stderr would fail under any non-English locale.
13
13
  const C_LOCALE: Record<string, string> = { LC_ALL: 'C', LANGUAGE: 'C' }
@@ -16,6 +16,8 @@ interface GitSyncOptions {
16
16
  cwd?: string
17
17
  timeout?: number
18
18
  env?: Record<string, string | undefined>
19
+ // A step a person watches (`josh start`) shows git's own progress and hook output as it runs.
20
+ stdio?: 'inherit'
19
21
  }
20
22
 
21
23
  interface GitSyncResult {
@@ -20,7 +20,7 @@ import { git_ssh_keepalive } from './git-ssh-keepalive'
20
20
  // spawns git itself. The timeout it carries is shared here by `read_remote` / `with_output_remote`. Those two files are the spawn sites of the ordinary command
21
21
  // modules — the claim stops there. A synchronous caller goes through `git-spawn-sync.ts`, the same
22
22
  // binary resolution with a result instead of an exception (joshuafolkken/kit#3065). Other parts of
23
- // this package spawn git for their own purposes (`scripts/run/run-progress-clock.ts`,
23
+ // this package spawn git for their own purposes (`scripts/run/progress/run-progress-clock.ts`,
24
24
  // `scripts/git/git-fixture-workspace.ts`), and an audit of how the git binary is resolved has to read
25
25
  // those too.
26
26
 
@@ -1,7 +1,7 @@
1
1
  import {
2
+ LEDGER_DIRECTORIES,
2
3
  LEGACY_OBSERVATION_LEDGER_PATHS,
3
4
  observation_ledger,
4
- OBSERVATION_LEDGER_DIRECTORY,
5
5
  } from '#scripts/observations/observation-ledger'
6
6
  import { observation_ledger_prepare } from '#scripts/observations/observation-ledger-prepare'
7
7
  import { git_command } from './git-command'
@@ -29,9 +29,7 @@ async function confirm_package_json_staged(should_force = false): Promise<boolea
29
29
  async function confirm_package_json_version(should_force = false): Promise<void> {
30
30
  const is_version_updated = await git_status.check_package_json_version()
31
31
 
32
- if (is_version_updated) {
33
- return
34
- }
32
+ if (is_version_updated) return
35
33
 
36
34
  if (should_force) {
37
35
  console.info('💡 Skipping package.json version check (force).')
@@ -83,7 +81,7 @@ async function stage_untracked_files(files: ReadonlyArray<string>): Promise<void
83
81
  // own lines** (joshuafolkken/kit#2919): its writers append to its own tree's issue file, so this is
84
82
  // the step that takes them to the default branch.
85
83
  const LEDGER_PATHSPECS: ReadonlyArray<string> = [
86
- OBSERVATION_LEDGER_DIRECTORY,
84
+ ...LEDGER_DIRECTORIES,
87
85
  ...LEGACY_OBSERVATION_LEDGER_PATHS,
88
86
  ]
89
87
 
@@ -108,7 +106,7 @@ function is_stageable(file_path: string, excluded: ReadonlyArray<string>): boole
108
106
  // step dies on git's empty index with `Failed to commit changes` — a message naming nothing that
109
107
  // caused it. This line is what turns that into a diagnosis.
110
108
  //
111
- // **The paths are parsed rather than matched as substrings**: `docs/maintainers/observations-old/x.md`
109
+ // **The paths are parsed rather than matched as substrings**: `.josh/observations-old/x.md`
112
110
  // contains the ledger's path, and a hint naming a file the exclusion never touched is a hint that
113
111
  // teaches the reader to ignore it.
114
112
  function report_excluded_paths(status_output: string, excluded: ReadonlyArray<string>): void {
@@ -31,9 +31,7 @@ function has_unstaged_files(status_output: string): boolean {
31
31
  }
32
32
 
33
33
  function is_staged_file(line: string): boolean {
34
- if (line.length < REQUIRED_STATUS_LENGTH || is_untracked_file(line)) {
35
- return false
36
- }
34
+ if (line.length < REQUIRED_STATUS_LENGTH || is_untracked_file(line)) return false
37
35
 
38
36
  const [staged_status] = line
39
37
 
@@ -47,9 +45,7 @@ function has_unstaged_changes(line: string): boolean {
47
45
  function has_all_files_staged(status_output: string): boolean {
48
46
  const lines = parse_status_lines(status_output)
49
47
 
50
- if (lines.length === 0) {
51
- return true
52
- }
48
+ if (lines.length === 0) return true
53
49
 
54
50
  return lines.every((line) => !has_unstaged_changes(line))
55
51
  }
@@ -62,9 +58,7 @@ function is_package_json_staged(status_output: string): boolean {
62
58
  const lines = parse_status_lines(status_output)
63
59
 
64
60
  return lines.some((line) => {
65
- if (!is_staged_file(line)) {
66
- return false
67
- }
61
+ if (!is_staged_file(line)) return false
68
62
 
69
63
  const filename = extract_filename(line)
70
64
 
@@ -44,44 +44,79 @@ function merge_message(default_branch: string, current_branch: string): string |
44
44
  return `Merge ${default_branch} into ${current_branch} #${issue_number}`
45
45
  }
46
46
 
47
- // The refusal is read after the fetch, so the incoming paths are the ones the merge would bring in
48
- // (joshuafolkken/kit#2445).
49
- async function merge_refusal(default_branch: string): Promise<string | undefined> {
50
- const status = await git_command.status()
51
- const incoming = await main_merge_guard.incoming_paths(default_branch)
52
-
53
- return main_merge_guard.refusal(status, incoming, default_branch)
47
+ // What one merge of the default branch came to (joshuafolkken/kit#3221). `josh ship` merges it again
48
+ // before the commit and on a conflicting pull request, and branches on the answer rather than on an
49
+ // exit code: `current` brought nothing in, so the tree is unchanged; `merged` changed it; `refused`
50
+ // is the guard's answer, nothing attempted; `conflict` names the paths git left unmerged, so a resumed
51
+ // session goes straight to them instead of asking git again.
52
+ type MergeOutcome =
53
+ | { kind: 'current'; branch: string }
54
+ | { kind: 'merged'; branch: string }
55
+ | { kind: 'refused'; message: string }
56
+ | { kind: 'conflict'; files: ReadonlyArray<string> }
57
+
58
+ interface MergeTarget {
59
+ default_branch: string
60
+ current_branch: string
61
+ is_current: boolean
54
62
  }
55
63
 
56
- // A merge that stops on a conflict keeps git's report and adds how to finish it, so the next step is
57
- // the sanctioned commit rather than a request for `git add`.
58
- async function merge_with_hint(default_branch: string, message: string | undefined): Promise<void> {
64
+ // A failure that left no unmerged path — a remote that cannot be reached, a hook that refused — is not
65
+ // a conflict, so it is rethrown rather than reported as one with nothing to resolve.
66
+ async function merge_into(target: MergeTarget): Promise<MergeOutcome> {
67
+ const { default_branch, current_branch } = target
68
+
59
69
  try {
60
- await git_command.merge_branch(default_branch, message)
70
+ await git_command.merge_branch(default_branch, merge_message(default_branch, current_branch))
61
71
  } catch (error) {
62
- console.error(main_merge_guard.CONFLICT_HINT)
63
- throw error
72
+ const files = main_merge_guard.unmerged_paths(await git_command.status())
73
+
74
+ if (files.length === 0) throw error
75
+
76
+ return { kind: 'conflict', files }
64
77
  }
78
+
79
+ return { kind: target.is_current ? 'current' : 'merged', branch: default_branch }
65
80
  }
66
81
 
67
82
  // The fetch is what makes `origin/<default>` current before the merge reads it. `git pull` did the
68
- // two together, which is the only thing it was doing here that is worth keeping.
69
- async function merge_default_branch(): Promise<number> {
83
+ // two together, which is the only thing it was doing here that is worth keeping. The refusal is read
84
+ // after the fetch, so the incoming paths are the ones the merge would bring in (joshuafolkken/kit#2445).
85
+ async function merge(): Promise<MergeOutcome> {
70
86
  const default_branch = await git_command.get_default_branch()
71
87
  const current_branch = await git_command.branch()
72
88
 
73
89
  await git_command.fetch_branch(default_branch)
74
90
 
75
- const refusal = await merge_refusal(default_branch)
91
+ const status = await git_command.status()
92
+ const incoming = await main_merge_guard.incoming_paths(default_branch)
93
+ const message = main_merge_guard.refusal(status, incoming, default_branch)
94
+
95
+ if (message !== undefined) return { kind: 'refused', message }
96
+
97
+ return await merge_into({ default_branch, current_branch, is_current: incoming.length === 0 })
98
+ }
99
+
100
+ // A merge that stops on a conflict keeps git's report and adds how to finish it, so the next step is
101
+ // the sanctioned commit rather than a request for `git add`.
102
+ function failure_text(outcome: MergeOutcome): string | undefined {
103
+ if (outcome.kind === 'refused') return outcome.message
104
+ if (outcome.kind === 'conflict') return main_merge_guard.CONFLICT_HINT
105
+
106
+ return undefined
107
+ }
108
+
109
+ async function merge_default_branch(): Promise<number> {
110
+ const outcome = await merge()
111
+ const failure = failure_text(outcome)
76
112
 
77
- if (refusal !== undefined) {
78
- console.error(refusal)
113
+ if (failure !== undefined) {
114
+ console.error(failure)
79
115
 
80
116
  return FAILURE_EXIT_CODE
81
117
  }
82
118
 
83
- await merge_with_hint(default_branch, merge_message(default_branch, current_branch))
84
- console.info(default_branch)
119
+ if ('branch' in outcome) console.info(outcome.branch)
85
120
 
86
121
  return SUCCESS_EXIT_CODE
87
122
  }
@@ -114,8 +149,9 @@ async function main(argv: ReadonlyArray<string>): Promise<void> {
114
149
  process.exitCode = await run(argv)
115
150
  }
116
151
 
117
- const main_merge = { run }
152
+ const main_merge = { merge, run }
118
153
 
119
154
  if (process.argv[1] === fileURLToPath(import.meta.url)) await main(process.argv.slice(ARGV_OFFSET))
120
155
 
121
156
  export { main_merge }
157
+ export type { MergeOutcome }
@@ -49,7 +49,7 @@ function paths_matching(command: string, pattern: RegExp): Array<string> {
49
49
  return [...command.matchAll(pattern)].flatMap((match) => {
50
50
  const file_path = match[1]?.trim()
51
51
 
52
- return file_path === undefined || file_path === '' ? [] : [file_path]
52
+ return file_path === undefined || file_path === '' ? [] : file_path
53
53
  })
54
54
  }
55
55
 
@@ -1,5 +1,6 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import path from 'node:path'
3
+ import { HOOK_PROCESS_TIMEOUT_MS } from '#scripts/lib/timeouts'
3
4
  import { execa } from 'execa'
4
5
 
5
6
  // The spell half of the edit hook (joshuafolkken/kit#2296). joshuafolkken/kit#2275 gave the hook a way
@@ -47,9 +48,6 @@ const CSPELL_ARGS: ReadonlyArray<string> = [
47
48
  '--no-summary',
48
49
  ]
49
50
  const PNPM = 'pnpm'
50
- // A spell check that hangs would hold the edit's turn open; the bound is generous because reaching it
51
- // means something is already wrong, exactly as the formatter spawns in `format-edited-file.ts`.
52
- const PROCESS_TIMEOUT_MS = 15_000
53
51
 
54
52
  const HEADER =
55
53
  'The edit hook found words cspell does not know; fix them or add them to `cspell.config.yaml` now rather than at the gate:'
@@ -79,7 +77,7 @@ async function run_cspell(file_path: string, project_root: string): Promise<stri
79
77
  cwd: project_root,
80
78
  stdout: 'pipe',
81
79
  stderr: 'ignore',
82
- timeout: PROCESS_TIMEOUT_MS,
80
+ timeout: HOOK_PROCESS_TIMEOUT_MS,
83
81
  })
84
82
 
85
83
  return result.stdout
@@ -4,6 +4,7 @@ import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
5
  import { resolve_local_bin, resolve_package_bin } from '#scripts/build/local-bin'
6
6
  import { ESLINT_EDIT_CACHE_FLAGS } from '#scripts/josh/josh-command-types'
7
+ import { HOOK_PROCESS_TIMEOUT_MS } from '#scripts/lib/timeouts'
7
8
  import { time_density_hook } from '#scripts/time-runtime/time-density-hook'
8
9
  import { execa } from 'execa'
9
10
  import { z } from 'zod'
@@ -87,15 +88,6 @@ const DAEMON_RESTART = 'restart'
87
88
  // `eslint.config.*` entry, and the rule modules under `eslint/` that it imports.
88
89
  const ESLINT_CONFIG_DIR = 'eslint'
89
90
  const ESLINT_CONFIG_ENTRY = /^eslint\.config\.[cm]?[jt]s$/u
90
- // A formatter that hangs would hold the edit's turn open, so each spawn is bounded. The bound has
91
- // to be read against the *worst-case run* rather than one spawn: an edit to a config input plans
92
- // eslint, prettier and `eslint_d restart`, and the first and last each have a second route behind
93
- // the daemon — five spawns. The hook entry in `.claude/settings.json` declares 90 seconds, so the
94
- // per-spawn bound must leave five of them under it, or the harness kill lands first and lands at a
95
- // moment this file did not choose — possibly inside `prettier --write`, which rewrites in place and
96
- // can leave the file truncated. 15s × 5 = 75s. It is still two orders of magnitude beyond the 0.84s
97
- // a warm run takes, so reaching it means something is already wrong.
98
- const PROCESS_TIMEOUT_MS = 15_000
99
91
 
100
92
  // The problems eslint could not auto-fix are handed to the model verbatim from eslint's own stdout —
101
93
  // its default formatter already prints the file, the `line:col`, and the rule name the issue asks
@@ -315,7 +307,7 @@ async function spawn_invocation(
315
307
  env: { ESLINT_D_MISS: 'fail' },
316
308
  stdout: 'pipe',
317
309
  stderr: 'ignore',
318
- timeout: PROCESS_TIMEOUT_MS,
310
+ timeout: HOOK_PROCESS_TIMEOUT_MS,
319
311
  })
320
312
 
321
313
  return { exit_code: result.exitCode ?? 1, stdout: result.stdout }
@@ -469,7 +461,7 @@ function report_no_payload(): void {
469
461
  // have (joshuafolkken/kit#2314). The rewrite, lint and spell blocks are composed together first, then
470
462
  // joined to the density line — one envelope, since a `PostToolUse` hook's stdout carries only one
471
463
  // (joshuafolkken/kit#2296). A hook killed at its 15s timeout is the one case this loses the density
472
- // line to — a run already gone pathologically wrong, per PROCESS_TIMEOUT_MS.
464
+ // line to — a run already gone pathologically wrong, per HOOK_PROCESS_TIMEOUT_MS.
473
465
  async function run_hook(payload: string): Promise<void> {
474
466
  const notice = time_density_hook.density_notice(payload)
475
467
  const file_path = parse_edited_path(payload)
@@ -500,6 +492,5 @@ export {
500
492
  select_invocation,
501
493
  ESLINT_DAEMON,
502
494
  MAX_DIAGNOSTIC_CHARS,
503
- PROCESS_TIMEOUT_MS,
504
495
  }
505
496
  export type { CommandRunner, FormatCommand }
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { fileURLToPath } from 'node:url'
3
- import { gate_skip } from '#scripts/gate/gate-skip'
3
+ import { gate_skip, type GateReuse } from '#scripts/gate/gate-skip'
4
4
  import { gate_tree, type GateTree } from '#scripts/gate/gate-tree'
5
5
  import { hook_gate_reuse } from '#scripts/gate/hook-gate-reuse'
6
- import type { FileMapStamp } from '#scripts/josh/file-map-stamp'
7
6
  import { test_unit_guard } from '#scripts/test/test-unit-guard'
8
7
 
9
8
  // The pre-push hook's unit run, which no longer re-runs a suite a green gate already covers
@@ -42,6 +41,15 @@ const FORCE_ENV = 'JOSH_PRE_PUSH_FORCE'
42
41
  // `gate-tree.ts` rather than read a second way here — plus the one this hook adds.
43
42
  interface PushTree extends GateTree {
44
43
  is_clean: boolean
44
+ // What a dirty tree reports as the reason the record was not reused (joshuafolkken/kit#3307) — the
45
+ // paths name a lock file a hook's own `pnpm install` rewrote, the case that went unexplained.
46
+ carry_miss: string
47
+ }
48
+
49
+ function carry_miss(lines: ReadonlyArray<string> | undefined): string {
50
+ if (lines === undefined) return 'git status could not be read'
51
+
52
+ return `the working tree differs from HEAD (${lines.join('; ')})`
45
53
  }
46
54
 
47
55
  // `git status --porcelain` prints one line per difference from HEAD, untracked files included — so an
@@ -57,7 +65,11 @@ async function read_push_tree(): Promise<PushTree> {
57
65
  hook_gate_reuse.read_status_lines(),
58
66
  ])
59
67
 
60
- return { ...tree, is_clean: hook_gate_reuse.is_worktree_clean(lines) }
68
+ return {
69
+ ...tree,
70
+ is_clean: hook_gate_reuse.is_worktree_clean(lines),
71
+ carry_miss: carry_miss(lines),
72
+ }
61
73
  }
62
74
 
63
75
  // `--force` is the gate's own flag, reused rather than respelled: it and `JOSH_PRE_PUSH_FORCE` are
@@ -74,14 +86,15 @@ function forwarded_arguments(extra_arguments: ReadonlyArray<string>): ReadonlyAr
74
86
  // **Any argument at all refuses the reuse**, not only `--force`. Everything else is forwarded to
75
87
  // vitest, and a caller who narrowed the run to one spec asked for that run rather than for a
76
88
  // recorded result about a whole tree. The hook's own line passes none, so this costs it nothing.
77
- function reusable_green_push(
89
+ function push_reuse(
78
90
  tree: PushTree,
79
91
  extra_arguments: ReadonlyArray<string>,
80
92
  source?: string,
81
- ): FileMapStamp | undefined {
82
- return hook_gate_reuse.reusable_green_hook({
93
+ ): GateReuse {
94
+ return hook_gate_reuse.hook_reuse({
83
95
  tree,
84
96
  is_tree_carried: tree.is_clean,
97
+ carry_miss: tree.carry_miss,
85
98
  extra_arguments,
86
99
  force_env: FORCE_ENV,
87
100
  source,
@@ -107,20 +120,29 @@ function format_skip(taken_at: string): string {
107
120
  // other commands already have. **A project that has vitest and no test file at all fails the push
108
121
  // instead** (joshuafolkken/kit#1224): the guard treats that half as a broken state rather than a
109
122
  // young project, and the hook returns what the guard returns.
123
+ // **A fall back to the whole suite says why, in one line** (joshuafolkken/kit#3307): under a crowded
124
+ // machine the suite costs minutes, and a silent miss left a moved merge base, a dirty tree and a
125
+ // mismatched record indistinguishable from one another.
126
+ function format_miss(reason: string): string {
127
+ return `↻ running the whole unit suite — the green gate record does not cover this push: ${reason}.`
128
+ }
129
+
110
130
  async function run_pre_push_unit(
111
131
  extra_arguments: ReadonlyArray<string> = [],
112
132
  source?: string,
113
133
  ): Promise<number> {
114
- const reusable = reusable_green_push(await read_push_tree(), extra_arguments, source)
134
+ const reuse = push_reuse(await read_push_tree(), extra_arguments, source)
135
+
136
+ if ('miss' in reuse) {
137
+ process.stdout.write(`${format_miss(reuse.miss)}\n`)
115
138
 
116
- if (reusable === undefined) {
117
139
  return await test_unit_guard.run_guarded_unit(
118
140
  process.cwd(),
119
141
  forwarded_arguments(extra_arguments),
120
142
  )
121
143
  }
122
144
 
123
- process.stdout.write(`${format_skip(reusable.taken_at)}\n`)
145
+ process.stdout.write(`${format_skip(reuse.stamp.taken_at)}\n`)
124
146
 
125
147
  return 0
126
148
  }
@@ -54,8 +54,8 @@ function parse_arguments(argv: ReadonlyArray<string>): ReservedCommand | undefin
54
54
  // command becomes an exit code the caller returns rather than a throw — the reservation is released by
55
55
  // `with_core_reservation`'s `finally` on either path.
56
56
  async function run_command(command: ReadonlyArray<string>): Promise<number> {
57
- const [file, ...args] = command
58
- const result = await execa(file ?? '', args, { stdio: 'inherit', reject: false })
57
+ const [file = '', ...args] = command
58
+ const result = await execa(file, args, { stdio: 'inherit', reject: false })
59
59
 
60
60
  return result.exitCode ?? FAIL_EXIT_CODE
61
61
  }
@@ -12,9 +12,10 @@
12
12
  # runs through josh, whose command is the name with its first `-` turned into `:`
13
13
  # (`pretool-guard` → `pretool:guard`).
14
14
  # - In kit's own checkout the gate is `hook-bundle-ready.ts`, which rebuilds stale bundles
15
- # (joshuafolkken/kit#2984). In an installed package it is a presence check: the published bundles
16
- # cannot be stale against a source the consumer never edits, and Node strips no types under
17
- # `node_modules`.
15
+ # (joshuafolkken/kit#2984) and then runs the bundle — or the josh fallback — itself, so a hook call
16
+ # starts node once (joshuafolkken/kit#3327). In an installed package it is a presence check: the
17
+ # published bundles cannot be stale against a source the consumer never edits, and Node strips no
18
+ # types under `node_modules`.
18
19
  # - An `if`/`else`, never `&&`/`||`: a guard's refusal is a non-zero exit, and a chain would read it as
19
20
  # a missing bundle and run the hook twice.
20
21
 
@@ -36,6 +37,6 @@ case $0 in
36
37
  if [ -d "$package_dir/dist/hooks" ]; then node "$bundle" "$@"; else node "$package_dir/dist/josh.js" "$josh_command" "$@"; fi
37
38
  ;;
38
39
  *)
39
- if node --disable-warning=ExperimentalWarning "$hooks_dir/hook-bundle-ready.ts"; then node "$bundle" "$@"; else pnpm josh "$josh_command" "$@"; fi
40
+ node --disable-warning=ExperimentalWarning "$hooks_dir/hook-bundle-ready.ts" "$hook_name" "$josh_command" "$@"
40
41
  ;;
41
42
  esac
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { text } from 'node:stream/consumers'
3
3
  import { fileURLToPath } from 'node:url'
4
+ import { agent_headless } from '#scripts/agent/agent-headless'
4
5
  import { backlog_ready } from '#scripts/backlog/backlog-ready'
5
6
  import { backlog_stalled_detect } from '#scripts/backlog/backlog-stalled-detect'
6
7
  import { repo_party } from '#scripts/discovery/repo-party'
@@ -13,11 +14,11 @@ import { lane_background } from '#scripts/rules/lane-background'
13
14
  import { lane_park } from '#scripts/rules/lane-park'
14
15
  import { last_prompt } from '#scripts/rules/last-prompt'
15
16
  import { stop_rules, type StopContext, type StopOutcome } from '#scripts/rules/stop-rules'
16
- import { run_carry } from '#scripts/run/run-carry'
17
- import { run_cut } from '#scripts/run/run-cut'
17
+ import { run_carry } from '#scripts/run/carry/run-carry'
18
+ import { run_cut } from '#scripts/run/cut/run-cut'
19
+ import { run_hold } from '#scripts/run/hold/run-hold'
18
20
  import { run_halfrun_resume } from '#scripts/run/run-halfrun-resume'
19
21
  import { run_headless } from '#scripts/run/run-headless'
20
- import { run_hold } from '#scripts/run/run-hold'
21
22
  import { run_stranded_detect } from '#scripts/run/run-stranded-detect'
22
23
  import { time_density_hook } from '#scripts/time-runtime/time-density-hook'
23
24
  import { time_hook_transcript } from '#scripts/time-runtime/time-hook-transcript'
@@ -87,6 +88,7 @@ async function build_context(
87
88
  agent_pending: lane_background.pending_agent_ids(tail).length > 0,
88
89
  handed_off: lane_handoff.is_handed_off(process.cwd()),
89
90
  session_lang: session_lang(),
91
+ headless_agent: agent_headless.is_headless(),
90
92
  }
91
93
  }
92
94
 
@@ -217,11 +217,6 @@ function build_file_actions(shape?: ProjectShape): ReadonlyArray<FileAction> {
217
217
 
218
218
  return [
219
219
  ...(shape?.has_git === false ? [] : [build_gitignore_action()]),
220
- build_action(
221
- '.npmrc',
222
- () => init_logic.generate_npmrc(),
223
- (existing) => init_logic.merge_npmrc(existing),
224
- ),
225
220
  build_eslint_action(),
226
221
  build_action(
227
222
  PRETTIER_CONFIG_JS,
@@ -104,8 +104,16 @@ function did_skip_copy_if_absent(
104
104
 
105
105
  // The merged file is written back, and reported as `updated`, only when the merge changed it — a
106
106
  // second `josh init` over an already-merged file reports `unchanged` (joshuafolkken/kit#2873).
107
+ // The workspace template with the project's own `.npmrc` values carried over: once the file holds a
108
+ // key, the merge keeps it, so this is the one chance to move the value (joshuafolkken/kit#3267).
109
+ function read_workspace_template(source_path: string, destination_path: string): string {
110
+ const npmrc = file_reader.read_file_or_empty(path.join(path.dirname(destination_path), '.npmrc'))
111
+
112
+ return init_logic.carry_npmrc_settings(readFileSync(source_path, 'utf8'), npmrc)
113
+ }
114
+
107
115
  function merge_existing_workspace_yaml(source_path: string, destination_path: string): void {
108
- const template = readFileSync(source_path, 'utf8')
116
+ const template = read_workspace_template(source_path, destination_path)
109
117
  const existing = readFileSync(destination_path, 'utf8')
110
118
  const merged = init_logic.merge_workspace_yaml(existing, template)
111
119
 
@@ -126,7 +134,9 @@ function did_skip_workspace_yaml_copy(source_path: string, destination_path: str
126
134
  return false
127
135
  }
128
136
 
129
- copy_ai_file(source_path, destination_path)
137
+ const template = read_workspace_template(source_path, destination_path)
138
+
139
+ writeFileSync(destination_path, transform_copied_content(destination_path, template))
130
140
  console.info(` ✔ created ${WORKSPACE_YAML}`)
131
141
 
132
142
  return false
@@ -385,6 +395,7 @@ const init_ai_copy = {
385
395
  ai_file_source,
386
396
  ai_file_mappings,
387
397
  ensure_basic_claude_md,
398
+ read_workspace_template,
388
399
  }
389
400
 
390
401
  export { init_ai_copy, CLAUDE_MD_FILENAME }
@@ -3,6 +3,7 @@ import { json_format } from '#scripts/config-merge/json-format'
3
3
  import { parse_jsonc } from '#scripts/config-merge/parse-jsonc'
4
4
  import { patch_json_key } from '#scripts/config-merge/patch-json-key'
5
5
  import { json_object_schema, string_array_schema, string_record_schema } from '#scripts/lib/schemas'
6
+ import { safe_chain_preinstall } from '#scripts/safe-chain/preinstall-command'
6
7
  import { apply_jf_migrations, remove_retired_scripts } from './init-logic-migrate'
7
8
  import { PACKAGE_JSON_KEY_ORDER } from './init-logic-package-key-order'
8
9
  import { kit_base_preset } from './kit-base-preset'
@@ -216,6 +217,23 @@ function merge_package_scripts(content: string, scripts: Record<string, string>)
216
217
  return serialize_package_json({ ...parsed, scripts: { ...prepend, ...migrated, ...append } })
217
218
  }
218
219
 
220
+ // Replace only a safe-chain `preinstall` an earlier kit wrote, for a consumer that never re-runs
221
+ // `josh init`; every other key and script is kept (joshuafolkken/kit#3269).
222
+ function upgrade_safe_chain_preinstall(content: string): string {
223
+ const parsed = parse_jsonc(content)
224
+ const raw = parsed['scripts']
225
+ if (raw === undefined) return content
226
+
227
+ const scripts = string_record_schema.parse(raw)
228
+ const current = scripts['preinstall']
229
+ if (current === undefined) return content
230
+
231
+ const migrated = safe_chain_preinstall.migrate_preinstall(current)
232
+ if (migrated === current) return content
233
+
234
+ return serialize_package_json({ ...parsed, scripts: { ...scripts, preinstall: migrated } })
235
+ }
236
+
219
237
  function merge_development_dependencies(
220
238
  content: string,
221
239
  additions: Record<string, string>,
@@ -327,6 +345,7 @@ const init_logic_json_merge = {
327
345
  merge_json_array_field,
328
346
  merge_json_object,
329
347
  merge_package_scripts,
348
+ upgrade_safe_chain_preinstall,
330
349
  merge_package_script_suffix,
331
350
  replace_in_package_script,
332
351
  remove_script_with_marker,
@@ -1,4 +1,5 @@
1
1
  import { init_logic_json_merge } from './init-logic-json-merge'
2
+ import { kit_development_versions } from './kit-development-versions'
2
3
 
3
4
  const SECRETLINT_CONFIG_FILENAME = '.secretlintrc.json'
4
5
  const SECRETLINT_RULE_PRESET = '@secretlint/secretlint-rule-preset-recommend'
@@ -6,12 +7,8 @@ const SECRETLINT_RULE_PRESET = '@secretlint/secretlint-rule-preset-recommend'
6
7
  // secretlint resolves both its CLI and every rule package from the project it runs in, not
7
8
  // transitively through the kit — the same constraint that forces the prettier plugins into
8
9
  // consumer devDependencies (as kit peers, see init.ts). Omitting either
9
- // entry makes the pre-commit hook fail with "Cannot find module". Versions mirror the kit's
10
- // own devDependencies.
11
- const SECRETLINT_DEV_DEPS: Record<string, string> = {
12
- secretlint: '^13.0.2',
13
- [SECRETLINT_RULE_PRESET]: '^13.0.2',
14
- }
10
+ // entry makes the pre-commit hook fail with "Cannot find module". The versions are kit's own.
11
+ const SECRETLINT_PACKAGES = ['secretlint', SECRETLINT_RULE_PRESET]
15
12
 
16
13
  function generate_secretlint_config(): string {
17
14
  const config = { rules: [{ id: SECRETLINT_RULE_PRESET }] }
@@ -24,7 +21,10 @@ function get_secretlint_config_filename(): string {
24
21
  }
25
22
 
26
23
  function merge_secretlint_development_deps(content: string): string {
27
- return init_logic_json_merge.merge_development_dependencies(content, SECRETLINT_DEV_DEPS)
24
+ return init_logic_json_merge.merge_development_dependencies(
25
+ content,
26
+ kit_development_versions.versions_of(SECRETLINT_PACKAGES),
27
+ )
28
28
  }
29
29
 
30
30
  const init_logic_secretlint = {