@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
@@ -3,6 +3,10 @@ import { timed_fetch } from '#scripts/lib/timed-fetch'
3
3
  import { z } from 'zod'
4
4
 
5
5
  const TELEGRAM_API_BASE = 'https://api.telegram.org'
6
+ // `sendMessage` refuses a text longer than this.
7
+ const TELEGRAM_TEXT_LIMIT = 4096
8
+ const BODY_TRUNCATION_MARK = '\n...'
9
+ const TRAILING_HIGH_SURROGATE = /[\uD800-\uDBFF]$/u
6
10
 
7
11
  const telegram_environment_schema = z.object({
8
12
  telegram_bot_token: z.string().min(1, { message: 'TELEGRAM_BOT_TOKEN is required' }),
@@ -188,10 +192,31 @@ function build_blocks(input: TelegramSendInput): Array<string> {
188
192
  return blocks
189
193
  }
190
194
 
191
- function build_text(input: TelegramSendInput): string {
195
+ function join_blocks(input: TelegramSendInput): string {
192
196
  return build_blocks(input).join('\n\n')
193
197
  }
194
198
 
199
+ // A cut between the two halves of a surrogate pair leaves a lone surrogate, which is not valid
200
+ // UTF-8 once encoded, so the half is dropped with the rest of the tail.
201
+ function head_of(body: string, kept: number): string {
202
+ const head = body.slice(0, kept)
203
+
204
+ return TRAILING_HIGH_SURROGATE.test(head) ? head.slice(0, -1) : head
205
+ }
206
+
207
+ // **The body is cut to fit Telegram's length limit, keeping its head** (joshuafolkken/kit#3242). A
208
+ // warning carrying a whole driver stderr ran past 4096 characters and was refused with `400 Bad
209
+ // Request`, so it reached nobody. A caller puts the cause first, so the tail is what is dropped, and the
210
+ // header and the URLs around the body survive.
211
+ function build_text(input: TelegramSendInput): string {
212
+ const text = join_blocks(input)
213
+ const overflow = text.length - TELEGRAM_TEXT_LIMIT
214
+ if (overflow <= 0 || input.body === undefined) return text
215
+ const kept = Math.max(0, input.body.length - overflow - BODY_TRUNCATION_MARK.length)
216
+
217
+ return join_blocks({ ...input, body: `${head_of(input.body, kept)}${BODY_TRUNCATION_MARK}` })
218
+ }
219
+
195
220
  async function post_message(config: TelegramConfig, text: string): Promise<void> {
196
221
  const url = `${TELEGRAM_API_BASE}/bot${config.bot_token}/sendMessage`
197
222
  const response = await timed_fetch(url, {
@@ -6,13 +6,19 @@ import { git_gh_repo } from '#scripts/gh/git-gh-repo'
6
6
  import { github_issue_url, type IssueUrlTarget } from '#scripts/gh/github-issue-url'
7
7
  import { git_error } from '#scripts/git/git-error'
8
8
  import { josh_environment_file } from '#scripts/josh/josh-environment-file'
9
+ import { cli_flags } from '#scripts/lib/cli-flags'
9
10
  import { telegram_notify } from './telegram-notify'
10
11
  import { telegram_test_logic, type CliValues, type ResolvedContext } from './telegram-test-logic'
11
12
 
12
13
  const REPO_NAME_SEPARATOR = '/'
14
+ const ARGV_OFFSET = 2
15
+
16
+ // The free-text values a caller writes, often opening with a Markdown `-` bullet.
17
+ const FREE_TEXT_FLAGS = ['--body', '--issue-title']
13
18
 
14
19
  function parse_cli_arguments(): CliValues {
15
20
  const { values } = parseArgs({
21
+ args: [...cli_flags.attach_values(process.argv.slice(ARGV_OFFSET), FREE_TEXT_FLAGS)],
16
22
  options: {
17
23
  'task-type': { type: 'string' },
18
24
  'repo-name': { type: 'string' },
@@ -34,16 +34,16 @@ function slug_of(first_field: string): string {
34
34
 
35
35
  // The `?? ''` lives in these small helpers rather than in `field_reason`, so that function stays a
36
36
  // single lookup with no branching of its own.
37
- function is_present(field: string | undefined): boolean {
38
- return (field ?? '').trim().length > 0
37
+ function is_present(field: string | undefined = ''): boolean {
38
+ return field.trim().length > 0
39
39
  }
40
40
 
41
- function matches(pattern: RegExp, field: string | undefined): boolean {
42
- return pattern.test(field ?? '')
41
+ function matches(pattern: RegExp, field: string | undefined = ''): boolean {
42
+ return pattern.test(field)
43
43
  }
44
44
 
45
- function slug_ok(key_field: string | undefined): boolean {
46
- return SLUG_PATTERN.test(slug_of(key_field ?? ''))
45
+ function slug_ok(key_field: string | undefined = ''): boolean {
46
+ return SLUG_PATTERN.test(slug_of(key_field))
47
47
  }
48
48
 
49
49
  // Each field's check paired with the reason it fails, so `field_reason` is a single lookup for the
@@ -1,8 +1,18 @@
1
- import { appendFileSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync } from 'node:fs'
1
+ import {
2
+ appendFileSync,
3
+ mkdirSync,
4
+ readdirSync,
5
+ readFileSync,
6
+ renameSync,
7
+ rmdirSync,
8
+ rmSync,
9
+ } from 'node:fs'
2
10
  import path from 'node:path'
3
11
  import { file_reader } from '#scripts/lib/read-file'
4
12
  import {
13
+ LEDGER_FILE_EXTENSION,
5
14
  LEGACY_LEDGER_FILE,
15
+ LEGACY_OBSERVATION_LEDGER_DIRECTORY,
6
16
  LEGACY_OBSERVATION_LEDGER_PATHS,
7
17
  MIGRATION_CLAIM_SUFFIX,
8
18
  OBSERVATION_LEDGER_DIRECTORY,
@@ -136,14 +146,55 @@ function migrate_one(legacy: string, target: string): boolean {
136
146
  return true
137
147
  }
138
148
 
149
+ // The ledger file a name in the old directory belongs to — a claim `<name>.md.<pid>.migrating` left by
150
+ // a killed process included, whose `<name>.md` may already be gone.
151
+ function source_name(name: string): string | undefined {
152
+ const end = name.indexOf(LEDGER_FILE_EXTENSION)
153
+
154
+ return end === -1 ? undefined : name.slice(0, end + LEDGER_FILE_EXTENSION.length)
155
+ }
156
+
157
+ function legacy_directory_names(directory: string): ReadonlyArray<string> {
158
+ try {
159
+ const names = readdirSync(directory).map((name) => source_name(name))
160
+
161
+ return [...new Set(names.filter((name) => name !== undefined))]
162
+ } catch {
163
+ return []
164
+ }
165
+ }
166
+
167
+ // **The old directory moves file by file into the same names** (joshuafolkken/kit#3341), so an
168
+ // issue's lines stay in that issue's file; the emptied directory is removed when nothing else is left.
169
+ function migrate_directory(root: string): boolean {
170
+ const legacy_directory = path.join(root, LEGACY_OBSERVATION_LEDGER_DIRECTORY)
171
+ const did_move = legacy_directory_names(legacy_directory)
172
+ .map((name) =>
173
+ migrate_one(
174
+ path.join(legacy_directory, name),
175
+ path.join(root, OBSERVATION_LEDGER_DIRECTORY, name),
176
+ ),
177
+ )
178
+ .some(Boolean)
179
+
180
+ try {
181
+ rmdirSync(legacy_directory)
182
+ } catch {
183
+ // Absent, or still holding something that is not the ledger's — left where it is.
184
+ }
185
+
186
+ return did_move
187
+ }
188
+
139
189
  // Returns whether anything was moved. `root` is the checkout the ledger lives in. Every old path is
140
190
  // tried — `map` before `some`, so one that moved does not leave the next one where it was.
141
191
  function migrate(root: string): boolean {
142
192
  const target = path.join(root, OBSERVATION_LEDGER_DIRECTORY, LEGACY_LEDGER_FILE)
143
-
144
- return LEGACY_OBSERVATION_LEDGER_PATHS.map((legacy) =>
193
+ const moved_files = LEGACY_OBSERVATION_LEDGER_PATHS.map((legacy) =>
145
194
  migrate_one(path.join(root, legacy), target),
146
- ).some(Boolean)
195
+ )
196
+
197
+ return [...moved_files, migrate_directory(root)].some(Boolean)
147
198
  }
148
199
 
149
200
  const observation_ledger_migrate = { migrate }
@@ -4,14 +4,24 @@
4
4
  // tree, and `observations-flush.ts` commits what no run carried — so a literal in each would be three
5
5
  // answers to one question, and a rename reaching only some of them would silently split them.
6
6
  //
7
- // **It sits under `docs/maintainers/`, apart from the reader-facing documents** (joshuafolkken/kit#2724).
7
+ // **It sits outside `docs/`** (joshuafolkken/kit#3341): its files are machine-written data lines, not
8
+ // documents a person reads, and at about forty new files a day they were most of `docs/`.
8
9
  //
9
10
  // **It is a directory of one file per issue, not one file** (joshuafolkken/kit#2919). One file every
10
11
  // run appended to made two lanes' pull requests conflict at its tail, so lanes parked their lines in
11
12
  // the primary checkout for a batch-end flush — and another run's stash took them from there, and the
12
13
  // flush never saw them. A run now appends to `<issue>.md` in its own work tree and merges it with its
13
14
  // own pull request: parallel lanes write different files, so nothing conflicts and nothing waits.
14
- const OBSERVATION_LEDGER_DIRECTORY = 'docs/maintainers/observations'
15
+ const OBSERVATION_LEDGER_DIRECTORY = '.josh/observations'
16
+
17
+ // Where the directory lived until joshuafolkken/kit#3341, a migration source for the same reasons as
18
+ // the single-file ledgers below: each `<name>.md` in it moves to the same name in the directory.
19
+ const LEGACY_OBSERVATION_LEDGER_DIRECTORY = 'docs/maintainers/observations'
20
+
21
+ const LEDGER_DIRECTORIES: ReadonlyArray<string> = [
22
+ OBSERVATION_LEDGER_DIRECTORY,
23
+ LEGACY_OBSERVATION_LEDGER_DIRECTORY,
24
+ ]
15
25
 
16
26
  const LEDGER_FILE_EXTENSION = '.md'
17
27
 
@@ -51,18 +61,25 @@ function is_ledger_file_name(name: string): boolean {
51
61
  // (`observation-ledger-migrate.ts`): `docs/observations.md.<pid>.migrating`.
52
62
  const MIGRATION_CLAIM_SUFFIX = '.migrating'
53
63
 
64
+ function is_legacy_directory_file(file_path: string): boolean {
65
+ return file_path.startsWith(`${LEGACY_OBSERVATION_LEDGER_DIRECTORY}/`)
66
+ }
67
+
54
68
  // A claim left by a process killed mid-migration holds ledger lines, so it is the ledger too: never
55
69
  // staged by an ordinary commit, and absorbed by the next migration.
56
70
  function is_migration_claim(file_path: string): boolean {
57
- return LEGACY_OBSERVATION_LEDGER_PATHS.some(
58
- (legacy) => file_path.startsWith(`${legacy}.`) && file_path.endsWith(MIGRATION_CLAIM_SUFFIX),
71
+ if (!file_path.endsWith(MIGRATION_CLAIM_SUFFIX)) return false
72
+
73
+ return (
74
+ is_legacy_directory_file(file_path) ||
75
+ LEGACY_OBSERVATION_LEDGER_PATHS.some((legacy) => file_path.startsWith(`${legacy}.`))
59
76
  )
60
77
  }
61
78
 
62
- // Porcelain status collapses an untracked directory to `docs/maintainers/observations/`, so the
63
- // directory itself, trailing slash and all, is the ledger as much as any file inside it.
79
+ // Porcelain status collapses an untracked directory to `.josh/observations/`, so the directory
80
+ // itself, trailing slash and all, is the ledger as much as any file inside it — the old directory too.
64
81
  function is_in_directory(file_path: string): boolean {
65
- return file_path.startsWith(`${OBSERVATION_LEDGER_DIRECTORY}/`)
82
+ return LEDGER_DIRECTORIES.some((directory) => file_path.startsWith(`${directory}/`))
66
83
  }
67
84
 
68
85
  function is_ledger_path(file_path: string): boolean {
@@ -107,8 +124,10 @@ const observation_ledger = {
107
124
  }
108
125
 
109
126
  export {
127
+ LEDGER_DIRECTORIES,
110
128
  LEDGER_FILE_EXTENSION,
111
129
  LEGACY_LEDGER_FILE,
130
+ LEGACY_OBSERVATION_LEDGER_DIRECTORY,
112
131
  LEGACY_OBSERVATION_LEDGER_PATHS,
113
132
  MIGRATION_CLAIM_SUFFIX,
114
133
  OBSERVATION_LEDGER_DIRECTORY,
@@ -1,6 +1,6 @@
1
1
  import { yaml_document } from '#scripts/lib/yaml-document'
2
2
  import { overrides_check } from './overrides-logic'
3
- import { lockfile_importers_schema, type LockfileImporter } from './schemas'
3
+ import { lockfile_importers_schema, type LockfileImporter } from './overrides-schemas'
4
4
 
5
5
  // The importer sections whose entries pnpm rewrites through `overrides` before resolving.
6
6
  const DEPENDENCY_GROUPS = ['dependencies', 'devDependencies', 'optionalDependencies'] as const
@@ -3,7 +3,7 @@ import {
3
3
  package_pnpm_schema,
4
4
  package_with_deps_schema,
5
5
  workspace_overrides_schema,
6
- } from './schemas'
6
+ } from './overrides-schemas'
7
7
 
8
8
  interface AddedEntry {
9
9
  key: string
@@ -3,6 +3,7 @@ import path from 'node:path'
3
3
  import { git_location_environment } from '#scripts/git/git-location-environment'
4
4
  import { ls_remote_branch_arguments } from '#scripts/git/git-ls-remote'
5
5
  import { git_spawn_sync } from '#scripts/git/git-spawn-sync'
6
+ import { GIT_TIMEOUT_MS } from '#scripts/lib/timeouts'
6
7
 
7
8
  // The git probes propagation needs before it writes anything into a working tree.
8
9
  //
@@ -11,7 +12,6 @@ import { git_spawn_sync } from '#scripts/git/git-spawn-sync'
11
12
  // its remote would propagate the *previous* release, which is already published and would report
12
13
  // success (joshuafolkken/kit#863).
13
14
 
14
- const GIT_TIMEOUT_MS = 10_000
15
15
  // The probes that talk to the remote get their own budget. Ten seconds is generous for a local
16
16
  // `rev-parse` and tight for an ssh handshake, and a probe that times out answers "no" — which for
17
17
  // the two below would report a push that never reached origin as a push nobody attempted.
@@ -1,5 +1,6 @@
1
1
  import { git_gh_exec } from '#scripts/gh/git-gh-exec'
2
2
  import { poll } from '#scripts/lib/poll'
3
+ import { GH_API_TIMEOUT_MS } from '#scripts/lib/timeouts'
3
4
  import { with_page_size } from '#scripts/version/version-remote'
4
5
 
5
6
  // Waiting for a specific version of this package to actually exist in the registry.
@@ -14,7 +15,6 @@ const VERSIONS_PAGE_SIZE = 100
14
15
  const NAMES_JQ = '[.[] | .name]'
15
16
  const DEFAULT_TIMEOUT_MS = 600_000
16
17
  const DEFAULT_INTERVAL_MS = 15_000
17
- const GH_TIMEOUT_MS = 20_000
18
18
  // How many probes in a row may fail before the registry is called unreadable. One failure is a rate
19
19
  // limit, a 5xx or a DNS hiccup — ending a ten-minute wait seconds in, on the first of those, is the
20
20
  // opposite of waiting. Only a registry that fails this many times running is a broken one.
@@ -60,7 +60,7 @@ function fetch_published_versions(versions_endpoint: string): Array<string> | un
60
60
  const stdout = git_gh_exec.read_gh_api_sync({
61
61
  path: endpoint,
62
62
  jq_filter: NAMES_JQ,
63
- timeout_ms: GH_TIMEOUT_MS,
63
+ timeout_ms: GH_API_TIMEOUT_MS,
64
64
  })
65
65
  if (stdout === undefined) return undefined
66
66
 
@@ -4,7 +4,8 @@ import { git_gh_issue_write } from '#scripts/gh/git-gh-issue-write'
4
4
  import { OTHER_CHANGE_LABEL } from '#scripts/issue/issue-labels'
5
5
  import { GATE_COMMAND } from '#scripts/josh/josh-command-types'
6
6
  import { error_text } from '#scripts/lib/error-message'
7
- import { build_upgrade_shell_command } from '#scripts/version/upgrade-shell-command'
7
+ import { SUITE_TIMEOUT_MS } from '#scripts/lib/timeouts'
8
+ import { build_upgrade_command } from '#scripts/version/upgrade-shell-command'
8
9
  import { create_version_command_config } from '#scripts/version/version-command-config'
9
10
  import { execaSync } from 'execa'
10
11
  import { propagate_git } from './propagate-git'
@@ -20,9 +21,6 @@ import type { PropagateTarget } from './propagate-targets'
20
21
  // and the sequencing stays testable (joshuafolkken/kit#863).
21
22
 
22
23
  const SUCCESS_EXIT_CODE = 0
23
- // Long enough for a consumer's full unit suite and a `pnpm add`, short enough that a hung step ends
24
- // the run instead of holding the whole propagation open.
25
- const STEP_TIMEOUT_MS = 1_800_000
26
24
  // The consumer-side gate. `josh gate` is the same command the AI documents require of a person
27
25
  // (joshuafolkken/kit#914) — running the four checks concurrently and reporting every failure in one
28
26
  // pass — so the chain is not repeated here. It resolves in the consumer's directory, which by this
@@ -54,11 +52,15 @@ function sync_command(bin_name: string): ReadonlyArray<string> {
54
52
  // which would defeat the exact-version wait: a release published while the run was in flight would
55
53
  // be the one every consumer received. `josh adopt` names `latest` deliberately, because it is pulling
56
54
  // whatever is newest inward rather than carrying one known release outward. Built through kit's own
57
- // upgrade-command builder either way, so the lockfile repair it chains stays single-sourced.
58
- function upgrade_command(package_name: string, version: string): ReadonlyArray<string> {
55
+ // upgrade-command builder either way, so the lockfile repair it chains stays single-sourced — and
56
+ // its steps are argument vectors, so the version never passes through a shell.
57
+ function upgrade_command(
58
+ package_name: string,
59
+ version: string,
60
+ ): ReadonlyArray<ReadonlyArray<string>> {
59
61
  const config = create_version_command_config({ package_name })
60
62
 
61
- return ['sh', '-c', build_upgrade_shell_command(version, true, config)]
63
+ return build_upgrade_command(version, true, config).steps
62
64
  }
63
65
 
64
66
  const PROPAGATE_ORIGIN =
@@ -149,8 +151,8 @@ const NO_COMMAND = 'no command defined'
149
151
  // tail would throw on its first `replaceAll`, and nothing between here and the report catches it:
150
152
  // the throw escapes the whole run, so every consumer after this one goes unattempted and the
151
153
  // end-of-run block is never printed — "one consumer's failure never stops another", lost to a crash.
152
- function captured(text: string | undefined): string {
153
- return text ?? ''
154
+ function captured(text: string | undefined = ''): string {
155
+ return text
154
156
  }
155
157
 
156
158
  function step_result(step: string, exit_code: number | undefined): StepResult {
@@ -172,7 +174,7 @@ function spawn_step(
172
174
  cwd: target.path,
173
175
  reject: false,
174
176
  stdio: 'inherit',
175
- timeout: STEP_TIMEOUT_MS,
177
+ timeout: SUITE_TIMEOUT_MS,
176
178
  })
177
179
 
178
180
  return step_result(step, spawned.exitCode)
@@ -207,7 +209,7 @@ function spawn_captured(
207
209
  stdin: 'inherit',
208
210
  stdout: ['inherit', 'pipe'],
209
211
  stderr: ['inherit', 'pipe'],
210
- timeout: STEP_TIMEOUT_MS,
212
+ timeout: SUITE_TIMEOUT_MS,
211
213
  })
212
214
 
213
215
  return {
@@ -383,11 +385,14 @@ function run_each(
383
385
  return { step, is_ok: true }
384
386
  }
385
387
 
388
+ // One entry per step, so `run_each` stops a toolkit's lockfile repair after its failed install.
386
389
  function upgrade_commands(plan: ReleasePlan): ReadonlyArray<ToolkitCommand> {
387
- return plan.releases.map((release) => ({
388
- package_name: release.package_name,
389
- command: upgrade_command(release.package_name, release.version),
390
- }))
390
+ return plan.releases.flatMap((release) =>
391
+ upgrade_command(release.package_name, release.version).map((command) => ({
392
+ package_name: release.package_name,
393
+ command,
394
+ })),
395
+ )
391
396
  }
392
397
 
393
398
  function sync_commands(plan: ReleasePlan): ReadonlyArray<ToolkitCommand> {
@@ -24,8 +24,7 @@ const SKIP_PUBLISH_FLAG = '--skip-publish-wait'
24
24
  const DRY_RUN_FLAG = '--dry-run'
25
25
  const TARGET_FLAG = '--target'
26
26
  const KNOWN_FLAGS: ReadonlyArray<string> = [SKIP_PUBLISH_FLAG, DRY_RUN_FLAG]
27
- // Shown in the usage line only. `--target` carries a value, so it is taken out of argv before the
28
- // flag-only refusal runs, and this entry never has to match an argument.
27
+ // Shown in the usage line only: `--target` carries a value, so it is spelled with a placeholder.
29
28
  const TARGET_USAGE = `${TARGET_FLAG} <repo>`
30
29
  const DRY_RUN_REASON = 'would be propagated'
31
30
  const UNTOUCHED_NOTE = 'No consumer was touched.'
@@ -40,41 +39,51 @@ interface RunOptions {
40
39
  usage?: string
41
40
  }
42
41
 
43
- interface TargetArgument {
44
- rest: Array<string>
45
- target?: string
46
- usage?: string
42
+ // `--target` is read as a list so a second one is seen and refused rather than letting one of the two
43
+ // names win silently.
44
+ const OPTIONS = {
45
+ 'skip-publish-wait': { type: 'boolean' },
46
+ 'dry-run': { type: 'boolean' },
47
+ target: { type: 'string', multiple: true },
48
+ } as const
49
+ const USAGE = `Usage: josh propagate [${[...KNOWN_FLAGS, TARGET_USAGE].join('] [')}]`
50
+ const TARGET_MISSING = `${TARGET_FLAG} needs a repository name, e.g. ${TARGET_FLAG} app-kit`
51
+
52
+ type ParsedValues = NonNullable<ReturnType<typeof cli_flags.values_of<typeof OPTIONS>>>
53
+
54
+ function refused(usage: string): RunOptions {
55
+ return { is_dry_run: false, is_publish_wait_skipped: false, usage }
47
56
  }
48
57
 
49
- // Take `--target <repo>` out of argv. A second `--target` stays in `rest`, where the flag-only
50
- // refusal rejects it rather than letting one of the two names win silently.
51
- function split_target(argv: ReadonlyArray<string>): TargetArgument {
52
- const index = argv.indexOf(TARGET_FLAG)
53
- if (index === -1) return { rest: [...argv] }
54
- const target = argv[index + 1]
55
- const rest = argv.filter((_, position) => position !== index && position !== index + 1)
58
+ function to_options(values: ParsedValues): RunOptions {
59
+ const [target] = values.target ?? []
56
60
 
57
- if (target === undefined || target.startsWith('-')) {
58
- return { rest, usage: `${TARGET_FLAG} needs a repository name, e.g. ${TARGET_FLAG} app-kit` }
61
+ return {
62
+ is_dry_run: values['dry-run'] ?? false,
63
+ is_publish_wait_skipped: values['skip-publish-wait'] ?? false,
64
+ ...(target !== undefined && { target }),
59
65
  }
60
-
61
- return { rest, target }
62
66
  }
63
67
 
64
68
  // Reject anything not on the list rather than ignoring it. `--dryrun` silently falling through to
65
- // the real write path is the mistake this refusal exists to prevent.
69
+ // the real write path is the mistake this refusal exists to prevent. A `--target` with no usable name
70
+ // after it — last on the line, or followed by `--dry-run` — is named on its own, so the caller learns
71
+ // which value is missing rather than reading the generic usage.
72
+ function read_values(argv: ReadonlyArray<string>): ParsedValues | undefined {
73
+ const values = cli_flags.values_of(argv, OPTIONS)
74
+
75
+ return (values?.target?.length ?? 0) > 1 ? undefined : values
76
+ }
77
+
66
78
  function parse_options(argv: ReadonlyArray<string>): RunOptions {
67
- const { rest, target, usage: target_usage } = split_target(argv)
68
- const options: RunOptions = {
69
- is_dry_run: rest.includes(DRY_RUN_FLAG),
70
- is_publish_wait_skipped: rest.includes(SKIP_PUBLISH_FLAG),
71
- ...(target !== undefined && { target }),
79
+ if (cli_flags.is_value_unusable(argv, TARGET_FLAG)) return refused(TARGET_MISSING)
80
+ const values = read_values(argv)
81
+
82
+ if (values === undefined) {
83
+ return refused(`Unknown argument(s) or a repeated ${TARGET_FLAG}: ${argv.join(' ')}\n${USAGE}`)
72
84
  }
73
- const usage =
74
- target_usage ??
75
- cli_flags.refuse_unknown_flags(rest, [...KNOWN_FLAGS, TARGET_USAGE], 'propagate')
76
85
 
77
- return usage === undefined ? options : { ...options, usage }
86
+ return to_options(values)
78
87
  }
79
88
 
80
89
  // Propagation runs from the supplier's own repository, and only there — kit's, or a toolkit's built
@@ -28,7 +28,7 @@ type FileGraph = ReadonlyMap<string, ReadonlySet<string>>
28
28
  function specifiers_of(source: string, pattern: RegExp): ReadonlyArray<string> {
29
29
  const matches = [...source.matchAll(pattern)]
30
30
 
31
- return matches.flatMap((match) => (match[1] === undefined ? [] : [match[1]]))
31
+ return matches.flatMap((match) => match[1] ?? [])
32
32
  }
33
33
 
34
34
  function parse_imports(source: string): ReadonlyArray<string> {
@@ -157,6 +157,22 @@ function expand(
157
157
  return included
158
158
  }
159
159
 
160
+ // Every file `entry` reaches through its own imports, `entry` included, with no stage cap — the whole
161
+ // transitive closure a running script loads, not the §4.3 neighborhood `expand` bounds.
162
+ function reachable(entry: string, forward: FileGraph): ReadonlySet<string> {
163
+ const included = new Set<string>([entry])
164
+ const pending = [entry]
165
+
166
+ for (let file = pending.pop(); file !== undefined; file = pending.pop()) {
167
+ const fresh = [...(forward.get(file) ?? [])].filter((target) => !included.has(target))
168
+
169
+ for (const target of fresh) included.add(target)
170
+ pending.push(...fresh)
171
+ }
172
+
173
+ return included
174
+ }
175
+
160
176
  // The whole §4.3 walk: build the graph over the universe, then expand the seed within it.
161
177
  function expand_scope(
162
178
  seed: ReadonlyArray<string>,
@@ -174,6 +190,7 @@ const import_graph = {
174
190
  expand_scope,
175
191
  invert,
176
192
  parse_imports,
193
+ reachable,
177
194
  resolve_spec,
178
195
  MAX_STAGES,
179
196
  }
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path'
2
2
  import { find_local_bin_upwards } from '#scripts/build/local-bin'
3
+ import { LINT_TIMEOUT_MS } from '#scripts/lib/timeouts'
3
4
  import { execa } from 'execa'
4
5
  import { z } from 'zod'
5
6
 
@@ -20,7 +21,6 @@ const PNPM_EXEC = 'exec'
20
21
  // `--no-inline-config` so a `/* eslint-disable */` in a file cannot hide a candidate from the scan the
21
22
  // way it can from the gate; the scan reports what the rules say, not what a file asked to suppress.
22
23
  const FORMAT_FLAGS: ReadonlyArray<string> = ['--format', 'json', '--no-inline-config']
23
- const PROCESS_TIMEOUT_MS = 180_000
24
24
 
25
25
  type Priority = 'high' | 'medium' | 'low'
26
26
  const HIGH: Priority = 'high'
@@ -188,9 +188,9 @@ function eslint_command(root: string, files: ReadonlyArray<string>): ReadonlyArr
188
188
  // `undefined` means the run did not produce parseable eslint JSON — a failure, kept distinct from a
189
189
  // valid empty array (a clean scope), so a scan that could not run is never reported as `clear`
190
190
  // (joshuafolkken/kit#2180).
191
- function parse_results(raw: string | undefined): Array<LintResult> | undefined {
191
+ function parse_results(raw: string | undefined = 'null'): Array<LintResult> | undefined {
192
192
  try {
193
- const parsed = results_schema.safeParse(JSON.parse(raw ?? 'null'))
193
+ const parsed = results_schema.safeParse(JSON.parse(raw))
194
194
 
195
195
  return parsed.success ? parsed.data : undefined
196
196
  } catch {
@@ -216,7 +216,7 @@ async function run_eslint_json(
216
216
  reject: false,
217
217
  stdout: 'pipe',
218
218
  stderr: 'ignore',
219
- timeout: PROCESS_TIMEOUT_MS,
219
+ timeout: LINT_TIMEOUT_MS,
220
220
  })
221
221
 
222
222
  return parse_results(result.stdout)
@@ -52,9 +52,9 @@ function label_names(labels: ReadonlyArray<LabelDefinition>): ReadonlyArray<stri
52
52
 
53
53
  // An unreadable listing is not an empty one: nothing is created, and every label is reported as
54
54
  // unconfirmed rather than as missing. An unresolved repository (no GitHub remote) is the same case.
55
- function unreadable(repo: string | undefined): ReadonlyArray<string> {
55
+ function unreadable(repo: string | undefined = 'this repository'): ReadonlyArray<string> {
56
56
  const names = label_names(REPOSITORY_LABELS)
57
- const target = repo ?? 'this repository'
57
+ const target = repo
58
58
 
59
59
  console.warn(
60
60
  ` ⚠ could not read the labels of ${target}; make sure these exist: ${names.join(', ')}`,
@@ -72,9 +72,7 @@ function workflow_job_names(content: string): Array<string> {
72
72
  const parsed = workflow_schema.safeParse(parse_yaml_safe(content))
73
73
  if (!parsed.success) return []
74
74
 
75
- return Object.values(parsed.data.jobs).flatMap((job) =>
76
- job.name === undefined ? [] : [job.name],
77
- )
75
+ return Object.values(parsed.data.jobs).flatMap((job) => job.name ?? [])
78
76
  }
79
77
 
80
78
  // The checks this repository should require: only those a workflow it has actually reports. A file
@@ -16,12 +16,10 @@ import { test_declared_logic, type Verdict } from '#scripts/test/test-declared-l
16
16
 
17
17
  const EVIDENCE_HEADING = '## 実機証跡'
18
18
 
19
- function verdict_for(paths: ReadonlyArray<string>, body: string | undefined): Verdict {
19
+ function verdict_for(paths: ReadonlyArray<string>, body: string | undefined = ''): Verdict {
20
20
  if (test_declared_logic.runtime_files(paths).length === 0) return 'exempt'
21
21
 
22
- return reproduction_measure.has_command_output(body ?? '', EVIDENCE_HEADING)
23
- ? 'satisfied'
24
- : 'required'
22
+ return reproduction_measure.has_command_output(body, EVIDENCE_HEADING) ? 'satisfied' : 'required'
25
23
  }
26
24
 
27
25
  // The diff is this checkout's, so it answers for the named branch only when that is the one checked
@@ -13,7 +13,7 @@ import { find_package_directory } from '#scripts/josh/josh-logic'
13
13
  import { path_decision } from '#scripts/josh/path-decision'
14
14
  import { stamp_file } from '#scripts/josh/stamp-file'
15
15
  import { openai_review_broker } from '#scripts/lane/openai-review-broker'
16
- import { run_ship_review } from '#scripts/run/run-ship-review'
16
+ import { run_ship_review } from '#scripts/run/ship/run-ship-review'
17
17
  import { review_attest } from './review-attest'
18
18
  import { review_brief } from './review-brief'
19
19
  import { review_checkout, type ReviewCheckout } from './review-checkout'
@@ -1,7 +1,7 @@
1
1
  // The review-finding line's grammar, its category vocabulary and the aggregation over it — the single
2
2
  // place all three are defined (joshuafolkken/kit#2325). A `/code-review` round's findings evaporated
3
3
  // the moment they were fixed, so no one could say which category recurred. They now land in the same
4
- // append-only ledger the observation lines use (`docs/maintainers/observations/`), under a distinct `- rf:`
4
+ // append-only ledger the observation lines use (`.josh/observations/`), under a distinct `- rf:`
5
5
  // prefix so `observation-ledger-line.ts`'s `- k:` grammar never treats them as its own — one ledger,
6
6
  // one commit path, a second line *type* rather than a second ledger.
7
7
 
@@ -19,9 +19,7 @@ function zero_line(zero_rounds: number): string {
19
19
  }
20
20
 
21
21
  function format_report(counts: ReadonlyArray<CategoryCount>, zero_rounds: number): string {
22
- if (counts.length === 0) {
23
- return zero_rounds === 0 ? NO_FINDINGS : zero_line(zero_rounds)
24
- }
22
+ if (counts.length === 0) return zero_rounds === 0 ? NO_FINDINGS : zero_line(zero_rounds)
25
23
 
26
24
  const body = counts.map((entry) => count_line(entry)).join('\n')
27
25