@joshuafolkken/kit 1.1035.0 → 1.1094.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 (605) hide show
  1. package/.claude/settings.json +9 -8
  2. package/.claude/skills/epic-commands/SKILL.md +5 -7
  3. package/.claude/skills/verify-ui/SKILL.md +1 -1
  4. package/.claude/skills/workflow-commands/SKILL.md +21 -13
  5. package/.claude/skills/workflow-commands/background-commands.md +5 -4
  6. package/.claude/skills/workflow-commands/backlogrun-child.md +3 -3
  7. package/.claude/skills/workflow-commands/backlogrun-lanes.md +9 -7
  8. package/.claude/skills/workflow-commands/backlogrun-park.md +6 -0
  9. package/.claude/skills/workflow-commands/backlogrun-progress.md +31 -37
  10. package/.claude/skills/workflow-commands/backlogrun-steps.md +25 -25
  11. package/.claude/skills/workflow-commands/backlogrun.md +2 -2
  12. package/.claude/skills/workflow-commands/chain-rule.md +6 -4
  13. package/.claude/skills/workflow-commands/delegation.md +3 -3
  14. package/.claude/skills/workflow-commands/followup-reference.md +3 -3
  15. package/.claude/skills/workflow-commands/followup.md +7 -3
  16. package/.claude/skills/workflow-commands/fullrun-steps.md +3 -2
  17. package/.claude/skills/workflow-commands/fullrun.md +3 -2
  18. package/.claude/skills/workflow-commands/halfrun.md +16 -19
  19. package/.claude/skills/workflow-commands/issue-comments.md +2 -2
  20. package/.claude/skills/workflow-commands/issue-fold-existing.md +10 -10
  21. package/.claude/skills/workflow-commands/issue-scout.md +1 -1
  22. package/.claude/skills/workflow-commands/kickoff.md +9 -4
  23. package/.claude/skills/workflow-commands/latest-gate.md +1 -1
  24. package/.claude/skills/workflow-commands/needs-human-review.md +1 -1
  25. package/.claude/skills/workflow-commands/observation-filing.md +44 -89
  26. package/.claude/skills/workflow-commands/pre-gate-cut.md +33 -36
  27. package/.claude/skills/workflow-commands/prerequisite.md +1 -1
  28. package/.claude/skills/workflow-commands/prrun.md +36 -0
  29. package/.claude/skills/workflow-commands/retrospective.md +1 -1
  30. package/.claude/skills/workflow-commands/working-tree-hold.md +24 -4
  31. package/.codex/hooks.json +16 -20
  32. package/.github/actions/setup-pnpm/action.yml +93 -0
  33. package/.github/dependabot.yml +5 -1
  34. package/.github/workflows/auto-tag.yml +21 -0
  35. package/.github/workflows/ci.yml +26 -128
  36. package/.github/workflows/pr-classification.yml +5 -41
  37. package/.github/workflows/publish.yml +42 -48
  38. package/.github/workflows/sonar-qube.yml +37 -4
  39. package/AGENTS.md +12 -2
  40. package/CLAUDE.md +15 -16
  41. package/GEMINI.md +14 -2
  42. package/README.md +59 -18
  43. package/SECURITY.md +36 -6
  44. package/cspell/index.yaml +2 -1
  45. package/dist/CLAUDE.md +15 -16
  46. package/dist/config-merge/index.js +15 -3
  47. package/dist/hooks/batch-guard.js +4 -4
  48. package/dist/hooks/{chunk-Y27M6A7X.js → chunk-3C4YCCEF.js} +5 -13
  49. package/dist/hooks/{chunk-D67KVGGY.js → chunk-7ZPFAVR7.js} +17 -88
  50. package/dist/hooks/{chunk-HOYJU5RM.js → chunk-E54KGFEQ.js} +11 -4
  51. package/dist/hooks/{chunk-KG6PZD72.js → chunk-IL27YTPH.js} +2 -2
  52. package/dist/hooks/{chunk-GJT4BNVE.js → chunk-KHWHKJOM.js} +3 -3
  53. package/dist/hooks/{chunk-HU66PWWL.js → chunk-M2XDCUKD.js} +1076 -509
  54. package/dist/hooks/{chunk-GLBDBJOK.js → chunk-NKOG3XAV.js} +199 -304
  55. package/dist/hooks/{chunk-ZXNE6NY3.js → chunk-O4LL4HTI.js} +2 -7
  56. package/dist/hooks/{chunk-XPLP27WA.js → chunk-OIZUHPJI.js} +5 -27
  57. package/dist/hooks/{chunk-EIFCAJRB.js → chunk-UD24EAIL.js} +2546 -1636
  58. package/dist/hooks/codex-hook-adapter.js +10 -11
  59. package/dist/hooks/duplicate-read-guard.js +5 -5
  60. package/dist/hooks/format-edited.js +15 -34
  61. package/dist/hooks/inputs.json +279 -0
  62. package/dist/hooks/investigation-guard.js +5 -5
  63. package/dist/hooks/pretool-guard.js +23 -16
  64. package/dist/hooks/session-lang.js +0 -1
  65. package/dist/hooks/stop-guard.js +188 -509
  66. package/dist/josh.js +166 -225
  67. package/dist/managed-marker/index.js +2 -1
  68. package/dist/version/index.js +412 -64
  69. package/eslint/rules/promise.js +5 -0
  70. package/package.json +13 -8
  71. package/prompts/collaboration-workflow/file-edits.md +6 -53
  72. package/prompts/collaboration-workflow/issue-citation.md +5 -5
  73. package/prompts/collaboration-workflow/operating-rules.md +4 -4
  74. package/prompts/collaboration-workflow/overview.md +13 -1
  75. package/prompts/collaboration-workflow/report-format.md +1 -1
  76. package/prompts/collaboration-workflow/residency.md +4 -4
  77. package/prompts/collaboration-workflow/rule-delivery.md +5 -36
  78. package/prompts/collaboration-workflow/shell-body.md +7 -9
  79. package/prompts/collaboration-workflow/turn-batching.md +21 -37
  80. package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
  81. package/prompts/collaboration-workflow/wip-cap.md +36 -36
  82. package/prompts/collaboration-workflow.md +5 -8
  83. package/prompts/review-rubric.md +1 -1
  84. package/prompts/review.md +3 -3
  85. package/prompts/sonar-hotspot-handling.md +12 -4
  86. package/prompts/testing-guide.md +3 -3
  87. package/scripts/adopt/adopt-toolkits.ts +2 -2
  88. package/scripts/adopt/adopt.ts +0 -2
  89. package/scripts/agent/agent-argv.ts +0 -2
  90. package/scripts/agent/agent-diagnostics.ts +1 -1
  91. package/scripts/agent/agent-role-profile.ts +0 -7
  92. package/scripts/agent/claude-agent-argv.ts +0 -2
  93. package/scripts/agent/codex-agent-argv.ts +1 -1
  94. package/scripts/agent/codex-hooks.ts +92 -0
  95. package/scripts/agent/interactive-ask.ts +1 -1
  96. package/scripts/auto-ok/auto-ok-cli.ts +15 -25
  97. package/scripts/backlog/backlog-budget.ts +0 -2
  98. package/scripts/backlog/backlog-defect-priority.ts +2 -2
  99. package/scripts/backlog/backlog-drive-cli.ts +10 -8
  100. package/scripts/backlog/backlog-drive-named-offer.ts +16 -3
  101. package/scripts/backlog/backlog-drive-named.ts +2 -1
  102. package/scripts/backlog/backlog-drive.ts +76 -16
  103. package/scripts/backlog/backlog-named.ts +0 -3
  104. package/scripts/backlog/backlog-next.ts +32 -22
  105. package/scripts/backlog/backlog-offer-cli.ts +0 -5
  106. package/scripts/backlog/backlog-plan-cli.ts +6 -8
  107. package/scripts/backlog/backlog-plan.ts +1 -1
  108. package/scripts/backlog/backlog-pool.ts +34 -17
  109. package/scripts/backlog/backlog-rank.ts +106 -0
  110. package/scripts/backlog/backlog-ready.ts +19 -3
  111. package/scripts/backlog/backlog-scope.ts +1 -3
  112. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  113. package/scripts/backlog/backlog-stalled.ts +0 -1
  114. package/scripts/backlog/backlog-waves.ts +47 -31
  115. package/scripts/behavior/behavior-cli.ts +1 -1
  116. package/scripts/build/build-codex-hooks.ts +26 -0
  117. package/scripts/build/build-hooks.ts +92 -11
  118. package/scripts/build/hook-bundle-stamp.ts +111 -0
  119. package/scripts/cases/cases-cli.ts +1 -1
  120. package/scripts/cases/cases-logic.ts +1 -1
  121. package/scripts/ci/composite-actions.ts +30 -0
  122. package/scripts/ci/pr-classification.ts +3 -3
  123. package/scripts/claude/workflow-destination.ts +10 -1
  124. package/scripts/clone/clone-fingerprint.ts +0 -1
  125. package/scripts/clone/clone-scan-cli.ts +1 -1
  126. package/scripts/clone/clone-scan.ts +1 -1
  127. package/scripts/config-merge/json-format.ts +0 -1
  128. package/scripts/config-merge/patch-yaml-list.ts +1 -1
  129. package/scripts/cost-runtime/codex-usage.ts +2 -9
  130. package/scripts/cost-runtime/cost-attribute.ts +0 -1
  131. package/scripts/cost-runtime/cost-cli.ts +0 -3
  132. package/scripts/cost-runtime/cost-corpus.ts +0 -2
  133. package/scripts/cost-runtime/cost-format.ts +0 -3
  134. package/scripts/cost-runtime/cost-pricing.ts +0 -1
  135. package/scripts/cost-runtime/cost-tokens.ts +0 -1
  136. package/scripts/delegation/delegation-cli.ts +1 -1
  137. package/scripts/delegation/delegation-policy.ts +11 -1
  138. package/scripts/delegation/duplicate-read-guard.ts +0 -8
  139. package/scripts/delegation/fanout-cli.ts +1 -1
  140. package/scripts/delegation/fanout.ts +0 -2
  141. package/scripts/delegation/investigation-reads.ts +1 -164
  142. package/scripts/discovery/repo-map-logic.ts +0 -1
  143. package/scripts/discovery/repo-party-cli.ts +1 -1
  144. package/scripts/doctor/doctor-consumer.ts +4 -6
  145. package/scripts/doctor/doctor-logic.ts +0 -4
  146. package/scripts/doctor/doctor.ts +14 -9
  147. package/scripts/document/document-byte-budget.ts +8 -9
  148. package/scripts/document/document-byte-check.ts +0 -1
  149. package/scripts/document/document-read-cli.ts +0 -2
  150. package/scripts/document/document-section-cli.ts +1 -1
  151. package/scripts/document/document-section.ts +0 -3
  152. package/scripts/document/edit-files-cli.ts +0 -1
  153. package/scripts/document/entry-read-budget.ts +13 -4
  154. package/scripts/document/lane-child-read-set.ts +1 -6
  155. package/scripts/document/read-files-cli.ts +0 -4
  156. package/scripts/document/read-set-cli.ts +0 -2
  157. package/scripts/document/read-set-point-of-use.ts +1 -0
  158. package/scripts/document/read-set-pricing.ts +0 -1
  159. package/scripts/document/read-set-trim.ts +1 -1
  160. package/scripts/epic/epic-add.ts +1 -1
  161. package/scripts/epic/epic-audit-cli.ts +1 -4
  162. package/scripts/epic/epic-audit-orphans.ts +1 -3
  163. package/scripts/epic/epic-audit-rationale.ts +0 -1
  164. package/scripts/epic/epic-audit-report.ts +0 -2
  165. package/scripts/epic/epic-audit.ts +5 -4
  166. package/scripts/epic/epic-bundle-cli.ts +1 -1
  167. package/scripts/epic/epic-bundle-evidence.ts +0 -1
  168. package/scripts/epic/epic-bundle-referenced.ts +1 -5
  169. package/scripts/epic/epic-bundle.ts +0 -4
  170. package/scripts/epic/epic-busy.ts +4 -5
  171. package/scripts/epic/epic-candidate-confirm.ts +0 -5
  172. package/scripts/epic/epic-classify.ts +2 -2
  173. package/scripts/epic/epic-cli-argv.ts +0 -1
  174. package/scripts/epic/epic-close-comment.ts +1 -1
  175. package/scripts/epic/epic-close.ts +5 -2
  176. package/scripts/epic/epic-cross-repo.ts +2 -4
  177. package/scripts/epic/epic-decision.ts +0 -1
  178. package/scripts/epic/epic-fetch.ts +2 -2
  179. package/scripts/epic/epic-index.ts +2 -3
  180. package/scripts/epic/epic-issue.ts +1 -15
  181. package/scripts/epic/epic-lane-offer.ts +20 -30
  182. package/scripts/epic/epic-nested.ts +1 -1
  183. package/scripts/epic/epic-next-read.ts +1 -3
  184. package/scripts/epic/epic-next-views.ts +0 -1
  185. package/scripts/epic/epic-next.ts +1 -10
  186. package/scripts/epic/epic-parse.ts +0 -1
  187. package/scripts/epic/epic-promote.ts +0 -9
  188. package/scripts/epic/epic-rank.ts +37 -0
  189. package/scripts/epic/epic-read.ts +1 -2
  190. package/scripts/epic/epic-reconcile.ts +1 -1
  191. package/scripts/epic/epic-reference.ts +1 -13
  192. package/scripts/epic/epic-relation-recheck.ts +1 -1
  193. package/scripts/epic/epic-relations.ts +1 -1
  194. package/scripts/epic/epic-remove.ts +1 -1
  195. package/scripts/epic/epic-report.ts +0 -1
  196. package/scripts/epic/epic-run.ts +2 -3
  197. package/scripts/epic/epic-shape.ts +1 -1
  198. package/scripts/epic/epic-solo-stale.ts +107 -0
  199. package/scripts/epic/epic-solo.ts +32 -30
  200. package/scripts/epic/epic-triage.ts +1 -1
  201. package/scripts/epic/epic-validate.ts +1 -1
  202. package/scripts/epic/epic.ts +1 -1
  203. package/scripts/exports/unused-members-cli.ts +53 -0
  204. package/scripts/{git → followup}/followup-issue-number.ts +1 -6
  205. package/scripts/{git → followup}/git-followup-cleanup.ts +2 -1
  206. package/scripts/{git → followup}/git-followup-finish.ts +1 -3
  207. package/scripts/{git → followup}/git-followup-flush.ts +2 -2
  208. package/scripts/{git → followup}/git-followup-issue-close.ts +2 -2
  209. package/scripts/{git → followup}/git-followup-label.ts +4 -4
  210. package/scripts/followup/git-followup-merged.ts +112 -0
  211. package/scripts/{git → followup}/git-followup-pending.ts +1 -1
  212. package/scripts/{git → followup}/git-followup-workflow.ts +19 -11
  213. package/scripts/{git → followup}/git-pr-followup-wrapup.ts +9 -3
  214. package/scripts/{git → followup}/git-pr-followup.ts +53 -16
  215. package/scripts/gate/core-budget.ts +3 -9
  216. package/scripts/gate/gate-plan.ts +7 -4
  217. package/scripts/gate/gate-report.ts +0 -6
  218. package/scripts/gate/gate-tree.ts +1 -1
  219. package/scripts/gate/pre-commit-type-check.ts +0 -6
  220. package/scripts/gate/project-checks.ts +7 -8
  221. package/scripts/gate/scoped-green.ts +0 -1
  222. package/scripts/gate/type-check-step.ts +1 -1
  223. package/scripts/gate/verification-gate.ts +0 -2
  224. package/scripts/gh/fix-gh-packages.ts +15 -12
  225. package/scripts/gh/gh-spawn.ts +2 -2
  226. package/scripts/{git → gh}/git-ai-review-scan.ts +1 -5
  227. package/scripts/{git → gh}/git-gh-check.ts +2 -1
  228. package/scripts/{git → gh}/git-gh-exec.ts +1 -1
  229. package/scripts/{git → gh}/git-gh-failure.ts +1 -1
  230. package/scripts/{git → gh}/git-gh-helpers.ts +0 -1
  231. package/scripts/{git → gh}/git-gh-issue-list.ts +2 -0
  232. package/scripts/{git → gh}/git-gh-issue-read.ts +1 -1
  233. package/scripts/{git → gh}/git-gh-issue-rest.ts +6 -3
  234. package/scripts/{git → gh}/git-gh-pr-read.ts +24 -0
  235. package/scripts/{git → gh}/git-gh-pr-rest.ts +5 -2
  236. package/scripts/{git → gh}/git-gh-pr-review.ts +2 -2
  237. package/scripts/{git → gh}/git-gh-pr-rollup.ts +1 -3
  238. package/scripts/{git → gh}/git-gh-pr.ts +3 -1
  239. package/scripts/{git → gh}/git-pr-ai-review.ts +2 -5
  240. package/scripts/{git → gh}/git-pr-checks-eval.ts +0 -10
  241. package/scripts/{git → gh}/git-pr-checks-parse.ts +4 -20
  242. package/scripts/{git → gh}/git-pr-checks.ts +2 -1
  243. package/scripts/{git → gh}/git-pr-coderabbit.ts +2 -5
  244. package/scripts/{git → gh}/git-pr-confirmation.ts +1 -3
  245. package/scripts/{git → gh}/git-pr-managed-config.ts +2 -3
  246. package/scripts/{git → gh}/git-pr.ts +3 -3
  247. package/scripts/git/changed-file-scope.ts +0 -1
  248. package/scripts/git/git-branch.ts +3 -6
  249. package/scripts/git/git-command.ts +1 -1
  250. package/scripts/git/git-commit.ts +1 -1
  251. package/scripts/git/git-common-directory.ts +80 -24
  252. package/scripts/git/git-error.ts +66 -9
  253. package/scripts/git/git-execa-error.ts +6 -1
  254. package/scripts/git/git-issue.ts +1 -1
  255. package/scripts/git/git-preflight.ts +24 -6
  256. package/scripts/git/git-prompt.ts +6 -31
  257. package/scripts/git/git-push-transport.ts +12 -69
  258. package/scripts/git/git-push.ts +1 -1
  259. package/scripts/git/git-spawn.ts +79 -3
  260. package/scripts/git/git-ssh-keepalive.ts +74 -0
  261. package/scripts/git/git-staging.ts +1 -0
  262. package/scripts/git/git-status.ts +1 -1
  263. package/scripts/git/git-workflow.ts +1 -1
  264. package/scripts/git/gone-branch.ts +4 -2
  265. package/scripts/git/listing-cutoff.ts +1 -1
  266. package/scripts/git/main-merge.ts +1 -1
  267. package/scripts/git/main-sync.ts +19 -1
  268. package/scripts/git/parse-json-array.ts +0 -3
  269. package/scripts/git/repository-lock.ts +16 -16
  270. package/scripts/git/{git-stash.ts → stash/git-stash.ts} +1 -1
  271. package/scripts/git/{stash-pop-args.ts → stash/stash-pop-args.ts} +1 -1
  272. package/scripts/git/{stash-pop-cli.ts → stash/stash-pop-cli.ts} +0 -1
  273. package/scripts/git/{stash-sweep-lock.ts → stash/stash-sweep-lock.ts} +3 -3
  274. package/scripts/hooks/codex-hook-adapter.ts +0 -1
  275. package/scripts/hooks/edited-cspell.ts +1 -1
  276. package/scripts/hooks/format-edited-cli.ts +15 -0
  277. package/scripts/hooks/format-edited-file.ts +4 -9
  278. package/scripts/hooks/pre-push-unit.ts +0 -4
  279. package/scripts/hooks/pretool-guard-cli.ts +21 -0
  280. package/scripts/hooks/pretool-guard.ts +36 -19
  281. package/scripts/hooks/reserved-run.ts +1 -1
  282. package/scripts/hooks/step-zero-notice.ts +76 -0
  283. package/scripts/hooks/stop-guard.ts +47 -21
  284. package/scripts/init/claude-plugin-config.ts +4 -5
  285. package/scripts/init/hook-command-rewrite.ts +16 -8
  286. package/scripts/init/hook-launch.ts +41 -15
  287. package/scripts/init/init-basic.ts +2 -5
  288. package/scripts/init/init-logic.ts +7 -0
  289. package/scripts/init/init.ts +0 -3
  290. package/scripts/init/project-profile.ts +6 -11
  291. package/scripts/init/start-exec.ts +8 -2
  292. package/scripts/init/start-plan.ts +18 -5
  293. package/scripts/init/start-setup-pr.ts +52 -25
  294. package/scripts/init/start-steps.ts +42 -9
  295. package/scripts/init/start.ts +40 -10
  296. package/scripts/issue/behavior-change-lint.ts +0 -1
  297. package/scripts/issue/defect-rate-cli.ts +3 -3
  298. package/scripts/issue/defect-rate.ts +1 -2
  299. package/scripts/{git → issue}/git-next-issues.ts +37 -8
  300. package/scripts/issue/issue-backlinks-cli.ts +1 -5
  301. package/scripts/issue/issue-cite-cli.ts +2 -6
  302. package/scripts/issue/issue-cite.ts +19 -3
  303. package/scripts/issue/issue-classification.ts +1 -9
  304. package/scripts/issue/issue-closing-pr.ts +4 -4
  305. package/scripts/issue/issue-comment-cli.ts +2 -2
  306. package/scripts/issue/issue-file-cli.ts +5 -5
  307. package/scripts/issue/issue-file.ts +2 -2
  308. package/scripts/issue/issue-fold-cli.ts +1 -1
  309. package/scripts/issue/issue-fold-existing-cli.ts +1 -1
  310. package/scripts/{git → issue}/issue-labels.ts +20 -0
  311. package/scripts/issue/issue-lint-cli.ts +1 -1
  312. package/scripts/issue/issue-merged.ts +2 -2
  313. package/scripts/issue/issue-rank.ts +89 -0
  314. package/scripts/issue/issue-read-cli.ts +8 -47
  315. package/scripts/issue/issue-read.ts +0 -2
  316. package/scripts/issue/issue-report-failures.ts +80 -0
  317. package/scripts/issue/issue-scout-cli.ts +1 -7
  318. package/scripts/issue/issue-scout-closed.ts +2 -2
  319. package/scripts/issue/issue-scout.ts +0 -2
  320. package/scripts/issue/issue-state-cli.ts +5 -46
  321. package/scripts/issue/issue-state.ts +1 -5
  322. package/scripts/issue/markdown-section.ts +1 -1
  323. package/scripts/issue/measure-rerun-cli.ts +1 -1
  324. package/scripts/issue/reproduction-measure.ts +1 -1
  325. package/scripts/josh/cli-body.ts +1 -1
  326. package/scripts/josh/file-map-stamp.ts +0 -3
  327. package/scripts/josh/josh-command-map.ts +2 -102
  328. package/scripts/josh/josh-commands-ai.ts +9 -102
  329. package/scripts/josh/josh-commands-backlog.ts +5 -1
  330. package/scripts/josh/josh-commands-development.ts +10 -3
  331. package/scripts/josh/josh-commands-issue.ts +2 -2
  332. package/scripts/josh/josh-commands-maintenance.ts +7 -0
  333. package/scripts/josh/josh-commands-project.ts +7 -1
  334. package/scripts/josh/josh-commands-run.ts +114 -0
  335. package/scripts/josh/josh-commands-versioning.ts +8 -0
  336. package/scripts/josh/josh-commands-workflow.ts +2 -2
  337. package/scripts/josh/path-decision.ts +0 -3
  338. package/scripts/josh/process-owner.ts +16 -0
  339. package/scripts/josh/session-language-cli.ts +0 -1
  340. package/scripts/lane/lane-await.ts +3 -6
  341. package/scripts/lane/lane-capacity.ts +6 -3
  342. package/scripts/lane/lane-child-invocation.ts +20 -7
  343. package/scripts/lane/lane-child-marker.ts +16 -2
  344. package/scripts/lane/lane-cli.ts +33 -18
  345. package/scripts/lane/lane-close.ts +1 -3
  346. package/scripts/lane/lane-dispatch.ts +3 -4
  347. package/scripts/lane/lane-environment.ts +0 -9
  348. package/scripts/lane/lane-handoff.ts +38 -0
  349. package/scripts/lane/lane-launch-cli.ts +21 -11
  350. package/scripts/lane/lane-open.ts +0 -3
  351. package/scripts/lane/lane-output.ts +0 -1
  352. package/scripts/lane/lane-registry.ts +9 -0
  353. package/scripts/lane/lane-report.ts +10 -4
  354. package/scripts/lane/openai-lane-ship.ts +2 -0
  355. package/scripts/lane/openai-lane-supervisor-decision.ts +2 -1
  356. package/scripts/lane/openai-lane-supervisor.ts +6 -4
  357. package/scripts/lane/openai-review-broker.ts +3 -0
  358. package/scripts/lib/bounded-pool.ts +1 -0
  359. package/scripts/lib/json-value.ts +5 -4
  360. package/scripts/lib/poll.ts +3 -1
  361. package/scripts/lib/timed-fetch.ts +15 -0
  362. package/scripts/lib/timeouts.ts +13 -0
  363. package/scripts/{yaml → lib}/yaml-document.ts +3 -1
  364. package/scripts/{bytes → lines}/bytes-command.ts +0 -2
  365. package/scripts/lines/lines-command.ts +0 -1
  366. package/scripts/lint/lint-related-scope.ts +0 -1
  367. package/scripts/lint/lint-related.ts +1 -1
  368. package/scripts/{git → notify}/telegram-notify.ts +2 -2
  369. package/scripts/{git → notify}/telegram-test.ts +4 -4
  370. package/scripts/observations/observation-ledger-home.ts +0 -1
  371. package/scripts/observations/observation-ledger-line.ts +0 -1
  372. package/scripts/observations/observation-ledger-migrate.ts +1 -1
  373. package/scripts/observations/observations-flush-landing.ts +3 -3
  374. package/scripts/observations/observations-flush.ts +2 -2
  375. package/scripts/overrides/lockfile-overrides.ts +1 -1
  376. package/scripts/overrides/overrides-logic.ts +1 -1
  377. package/scripts/package/package-scout-cli.ts +2 -7
  378. package/scripts/package/package-scout.ts +0 -2
  379. package/scripts/propagate/propagate-git-failure.ts +0 -1
  380. package/scripts/propagate/propagate-git.ts +0 -2
  381. package/scripts/propagate/propagate-plan.ts +185 -0
  382. package/scripts/propagate/propagate-publish.ts +2 -3
  383. package/scripts/propagate/propagate-run.ts +5 -3
  384. package/scripts/propagate/propagate-steps.ts +19 -6
  385. package/scripts/propagate/propagate-targets.ts +13 -6
  386. package/scripts/propagate/propagate.ts +60 -46
  387. package/scripts/refactor/import-graph.ts +0 -1
  388. package/scripts/refactor/refactor-lint.ts +0 -2
  389. package/scripts/refactor/refactor-scan.ts +0 -1
  390. package/scripts/refactor/refactor-targets.ts +0 -1
  391. package/scripts/registry-migration/migrate-logic.ts +1 -2
  392. package/scripts/registry-migration/migrate.ts +19 -9
  393. package/scripts/release/github-release-cli.ts +8 -6
  394. package/scripts/release/github-release-environment.ts +46 -0
  395. package/scripts/release/github-release.ts +151 -94
  396. package/scripts/release/release-history.ts +0 -1
  397. package/scripts/release/release-plan.ts +0 -1
  398. package/scripts/release/release-publish.ts +2 -3
  399. package/scripts/release/release-scope-cli.ts +1 -2
  400. package/scripts/release/release-tag.ts +2 -4
  401. package/scripts/repo/auto-merge-setting-logic.ts +1 -4
  402. package/scripts/repo/repo-setting.ts +1 -1
  403. package/scripts/repo/repository-labels.ts +3 -3
  404. package/scripts/repo/required-checks-logic.ts +206 -0
  405. package/scripts/repo/required-checks-report.ts +71 -0
  406. package/scripts/repo/ruleset-check-cli.ts +53 -0
  407. package/scripts/repo/ruleset-check.ts +147 -0
  408. package/scripts/report/report-lint-cli.ts +0 -4
  409. package/scripts/review/disposition-cli.ts +1 -1
  410. package/scripts/review/live-evidence.ts +1 -1
  411. package/scripts/review/review-attest-cli.ts +0 -6
  412. package/scripts/review/review-attest.ts +48 -16
  413. package/scripts/review/review-brief-cli.ts +72 -8
  414. package/scripts/review/review-brief.ts +67 -9
  415. package/scripts/review/review-diff-parts.ts +89 -0
  416. package/scripts/review/review-diff-write.ts +97 -0
  417. package/scripts/review/review-finding-ledger.ts +0 -1
  418. package/scripts/review/review-record-cli.ts +1 -1
  419. package/scripts/review/review-record.ts +0 -1
  420. package/scripts/review/review-round2-cli.ts +1 -1
  421. package/scripts/review/review-stamps.ts +6 -2
  422. package/scripts/review/review-tree.ts +14 -2
  423. package/scripts/rules/decision-oracle-stage.ts +18 -0
  424. package/scripts/rules/decision-oracle.ts +10 -7
  425. package/scripts/rules/delivered-rules.ts +9 -1
  426. package/scripts/rules/destructive-command.ts +119 -0
  427. package/scripts/rules/direct-filing.ts +1 -1
  428. package/scripts/rules/file-body.ts +0 -3
  429. package/scripts/rules/filing-cap.ts +0 -2
  430. package/scripts/rules/gh-api.ts +7 -2
  431. package/scripts/rules/git-argv.ts +18 -7
  432. package/scripts/rules/git-force.ts +0 -2
  433. package/scripts/rules/implementation-cut.ts +1 -2
  434. package/scripts/rules/index-guard.ts +71 -0
  435. package/scripts/rules/issue-citation.ts +50 -25
  436. package/scripts/rules/issue-fold.ts +1 -2
  437. package/scripts/rules/lane-background.ts +15 -7
  438. package/scripts/rules/lane-carry-conflict.ts +1 -2
  439. package/scripts/rules/lane-park.ts +14 -8
  440. package/scripts/rules/last-prompt.ts +1 -0
  441. package/scripts/rules/oracle-consulted.ts +1 -1
  442. package/scripts/rules/oracle-firing.ts +1 -0
  443. package/scripts/rules/oracle-list-cli.ts +0 -3
  444. package/scripts/rules/permission-guards.ts +14 -0
  445. package/scripts/rules/pre-gate-cut.ts +2 -2
  446. package/scripts/rules/protected-files.ts +74 -0
  447. package/scripts/rules/reply-language.ts +1 -1
  448. package/scripts/rules/rule-body-guard.ts +0 -3
  449. package/scripts/rules/rule-value-cli.ts +0 -1
  450. package/scripts/rules/shell-body-trigger.ts +0 -1
  451. package/scripts/rules/shell-segments.ts +0 -1
  452. package/scripts/rules/stop-rules.ts +13 -5
  453. package/scripts/rules/tail-commands.ts +1 -1
  454. package/scripts/rules/test-declared-commit.ts +1 -1
  455. package/scripts/rules/third-party-write.ts +0 -1
  456. package/scripts/rules/worktree-guard.ts +26 -9
  457. package/scripts/run/run-carry-cli.ts +0 -2
  458. package/scripts/run/run-carry-stash.ts +2 -2
  459. package/scripts/run/run-carry-streak.ts +1 -1
  460. package/scripts/run/run-cut-cli.ts +0 -7
  461. package/scripts/run/run-cut.ts +8 -54
  462. package/scripts/run/run-ending-cli.ts +1 -1
  463. package/scripts/run/run-ending.ts +1 -4
  464. package/scripts/run/run-entry-cli.ts +101 -24
  465. package/scripts/run/run-entry.ts +0 -1
  466. package/scripts/run/run-event-cli.ts +0 -2
  467. package/scripts/run/run-event-follow.ts +1 -0
  468. package/scripts/run/run-event-render.ts +1 -1
  469. package/scripts/run/run-event-scope.ts +53 -4
  470. package/scripts/run/run-event-stream.ts +0 -3
  471. package/scripts/run/run-event-watch.ts +1 -0
  472. package/scripts/run/run-halfrun-resume.ts +20 -7
  473. package/scripts/run/run-headless.ts +24 -8
  474. package/scripts/run/run-hold-cli.ts +33 -10
  475. package/scripts/run/run-hold.ts +25 -5
  476. package/scripts/run/run-issue-number.ts +1 -1
  477. package/scripts/run/run-liveness-cli.ts +1 -1
  478. package/scripts/run/run-liveness.ts +2 -3
  479. package/scripts/run/run-merge-cli.ts +5 -13
  480. package/scripts/run/run-merge-steps.ts +5 -5
  481. package/scripts/run/run-merge.ts +1 -1
  482. package/scripts/run/run-next-cli.ts +1 -1
  483. package/scripts/run/run-parent-cut-hook.ts +143 -0
  484. package/scripts/run/run-preflight.ts +2 -9
  485. package/scripts/run/run-prep-cli.ts +2 -2
  486. package/scripts/run/run-prep-locate.ts +1 -1
  487. package/scripts/run/run-progress-cli.ts +4 -7
  488. package/scripts/run/run-progress-clock.ts +6 -34
  489. package/scripts/run/run-progress-config.ts +0 -4
  490. package/scripts/run/run-progress-read.ts +3 -3
  491. package/scripts/run/run-progress.ts +22 -10
  492. package/scripts/run/run-prrun-resume.ts +117 -0
  493. package/scripts/run/run-report-cli.ts +1 -2
  494. package/scripts/run/run-report.ts +0 -1
  495. package/scripts/run/run-retrospective.ts +1 -1
  496. package/scripts/run/run-review-cli.ts +0 -3
  497. package/scripts/run/run-review-steps.ts +2 -0
  498. package/scripts/run/run-review.ts +0 -2
  499. package/scripts/run/run-ship-cli.ts +27 -77
  500. package/scripts/run/run-ship-detach.ts +1 -0
  501. package/scripts/run/run-ship-next.ts +106 -0
  502. package/scripts/run/run-ship-preflight.ts +87 -0
  503. package/scripts/run/run-ship-return.ts +8 -3
  504. package/scripts/run/run-ship-review-steps.ts +23 -30
  505. package/scripts/run/run-ship-review.ts +20 -0
  506. package/scripts/run/run-ship-scoped.ts +45 -0
  507. package/scripts/run/run-ship-stage.ts +5 -1
  508. package/scripts/run/run-ship-steps.ts +105 -0
  509. package/scripts/run/run-ship.ts +2 -1
  510. package/scripts/run/run-stage-read.ts +62 -0
  511. package/scripts/run/run-stage.ts +160 -0
  512. package/scripts/run/run-status-cli.ts +1 -1
  513. package/scripts/run/run-step-cli.ts +21 -14
  514. package/scripts/run/run-step.ts +1 -1
  515. package/scripts/run/run-stop-notify.ts +1 -3
  516. package/scripts/run/run-stranded-cli.ts +0 -4
  517. package/scripts/run/run-stranded-detect.ts +2 -2
  518. package/scripts/run/run-tail-cli.ts +17 -11
  519. package/scripts/run/run-tail.ts +2 -0
  520. package/scripts/run/run-tidy-lanes.ts +2 -1
  521. package/scripts/run/run-tidy-stashes.ts +12 -12
  522. package/scripts/run/run-wake-cli.ts +4 -5
  523. package/scripts/run/run-wake-describe.ts +19 -1
  524. package/scripts/run/run-wake-loop.ts +2 -2
  525. package/scripts/run/run-wake.ts +2 -4
  526. package/scripts/run/run-watcher-guard-cli.ts +0 -4
  527. package/scripts/run/run-watcher-guard.ts +9 -7
  528. package/scripts/run/run-watcher-hook.ts +1 -1
  529. package/scripts/safe-chain/ci-installer-pin.ts +11 -5
  530. package/scripts/safe-chain/project-config.ts +1 -1
  531. package/scripts/security/security-audit-provision-logic.ts +0 -1
  532. package/scripts/security/security-audit-provision.ts +3 -2
  533. package/scripts/security/security-audit.ts +0 -1
  534. package/scripts/security/security-updates-logic.ts +0 -1
  535. package/scripts/sonar/sonar-hotspots-cli.ts +10 -52
  536. package/scripts/sonar/sonar-hotspots.ts +0 -1
  537. package/scripts/sonar/sonar-new-code-cli.ts +150 -0
  538. package/scripts/sonar/sonar-new-code.ts +79 -0
  539. package/scripts/sonar/sonar-project.ts +97 -0
  540. package/scripts/split/split-assess-cli.ts +1 -1
  541. package/scripts/split/split-assess.ts +2 -5
  542. package/scripts/sync/managed-config-scope-cli.ts +1 -1
  543. package/scripts/sync/skill-migration.ts +0 -1
  544. package/scripts/sync/sync-hook-safety.ts +2 -3
  545. package/scripts/sync/sync.ts +1 -8
  546. package/scripts/sync/template-source-logic.ts +0 -1
  547. package/scripts/sync/workflow-pin-logic.ts +16 -28
  548. package/scripts/test/test-declared-logic.ts +0 -1
  549. package/scripts/test/test-declared.ts +0 -2
  550. package/scripts/test/test-red-commit.ts +2 -2
  551. package/scripts/test/test-red.ts +14 -7
  552. package/scripts/test/test-type-logic.ts +1 -1
  553. package/scripts/test/unit-worker-share.ts +7 -16
  554. package/scripts/time-runtime/time-batch-guard.ts +1 -2
  555. package/scripts/time-runtime/time-bundle-call.ts +2 -3
  556. package/scripts/time-runtime/time-bundles.ts +0 -4
  557. package/scripts/time-runtime/time-density-hook.ts +0 -2
  558. package/scripts/time-runtime/time-followup-stage.ts +1 -1
  559. package/scripts/time-runtime/time-format.ts +0 -73
  560. package/scripts/time-runtime/time-round-trips.ts +0 -4
  561. package/scripts/time-runtime/time-single-check.ts +1 -1
  562. package/scripts/time-runtime/time-spans.ts +1 -1
  563. package/scripts/time-runtime/time-transcript-line.ts +1 -2
  564. package/scripts/ui/ui-routes-cli.ts +0 -5
  565. package/scripts/ui/ui-routes.ts +0 -5
  566. package/scripts/version/effective-upstream.ts +2 -2
  567. package/scripts/version/latest-corepack.ts +7 -9
  568. package/scripts/version/latest-scope-cli.ts +0 -2
  569. package/scripts/version/latest-update.ts +0 -2
  570. package/scripts/version/npm-registry.ts +97 -0
  571. package/scripts/version/publishable-range-check.ts +1 -1
  572. package/scripts/version/release-hold.ts +0 -4
  573. package/scripts/version/running-binary.ts +2 -2
  574. package/scripts/version/version-check-logic.ts +0 -2
  575. package/scripts/version/version-commands.ts +5 -4
  576. package/scripts/version/version-remote.ts +28 -14
  577. package/scripts/version/version-targets.ts +4 -4
  578. package/templates/sonar-project.properties +28 -1
  579. package/templates/workflows/dependabot-auto-merge.yml +5 -3
  580. package/templates/workflows/github-release.yml +81 -0
  581. package/prompts/collaboration-workflow/cross-repo-epic.md +0 -5
  582. package/prompts/collaboration-workflow/epic-audit.md +0 -5
  583. package/prompts/collaboration-workflow/epic-bundle.md +0 -5
  584. package/scripts/init/plugin-install-hint.ts +0 -47
  585. package/scripts/version/parse-json.ts +0 -13
  586. /package/scripts/{git → followup}/git-followup-stages.ts +0 -0
  587. /package/scripts/{git → gh}/git-gh-api-path.ts +0 -0
  588. /package/scripts/{git → gh}/git-gh-command.ts +0 -0
  589. /package/scripts/{git → gh}/git-gh-issue-write.ts +0 -0
  590. /package/scripts/{git → gh}/git-gh-issue.ts +0 -0
  591. /package/scripts/{git → gh}/git-gh-pr-auto-merge.ts +0 -0
  592. /package/scripts/{git → gh}/git-gh-pr-snapshot.ts +0 -0
  593. /package/scripts/{git → gh}/git-gh-reachability.ts +0 -0
  594. /package/scripts/{git → gh}/git-gh-repo.ts +0 -0
  595. /package/scripts/{git → gh}/git-gh-rest-state.ts +0 -0
  596. /package/scripts/{git → gh}/git-pr-checks-watch.ts +0 -0
  597. /package/scripts/{git → gh}/git-pr-error.ts +0 -0
  598. /package/scripts/{git → gh}/git-pr-messages.ts +0 -0
  599. /package/scripts/{git → gh}/github-issue-url.ts +0 -0
  600. /package/scripts/git/{stash-orphans.ts → stash/stash-orphans.ts} +0 -0
  601. /package/scripts/{git → issue}/issue-reference.ts +0 -0
  602. /package/scripts/{git → lib}/animation-helpers.ts +0 -0
  603. /package/scripts/{git → lib}/git-animation.ts +0 -0
  604. /package/scripts/{git → notify}/git-notify.ts +0 -0
  605. /package/scripts/{git → notify}/telegram-test-logic.ts +0 -0
@@ -0,0 +1,206 @@
1
+ import { yaml_document } from '#scripts/lib/yaml-document'
2
+ import { z } from 'zod'
3
+
4
+ // The status checks kit's distributed workflows report, keyed by the workflow that reports them —
5
+ // the single source `josh ruleset:check` and `josh doctor` compare a repository's required status
6
+ // checks against (joshuafolkken/kit#3012). Each name is the job's `name:`, the string a ruleset's
7
+ // required check is matched by; `required-checks-logic.test.ts` reads it back off the workflows.
8
+ //
9
+ // `E2E` belongs here even though `josh followup` deliberately does not wait on it
10
+ // (joshuafolkken/kit#991): a ruleset counts a skipped job as passing, and the job is skipped when the
11
+ // project has no E2E suite, so requiring it never blocks a merge — it only refuses a red one.
12
+ interface WorkflowChecks {
13
+ workflow: string
14
+ checks: ReadonlyArray<string>
15
+ }
16
+
17
+ const CI_WORKFLOW = '.github/workflows/ci.yml'
18
+ const SONAR_WORKFLOW = '.github/workflows/sonar-qube.yml'
19
+ const CLASSIFICATION_WORKFLOW = '.github/workflows/pr-classification.yml'
20
+ const REQUIRED_STATUS_CHECKS = 'required_status_checks'
21
+ const NOT_FOUND_STATUS = 404
22
+
23
+ const DISTRIBUTED_CHECKS: ReadonlyArray<WorkflowChecks> = [
24
+ { workflow: CI_WORKFLOW, checks: ['Checks', 'Detect E2E', 'E2E', 'Security Audit'] },
25
+ { workflow: SONAR_WORKFLOW, checks: ['SonarQube'] },
26
+ { workflow: CLASSIFICATION_WORKFLOW, checks: ['Release classification'] },
27
+ ]
28
+
29
+ // Where the repository's required checks live. A ruleset is the source `--apply` writes to; classic
30
+ // branch protection is read beside it, and is the source for a repository that never moved to rulesets.
31
+ type RequiredSource =
32
+ | { kind: 'ruleset'; ruleset_id: number; contexts: ReadonlyArray<string> }
33
+ | { kind: 'protection'; contexts: ReadonlyArray<string> }
34
+ | { kind: 'none' }
35
+ | { kind: 'unreadable' }
36
+
37
+ const status_check_schema = z.looseObject({ context: z.string() })
38
+ const branch_rule_schema = z.looseObject({
39
+ type: z.literal(REQUIRED_STATUS_CHECKS),
40
+ ruleset_id: z.number(),
41
+ parameters: z.looseObject({ required_status_checks: z.array(status_check_schema) }),
42
+ })
43
+ const protection_schema = z.looseObject({ contexts: z.array(z.string()) })
44
+ const ruleset_rule_schema = z.looseObject({
45
+ type: z.string(),
46
+ parameters: z
47
+ .looseObject({ required_status_checks: z.array(status_check_schema).optional() })
48
+ .optional(),
49
+ })
50
+ const ruleset_schema = z.looseObject({ rules: z.array(ruleset_rule_schema) })
51
+ const workflow_job_schema = z.looseObject({ name: z.string().optional() })
52
+ const workflow_schema = z.looseObject({ jobs: z.record(z.string(), workflow_job_schema) })
53
+
54
+ // One `gh api` read: the body it answered, or the HTTP status it failed with (`undefined` when
55
+ // nothing answered at all).
56
+ type ApiRead = { kind: 'body'; stdout: string } | { kind: 'failed'; status: number | undefined }
57
+
58
+ type BranchRule = z.infer<typeof branch_rule_schema>
59
+ type RulesetRule = z.infer<typeof ruleset_rule_schema>
60
+
61
+ function parse_yaml_safe(content: string): unknown {
62
+ try {
63
+ return yaml_document.parse_yaml(content)
64
+ } catch {
65
+ return undefined
66
+ }
67
+ }
68
+
69
+ // The job `name:`s a workflow defines — the contexts it reports. A file that does not parse, or has
70
+ // no `jobs` mapping, reports nothing.
71
+ function workflow_job_names(content: string): Array<string> {
72
+ const parsed = workflow_schema.safeParse(parse_yaml_safe(content))
73
+ if (!parsed.success) return []
74
+
75
+ return Object.values(parsed.data.jobs).flatMap((job) =>
76
+ job.name === undefined ? [] : [job.name],
77
+ )
78
+ }
79
+
80
+ // The checks this repository should require: only those a workflow it has actually reports. A file
81
+ // of the same name is not enough — a project's own `ci.yml` with a `build` job is never told to
82
+ // require `Checks`, which nothing there would ever report.
83
+ function expected_checks(read_workflow: (workflow: string) => string | undefined): Array<string> {
84
+ return DISTRIBUTED_CHECKS.flatMap((entry) => {
85
+ const content = read_workflow(entry.workflow)
86
+ if (content === undefined) return []
87
+ const reported = workflow_job_names(content)
88
+
89
+ return entry.checks.filter((check) => reported.includes(check))
90
+ })
91
+ }
92
+
93
+ function parse_json(stdout: string): unknown {
94
+ try {
95
+ return JSON.parse(stdout)
96
+ } catch {
97
+ return undefined
98
+ }
99
+ }
100
+
101
+ function status_check_rules(payload: unknown): Array<BranchRule> {
102
+ if (!Array.isArray(payload)) return []
103
+
104
+ return payload.flatMap((entry: unknown) => {
105
+ const parsed = branch_rule_schema.safeParse(entry)
106
+
107
+ return parsed.success ? [parsed.data] : []
108
+ })
109
+ }
110
+
111
+ // `GET repos/{owner}/{repo}/rules/branches/{branch}` — every rule in force on the branch, across
112
+ // rulesets. The contexts of all of them are combined, since each is enforced; the first ruleset is the
113
+ // one `--apply` writes to. No `required_status_checks` rule at all reads as `none`.
114
+ function parse_branch_rules(stdout: string | undefined): RequiredSource {
115
+ const payload = stdout === undefined ? undefined : parse_json(stdout)
116
+ if (!Array.isArray(payload)) return { kind: 'unreadable' }
117
+ const rules = status_check_rules(payload)
118
+ const [first] = rules
119
+ if (first === undefined) return { kind: 'none' }
120
+ const contexts = rules.flatMap((rule) =>
121
+ rule.parameters.required_status_checks.map((check) => check.context),
122
+ )
123
+
124
+ return { kind: 'ruleset', ruleset_id: first.ruleset_id, contexts }
125
+ }
126
+
127
+ // `GET …/branches/{branch}/protection/required_status_checks`. GitHub answers a branch without
128
+ // protection (or without required checks) with a 404, which alone reads as `none`. Any other
129
+ // failure — a 403 for a caller without admin access, a 5xx, a timeout — or a body that is not the
130
+ // expected document reads as `unreadable`, so a check that is in fact required is never reported
131
+ // missing.
132
+ function parse_protection(read: ApiRead): RequiredSource {
133
+ if (read.kind === 'failed') {
134
+ return { kind: read.status === NOT_FOUND_STATUS ? 'none' : 'unreadable' }
135
+ }
136
+
137
+ const parsed = protection_schema.safeParse(parse_json(read.stdout))
138
+
139
+ return parsed.success
140
+ ? { kind: 'protection', contexts: parsed.data.contexts }
141
+ : { kind: 'unreadable' }
142
+ }
143
+
144
+ // GitHub enforces rulesets and classic protection together, so a check either one requires is
145
+ // required. The ruleset stays the source `--apply` writes to; a protection read that failed for any
146
+ // reason but a 404 leaves the whole answer `unreadable`, as a lone protection read would.
147
+ function combine_sources(rules: RequiredSource, protection: RequiredSource): RequiredSource {
148
+ if (rules.kind !== 'ruleset') return protection
149
+ if (protection.kind === 'none') return rules
150
+ if (protection.kind !== 'protection') return { kind: 'unreadable' }
151
+
152
+ return { ...rules, contexts: [...new Set([...rules.contexts, ...protection.contexts])] }
153
+ }
154
+
155
+ function missing_checks(
156
+ expected: ReadonlyArray<string>,
157
+ contexts: ReadonlyArray<string>,
158
+ ): Array<string> {
159
+ return expected.filter((check) => !contexts.includes(check))
160
+ }
161
+
162
+ function with_added_checks(rule: RulesetRule, missing: ReadonlyArray<string>): RulesetRule {
163
+ if (rule.type !== REQUIRED_STATUS_CHECKS || rule.parameters === undefined) return rule
164
+ const current = rule.parameters.required_status_checks ?? []
165
+ const added = missing.map((context) => ({ context }))
166
+
167
+ return {
168
+ ...rule,
169
+ parameters: { ...rule.parameters, required_status_checks: [...current, ...added] },
170
+ }
171
+ }
172
+
173
+ // The `PUT repos/{owner}/{repo}/rulesets/{id}` body: the ruleset's own rules, unchanged but for the
174
+ // missing contexts appended to its required-checks rule. `rules` replaces the whole list, so every
175
+ // other rule is carried over as read rather than dropped.
176
+ function ruleset_update_body(
177
+ ruleset_stdout: string,
178
+ missing: ReadonlyArray<string>,
179
+ ): string | undefined {
180
+ const parsed = ruleset_schema.safeParse(parse_json(ruleset_stdout))
181
+ if (!parsed.success) return undefined
182
+
183
+ return JSON.stringify({
184
+ rules: parsed.data.rules.map((rule) => with_added_checks(rule, missing)),
185
+ })
186
+ }
187
+
188
+ // The `POST …/protection/required_status_checks/contexts` body, which appends rather than replaces.
189
+ function protection_update_body(missing: ReadonlyArray<string>): string {
190
+ return JSON.stringify({ contexts: missing })
191
+ }
192
+
193
+ const required_checks_logic = {
194
+ DISTRIBUTED_CHECKS,
195
+ workflow_job_names,
196
+ expected_checks,
197
+ parse_branch_rules,
198
+ parse_protection,
199
+ combine_sources,
200
+ missing_checks,
201
+ ruleset_update_body,
202
+ protection_update_body,
203
+ }
204
+
205
+ export type { ApiRead, RequiredSource, WorkflowChecks }
206
+ export { required_checks_logic }
@@ -0,0 +1,71 @@
1
+ import { repo_setting } from './repo-setting'
2
+ import type { RequiredSource } from './required-checks-logic'
3
+
4
+ // What one read of a repository's required status checks found, against what kit's distributed
5
+ // workflows report (joshuafolkken/kit#3012).
6
+ interface RequiredChecksReport {
7
+ repo: string | undefined
8
+ branch: string | undefined
9
+ source: RequiredSource
10
+ expected: ReadonlyArray<string>
11
+ missing: ReadonlyArray<string>
12
+ }
13
+
14
+ const LABEL = 'Required status checks'
15
+ const APPLY_COMMAND = 'pnpm josh ruleset:check --apply'
16
+
17
+ function source_label(source: RequiredSource): string {
18
+ return source.kind === 'ruleset' ? `ruleset ${String(source.ruleset_id)}` : 'branch protection'
19
+ }
20
+
21
+ // An unreadable answer is never reported as a missing one — the same rule every repository-setting
22
+ // report follows — so it names the possible causes and no remediation.
23
+ function format_unreadable(report: RequiredChecksReport): Array<string> {
24
+ return [
25
+ ` ⚠ ${LABEL}: could not be read (${repo_setting.report_target(report.repo)}) — not checked, not necessarily missing`,
26
+ ' `gh` is missing or unauthenticated, or the request failed (offline, timed out).',
27
+ ]
28
+ }
29
+
30
+ // No ruleset or protection requires anything. `--apply` adds to an existing rule and never creates
31
+ // one — which branches a ruleset targets and who may bypass it are the maintainer's choices.
32
+ function format_none(report: RequiredChecksReport, target: string): Array<string> {
33
+ return [
34
+ ` ⚠ ${LABEL}: none required on ${String(report.branch)} (${target})`,
35
+ ` Expected: ${report.expected.join(', ')}`,
36
+ ' Create a branch ruleset (Settings → Rules → Rulesets) that requires them.',
37
+ ]
38
+ }
39
+
40
+ function format_found(report: RequiredChecksReport, target: string): Array<string> {
41
+ const where = `${String(report.branch)} (${target}, ${source_label(report.source)})`
42
+
43
+ if (report.missing.length === 0) {
44
+ return [` ✔ ${LABEL}: all ${String(report.expected.length)} kit checks required on ${where}`]
45
+ }
46
+
47
+ return [
48
+ ` ⚠ ${LABEL}: ${report.missing.join(', ')} not required on ${where}`,
49
+ ` Add: ${APPLY_COMMAND}`,
50
+ ]
51
+ }
52
+
53
+ function format_report(report: RequiredChecksReport): Array<string> {
54
+ const target = repo_setting.report_target(report.repo)
55
+ if (report.source.kind === 'unreadable') return format_unreadable(report)
56
+ if (report.source.kind === 'none') return format_none(report, target)
57
+
58
+ return format_found(report, target)
59
+ }
60
+
61
+ // Whether nothing is left to do: the checks were read and every expected one is required.
62
+ function is_complete(report: RequiredChecksReport): boolean {
63
+ const is_read = report.source.kind === 'ruleset' || report.source.kind === 'protection'
64
+
65
+ return is_read && report.missing.length === 0
66
+ }
67
+
68
+ const required_checks_report = { format_report, is_complete }
69
+
70
+ export type { RequiredChecksReport }
71
+ export { required_checks_report }
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env tsx
2
+ import { fileURLToPath } from 'node:url'
3
+ import { gh_spawn } from '#scripts/gh/gh-spawn'
4
+ import { PROJECT_ROOT } from '#scripts/init/init-paths'
5
+ import { repo_setting } from './repo-setting'
6
+ import { required_checks_report, type RequiredChecksReport } from './required-checks-report'
7
+ import { ruleset_check } from './ruleset-check'
8
+
9
+ // `josh ruleset:check [--apply]` — say whether the default branch requires every status check kit's
10
+ // distributed workflows report, and with `--apply` add the missing ones (joshuafolkken/kit#3012).
11
+ // Changing a repository setting is outward-facing, so the write happens only on the flag a person
12
+ // typed; `josh sync` and `josh doctor` never pass it. Exits non-zero while anything is left to do.
13
+
14
+ const ARGV_OFFSET = 2
15
+ const APPLY_FLAG = '--apply'
16
+ const FAILURE_EXIT_CODE = 1
17
+
18
+ function exit_code_for(report: RequiredChecksReport): number {
19
+ return required_checks_report.is_complete(report) ? 0 : FAILURE_EXIT_CODE
20
+ }
21
+
22
+ function apply(report: RequiredChecksReport): number {
23
+ try {
24
+ if (!ruleset_check.apply_missing(report)) return FAILURE_EXIT_CODE
25
+ } catch (error) {
26
+ console.error(`Could not add ${report.missing.join(', ')}: ${String(error)}`)
27
+
28
+ return FAILURE_EXIT_CODE
29
+ }
30
+
31
+ console.info(` ✔ Added: ${report.missing.join(', ')}`)
32
+
33
+ return 0
34
+ }
35
+
36
+ function run(argv: ReadonlyArray<string>): number {
37
+ const report = ruleset_check.inspect(gh_spawn.get_repo_name_with_owner(), PROJECT_ROOT)
38
+
39
+ repo_setting.print_section(required_checks_report.format_report(report))
40
+ const is_writable = argv.includes(APPLY_FLAG) && report.missing.length > 0
41
+
42
+ return is_writable ? apply(report) : exit_code_for(report)
43
+ }
44
+
45
+ function main(argv: ReadonlyArray<string>): void {
46
+ process.exitCode = run(argv)
47
+ }
48
+
49
+ const ruleset_check_cli = { APPLY_FLAG, run }
50
+
51
+ if (process.argv[1] === fileURLToPath(import.meta.url)) main(process.argv.slice(ARGV_OFFSET))
52
+
53
+ export { ruleset_check_cli }
@@ -0,0 +1,147 @@
1
+ import path from 'node:path'
2
+ import { git_gh_exec } from '#scripts/gh/git-gh-exec'
3
+ import { gh_failure } from '#scripts/gh/git-gh-failure'
4
+ import { PROJECT_ROOT } from '#scripts/init/init-paths'
5
+ import { file_reader } from '#scripts/lib/read-file'
6
+ import { repo_setting } from './repo-setting'
7
+ import { required_checks_logic, type ApiRead, type RequiredSource } from './required-checks-logic'
8
+ import { required_checks_report, type RequiredChecksReport } from './required-checks-report'
9
+
10
+ // Read a repository's required status checks on its default branch and compare them with the checks
11
+ // kit's distributed workflows report (joshuafolkken/kit#3012). Every read degrades to `unreadable`
12
+ // rather than throwing; the one write, `apply_missing`, runs only on `josh ruleset:check --apply`.
13
+
14
+ function read_api(api_path: string): string | undefined {
15
+ return git_gh_exec.read_gh_api_sync({
16
+ path: api_path,
17
+ cwd: PROJECT_ROOT,
18
+ timeout_ms: repo_setting.GH_TIMEOUT_MS,
19
+ })
20
+ }
21
+
22
+ function read_default_branch(repo: string): string | undefined {
23
+ const branch = git_gh_exec
24
+ .read_gh_api_sync({
25
+ path: `repos/${repo}`,
26
+ jq_filter: '.default_branch',
27
+ cwd: PROJECT_ROOT,
28
+ timeout_ms: repo_setting.GH_TIMEOUT_MS,
29
+ })
30
+ ?.trim()
31
+
32
+ return branch === '' ? undefined : branch
33
+ }
34
+
35
+ // Unlike `read_api`, keeps the failure's HTTP status: a 404 here means "no protection", which is an
36
+ // answer, while any other failure is not.
37
+ function read_protection(repo: string, branch: string): ApiRead {
38
+ try {
39
+ const stdout = git_gh_exec.exec_gh_api_sync({
40
+ path: `repos/${repo}/branches/${branch}/protection/required_status_checks`,
41
+ cwd: PROJECT_ROOT,
42
+ timeout_ms: repo_setting.GH_TIMEOUT_MS,
43
+ })
44
+
45
+ return { kind: 'body', stdout }
46
+ } catch (error) {
47
+ return { kind: 'failed', status: gh_failure.failure_of(error)?.status }
48
+ }
49
+ }
50
+
51
+ // Rulesets and classic branch protection both, since GitHub enforces both; unreadable rules stop
52
+ // there rather than letting protection alone pass for the whole answer.
53
+ function read_source(repo: string, branch: string): RequiredSource {
54
+ const from_rules = required_checks_logic.parse_branch_rules(
55
+ read_api(`repos/${repo}/rules/branches/${branch}`),
56
+ )
57
+ if (from_rules.kind === 'unreadable') return from_rules
58
+ const from_protection = required_checks_logic.parse_protection(read_protection(repo, branch))
59
+
60
+ return required_checks_logic.combine_sources(from_rules, from_protection)
61
+ }
62
+
63
+ // The checks `root` should require, decided by the jobs its distributed workflows define on disk.
64
+ function expected_in(root: string): Array<string> {
65
+ return required_checks_logic.expected_checks((workflow) =>
66
+ file_reader.read_if_readable(path.join(root, workflow)),
67
+ )
68
+ }
69
+
70
+ function contexts_of(source: RequiredSource): ReadonlyArray<string> {
71
+ return source.kind === 'ruleset' || source.kind === 'protection' ? source.contexts : []
72
+ }
73
+
74
+ function inspect(repo: string | undefined, root: string): RequiredChecksReport {
75
+ const expected = expected_in(root)
76
+ const branch = repo === undefined ? undefined : read_default_branch(repo)
77
+ const source: RequiredSource =
78
+ repo === undefined || branch === undefined ? { kind: 'unreadable' } : read_source(repo, branch)
79
+ const missing =
80
+ source.kind === 'unreadable'
81
+ ? []
82
+ : required_checks_logic.missing_checks(expected, contexts_of(source))
83
+
84
+ return { repo, branch, source, expected, missing }
85
+ }
86
+
87
+ function apply_to_ruleset(repo: string, ruleset_id: number, missing: ReadonlyArray<string>): void {
88
+ const api_path = `repos/${repo}/rulesets/${String(ruleset_id)}`
89
+ const current = git_gh_exec.exec_gh_api_sync({ path: api_path, cwd: PROJECT_ROOT })
90
+ const body = required_checks_logic.ruleset_update_body(current, missing)
91
+ if (body === undefined) throw new Error(`Unexpected ruleset response from ${api_path}`)
92
+
93
+ git_gh_exec.exec_gh_api_sync({ path: api_path, method: 'PUT', body, cwd: PROJECT_ROOT })
94
+ }
95
+
96
+ function apply_to_protection(repo: string, branch: string, missing: ReadonlyArray<string>): void {
97
+ git_gh_exec.exec_gh_api_sync({
98
+ path: `repos/${repo}/branches/${branch}/protection/required_status_checks/contexts`,
99
+ method: 'POST',
100
+ body: required_checks_logic.protection_update_body(missing),
101
+ cwd: PROJECT_ROOT,
102
+ })
103
+ }
104
+
105
+ function apply_to_source(
106
+ repo: string,
107
+ branch: string,
108
+ source: RequiredSource,
109
+ missing: ReadonlyArray<string>,
110
+ ): boolean {
111
+ if (source.kind === 'ruleset') {
112
+ apply_to_ruleset(repo, source.ruleset_id, missing)
113
+
114
+ return true
115
+ }
116
+
117
+ if (source.kind !== 'protection') return false
118
+ apply_to_protection(repo, branch, missing)
119
+
120
+ return true
121
+ }
122
+
123
+ // The one write: append the missing checks to the rule that already requires checks. A repository
124
+ // with neither a ruleset rule nor protection gets nothing created for it, which `format_report`
125
+ // explains. Throws on a failed request so the caller can report it; `false` when nothing was written.
126
+ function apply_missing(report: RequiredChecksReport): boolean {
127
+ const { repo, branch, source, missing } = report
128
+ if (repo === undefined || branch === undefined || missing.length === 0) return false
129
+
130
+ return apply_to_source(repo, branch, source, missing)
131
+ }
132
+
133
+ // The `josh doctor` section: read, never write.
134
+ function report_required_checks_section(
135
+ repo: string | undefined,
136
+ root: string,
137
+ ): RequiredChecksReport {
138
+ const report = inspect(repo, root)
139
+
140
+ repo_setting.print_section(required_checks_report.format_report(report))
141
+
142
+ return report
143
+ }
144
+
145
+ const ruleset_check = { expected_in, inspect, apply_missing, report_required_checks_section }
146
+
147
+ export { ruleset_check }
@@ -42,8 +42,4 @@ async function main(): Promise<void> {
42
42
  process.exitCode = await read_and_run()
43
43
  }
44
44
 
45
- const report_lint_cli = { run, OK_MESSAGE }
46
-
47
45
  if (process.argv[1] === fileURLToPath(import.meta.url)) await main()
48
-
49
- export { report_lint_cli }
@@ -37,7 +37,7 @@ function main(argv: ReadonlyArray<string>): void {
37
37
  process.exitCode = run(argv)
38
38
  }
39
39
 
40
- const disposition_cli = { USAGE, main, reason_line, run }
40
+ const disposition_cli = { USAGE, reason_line, run }
41
41
 
42
42
  if (process.argv[1] === fileURLToPath(import.meta.url)) main(process.argv.slice(ARGV_OFFSET))
43
43
 
@@ -1,5 +1,5 @@
1
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
1
2
  import { git_command } from '#scripts/git/git-command'
2
- import { git_gh_command } from '#scripts/git/git-gh-command'
3
3
  import { reproduction_measure } from '#scripts/issue/reproduction-measure'
4
4
  import { test_declared_logic, type Verdict } from '#scripts/test/test-declared-logic'
5
5
 
@@ -101,13 +101,7 @@ async function main(argv: ReadonlyArray<string>): Promise<void> {
101
101
  }
102
102
 
103
103
  const review_attest_cli = {
104
- CHECK_FLAG,
105
- main,
106
- NOT_REQUIRED_LINE,
107
104
  run,
108
- run_check,
109
- UNKNOWN_NONCE_LINE,
110
- USAGE,
111
105
  }
112
106
 
113
107
  if (process.argv[1] === fileURLToPath(import.meta.url)) await main(process.argv.slice(ARGV_OFFSET))
@@ -1,7 +1,9 @@
1
1
  import { randomBytes } from 'node:crypto'
2
+ import { lstatSync } from 'node:fs'
2
3
  import { git_command } from '#scripts/git/git-command'
3
4
  import { PROJECT_ROOT } from '#scripts/init/init-paths'
4
5
  import { stamp_file } from '#scripts/josh/stamp-file'
6
+ import { json_value } from '#scripts/lib/json-value'
5
7
  import { review_checkout, type ReviewCheckout } from './review-checkout'
6
8
 
7
9
  // The record that says which checkout a `/code-review` actually read (joshuafolkken/kit#1522).
@@ -80,17 +82,7 @@ function attest_path(nonce: string): string {
80
82
  function read_json(source: string): unknown {
81
83
  const raw = stamp_file.read_stamp_text(source)
82
84
 
83
- if (raw === undefined) return undefined
84
-
85
- try {
86
- return JSON.parse(raw)
87
- } catch {
88
- return undefined
89
- }
90
- }
91
-
92
- function is_record(value: unknown): value is Record<string, unknown> {
93
- return typeof value === 'object' && value !== null
85
+ return raw === undefined ? undefined : json_value.parse_or_undefined(raw)
94
86
  }
95
87
 
96
88
  function string_field(value: Record<string, unknown>, key: string): string | undefined {
@@ -100,7 +92,7 @@ function string_field(value: Record<string, unknown>, key: string): string | und
100
92
  }
101
93
 
102
94
  function to_checkout(value: unknown): ReviewCheckout | undefined {
103
- if (!is_record(value)) return undefined
95
+ if (!json_value.is_record(value)) return undefined
104
96
 
105
97
  const root = string_field(value, 'root')
106
98
  const branch = string_field(value, 'branch')
@@ -112,7 +104,7 @@ function to_checkout(value: unknown): ReviewCheckout | undefined {
112
104
  }
113
105
 
114
106
  function to_pointer(value: unknown): PointerRecord | undefined {
115
- if (!is_record(value)) return undefined
107
+ if (!json_value.is_record(value)) return undefined
116
108
 
117
109
  const nonce = string_field(value, 'nonce')
118
110
  const taken_at = string_field(value, 'taken_at')
@@ -240,6 +232,47 @@ function check(now: number = Date.now(), root?: string): AttestVerdict {
240
232
  return verdict
241
233
  }
242
234
 
235
+ function is_written_since(source: string, taken_at: string): boolean {
236
+ try {
237
+ const stats = lstatSync(source)
238
+
239
+ return stats.isFile() && stats.mtimeMs >= Date.parse(taken_at)
240
+ } catch {
241
+ return false
242
+ }
243
+ }
244
+
245
+ // **Whether the last briefed review of this branch finished** (joshuafolkken/kit#2945). A ship resumed
246
+ // after a stop re-issues round 1, and without this answer it briefed the whole change again — the
247
+ // files the finished review had just read, re-read at full cost. It is asked before `record_target`
248
+ // mints the next nonce, since that call drops the record it reads. The branch must match: the pointer
249
+ // is keyed on the root, and a lane root outlives the branch a previous run reviewed there.
250
+ //
251
+ // **An attestation alone is not a finished review.** The reviewer may attest and then die before it
252
+ // reports — a timeout, a usage limit — and narrowing on that would call files nobody read "already
253
+ // read". So the findings file, written only once the review has its verdict, must postdate the brief
254
+ // this pointer names; one left from an earlier review is older than that brief and does not count.
255
+ function is_reviewed_on(checkout: ReviewCheckout, findings_path: string, root?: string): boolean {
256
+ const pointer = read_pointer(root)
257
+
258
+ if (pointer === undefined) return false
259
+
260
+ const verdict = verdict_for(pointer.nonce)
261
+
262
+ if (verdict.status !== 'ok' || verdict.expected?.branch !== checkout.branch) return false
263
+
264
+ return is_written_since(findings_path, pointer.taken_at)
265
+ }
266
+
267
+ // Whether a record written at `written_at` belongs to the brief the pointer names rather than an earlier
268
+ // one (joshuafolkken/kit#2945): that brief writes it right after minting the pointer. ISO timestamps
269
+ // order as strings.
270
+ function is_briefed_since(written_at: string, root?: string): boolean {
271
+ const pointer = read_pointer(root)
272
+
273
+ return pointer !== undefined && written_at >= pointer.taken_at
274
+ }
275
+
243
276
  // The pointer is keyed on the repository root rather than on `process.cwd()`, which is what
244
277
  // `PROJECT_ROOT` is. `josh review:brief` records against the root it just asked git for, so the check
245
278
  // has to ask the same question: run the two from different directories — one from `scripts/`, one
@@ -294,18 +327,17 @@ function refusal_message(verdict: AttestVerdict): string {
294
327
  }
295
328
 
296
329
  const review_attest = {
297
- ATTEST_PREFIX,
298
330
  attest,
299
331
  attest_path,
300
332
  check,
301
333
  check_here,
302
334
  clear,
303
335
  clear_here,
304
- EXPECT_PREFIX,
305
336
  expect_path,
337
+ is_briefed_since,
338
+ is_reviewed_on,
306
339
  MISMATCH_VERDICT,
307
340
  MISSING_REASON,
308
- POINTER_PREFIX,
309
341
  pointer_path,
310
342
  read_attestation,
311
343
  read_expectation,